Initialize Tizen 3.0 32/35932/1 accepted/tizen_3.0.2015.q1_common tizen_3.0.2015.q1_common tizen_3.0.2015.q2_common accepted/tizen/3.0.2015.q1/common/20150323.084221 accepted/tizen/common/20150313.083743 accepted/tizen/mobile/20150313.084815 accepted/tizen/tv/20150313.084515 accepted/tizen/wearable/20150313.084641 submit/tizen/20150313.025220 submit/tizen_3.0.2015.q1_common/20150320.000000
authorDoohwan Kim <dh8210.kim@samsung.com>
Thu, 26 Feb 2015 08:39:16 +0000 (17:39 +0900)
committerDoohwan Kim <dh8210.kim@samsung.com>
Thu, 26 Feb 2015 08:39:45 +0000 (17:39 +0900)
Signed-off-by: Doohwan Kim <dh8210.kim@samsung.com>
Change-Id: I2ad7e23e121506673a724f512fea429489928451

450 files changed:
AUTHORS [new file with mode: 0644]
COPYING [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
LICENSE-BSD [new file with mode: 0644]
Makefile.am [new file with mode: 0644]
NEWS [new file with mode: 0644]
README [new file with mode: 0644]
README.md [new file with mode: 0644]
bootstrap [new file with mode: 0755]
build-aux/enlicense [new file with mode: 0755]
build-aux/gen-linkedin-loader [new file with mode: 0755]
build-aux/gen-linker-script [new file with mode: 0755]
build-aux/gen-linker-script.collect-symbols [new file with mode: 0755]
build-aux/gen-linker-script.ctags [new file with mode: 0755]
build-aux/git-version-gen [new file with mode: 0755]
build-aux/shave-libtool.in [new file with mode: 0644]
build-aux/shave.in [new file with mode: 0644]
configure.ac [new file with mode: 0644]
doc/CODING-STYLE [new file with mode: 0644]
doc/Makefile.am [new file with mode: 0644]
doc/Makefile.rules [new file with mode: 0644]
doc/common/figures/db-layers.svg [new file with mode: 0644]
doc/plugin-developer-guide/Makefile.am [new file with mode: 0644]
doc/plugin-developer-guide/db/Makefile.am [new file with mode: 0644]
doc/plugin-developer-guide/doxml/Doxyfile [new file with mode: 0644]
doc/plugin-developer-guide/doxml/Makefile.am [new file with mode: 0644]
doc/plugin-developer-guide/lyx/murphy-db-high-level-api.lyx [new file with mode: 0644]
doc/plugin-developer-guide/lyx/murphy-db-introduction.lyx [new file with mode: 0644]
doc/plugin-developer-guide/lyx/murphy-db-query-language.lyx [new file with mode: 0644]
doc/plugin-developer-guide/lyx/plugin-developer-guide.lyx [new file with mode: 0644]
doc/plugins/resource-dbus/dbus-api-resource.txt [new file with mode: 0644]
doc/plugins/resource-dbus/resource-client.py [new file with mode: 0644]
doc/scripts/abnf.py [new file with mode: 0755]
doc/scripts/dblyxfix.py [new file with mode: 0755]
doc/scripts/doxml2db.py [new file with mode: 0755]
doc/scripts/doxydeps.py [new file with mode: 0755]
githooks/commit-msg [new file with mode: 0755]
githooks/post-rewrite [new file with mode: 0755]
githooks/pre-commit [new file with mode: 0755]
githooks/pre-rebase [new file with mode: 0755]
m4/docsetup.m4 [new file with mode: 0644]
m4/shave.m4 [new file with mode: 0644]
m4/websockets.m4 [new file with mode: 0644]
packaging.in/murphy-lua.conf [new file with mode: 0644]
packaging.in/murphy-wait-for-launchpad-ready.path [new file with mode: 0644]
packaging.in/murphy.lua [new file with mode: 0644]
packaging.in/murphy.spec.in [new file with mode: 0644]
packaging.in/murphyd.conf [new file with mode: 0644]
packaging.in/murphyd.init [new file with mode: 0755]
packaging.in/murphyd.service [new file with mode: 0644]
packaging.in/org.Murphy.conf.in [new file with mode: 0644]
packaging/murphy.manifest [new file with mode: 0644]
packaging/murphy.spec [new file with mode: 0644]
patches/lua/lua-5.1.4-specfile.patch [new file with mode: 0644]
patches/lua/lua-5.2.2-specfile.patch [new file with mode: 0644]
scripts/cleanup-whitespace.sh [new file with mode: 0755]
src/Makefile.am [new file with mode: 0644]
src/breedline/LICENSE-BSD [new file with mode: 0644]
src/breedline/Makefile [new file with mode: 0644]
src/breedline/README [new file with mode: 0644]
src/breedline/breedline-glib.c [new file with mode: 0644]
src/breedline/breedline-glib.h [new file with mode: 0644]
src/breedline/breedline-glib.pc.in [new file with mode: 0644]
src/breedline/breedline-murphy.c [new file with mode: 0644]
src/breedline/breedline-murphy.h [new file with mode: 0644]
src/breedline/breedline-murphy.pc.in [new file with mode: 0644]
src/breedline/breedline.c [new file with mode: 0644]
src/breedline/breedline.h [new file with mode: 0644]
src/breedline/breedline.pc.in [new file with mode: 0644]
src/breedline/macros.h [new file with mode: 0644]
src/breedline/mm.h [new file with mode: 0644]
src/breedline/tests/Makefile.am [new file with mode: 0644]
src/breedline/tests/breedline-glib-test.c [new file with mode: 0644]
src/breedline/tests/breedline-murphy-test.c [new file with mode: 0644]
src/breedline/tests/breedline-test.c [new file with mode: 0644]
src/common.h [new file with mode: 0644]
src/common/Makefile [new file with mode: 0644]
src/common/dbus-error.h [new file with mode: 0644]
src/common/dbus-libdbus-glue.c [new file with mode: 0644]
src/common/dbus-libdbus-transport.c [new file with mode: 0644]
src/common/dbus-libdbus.c [new file with mode: 0644]
src/common/dbus-libdbus.h [new file with mode: 0644]
src/common/dbus-sdbus-glue.c [new file with mode: 0644]
src/common/dbus-sdbus-transport.c [new file with mode: 0644]
src/common/dbus-sdbus.c [new file with mode: 0644]
src/common/dbus-sdbus.h [new file with mode: 0644]
src/common/dbus-transport.c [new file with mode: 0644]
src/common/dbus-transport.h [new file with mode: 0644]
src/common/debug-auto-register.c [new file with mode: 0644]
src/common/debug-info.h [new file with mode: 0644]
src/common/debug.c [new file with mode: 0644]
src/common/debug.h [new file with mode: 0644]
src/common/dgram-transport.c [new file with mode: 0644]
src/common/ecore-glue.c [new file with mode: 0644]
src/common/ecore-glue.h [new file with mode: 0644]
src/common/env.c [new file with mode: 0644]
src/common/env.h [new file with mode: 0644]
src/common/file-utils.c [new file with mode: 0644]
src/common/file-utils.h [new file with mode: 0644]
src/common/fragbuf.c [new file with mode: 0644]
src/common/fragbuf.h [new file with mode: 0644]
src/common/glib-glue.c [new file with mode: 0644]
src/common/glib-glue.h [new file with mode: 0644]
src/common/hashtbl.c [new file with mode: 0644]
src/common/hashtbl.h [new file with mode: 0644]
src/common/internal-transport.c [new file with mode: 0644]
src/common/json.c [new file with mode: 0644]
src/common/json.h [new file with mode: 0644]
src/common/libdbus-glue.c [new file with mode: 0644]
src/common/libdbus.c [new file with mode: 0644]
src/common/libdbus.h [new file with mode: 0644]
src/common/list.h [new file with mode: 0644]
src/common/log.c [new file with mode: 0644]
src/common/log.h [new file with mode: 0644]
src/common/macros.h [new file with mode: 0644]
src/common/mainloop.c [new file with mode: 0644]
src/common/mainloop.h [new file with mode: 0644]
src/common/mask.h [new file with mode: 0644]
src/common/mm.c [new file with mode: 0644]
src/common/mm.h [new file with mode: 0644]
src/common/msg.c [new file with mode: 0644]
src/common/msg.h [new file with mode: 0644]
src/common/murphy-common.pc.in [new file with mode: 0644]
src/common/murphy-dbus-libdbus.pc.in [new file with mode: 0644]
src/common/murphy-dbus-sdbus.pc.in [new file with mode: 0644]
src/common/murphy-ecore.pc.in [new file with mode: 0644]
src/common/murphy-glib.pc.in [new file with mode: 0644]
src/common/murphy-libdbus.pc.in [new file with mode: 0644]
src/common/murphy-pulse.pc.in [new file with mode: 0644]
src/common/murphy-qt.pc.in [new file with mode: 0644]
src/common/native-types.c [new file with mode: 0644]
src/common/native-types.h [new file with mode: 0644]
src/common/process.c [new file with mode: 0644]
src/common/process.h [new file with mode: 0644]
src/common/pulse-glue.c [new file with mode: 0644]
src/common/pulse-glue.h [new file with mode: 0644]
src/common/pulse-subloop.c [new file with mode: 0644]
src/common/pulse-subloop.h [new file with mode: 0644]
src/common/qt-glue-priv.h [new file with mode: 0644]
src/common/qt-glue.cpp [new file with mode: 0644]
src/common/qt-glue.h [new file with mode: 0644]
src/common/refcnt.h [new file with mode: 0644]
src/common/socket-utils.c [new file with mode: 0644]
src/common/socket-utils.h [new file with mode: 0644]
src/common/stream-transport.c [new file with mode: 0644]
src/common/tests/Makefile.am [new file with mode: 0644]
src/common/tests/dbus-pump.c [new file with mode: 0644]
src/common/tests/dbus-sdbus-test.c [new file with mode: 0644]
src/common/tests/dbus-test.c [new file with mode: 0644]
src/common/tests/fragbuf-test.c [new file with mode: 0644]
src/common/tests/glib-pump.c [new file with mode: 0644]
src/common/tests/hash-test.c [new file with mode: 0644]
src/common/tests/hash12-test.c [new file with mode: 0644]
src/common/tests/internal-transport-test.c [new file with mode: 0644]
src/common/tests/libdbus-test.c [new file with mode: 0644]
src/common/tests/libdbus-transport-test.c [new file with mode: 0644]
src/common/tests/mainloop-ecore-test.c [new file with mode: 0644]
src/common/tests/mainloop-glib-test.c [new file with mode: 0644]
src/common/tests/mainloop-pulse-test.c [new file with mode: 0644]
src/common/tests/mainloop-qt-test.cpp [new file with mode: 0644]
src/common/tests/mainloop-qt-test.h [new file with mode: 0644]
src/common/tests/mainloop-test.c [new file with mode: 0644]
src/common/tests/mask-test.c [new file with mode: 0644]
src/common/tests/mkdir-test.c [new file with mode: 0644]
src/common/tests/mm-test.c [new file with mode: 0644]
src/common/tests/msg-test.c [new file with mode: 0644]
src/common/tests/native-test.c [new file with mode: 0644]
src/common/tests/path-test.c [new file with mode: 0644]
src/common/tests/process-test.c [new file with mode: 0644]
src/common/tests/sdbus-error-message.c [new file with mode: 0644]
src/common/tests/sdbus-test.c [new file with mode: 0644]
src/common/tests/transport-test.c [new file with mode: 0644]
src/common/tlv.c [new file with mode: 0644]
src/common/tlv.h [new file with mode: 0644]
src/common/transport.c [new file with mode: 0644]
src/common/transport.h [new file with mode: 0644]
src/common/utils.c [new file with mode: 0644]
src/common/utils.h [new file with mode: 0644]
src/common/websocket.c [new file with mode: 0644]
src/common/websocket.h [new file with mode: 0644]
src/common/websocklib.c [new file with mode: 0644]
src/common/websocklib.h [new file with mode: 0644]
src/common/wsck-transport.c [new file with mode: 0644]
src/common/wsck-transport.h [new file with mode: 0644]
src/console-client/Makefile [new file with mode: 0644]
src/console-client/client.c [new file with mode: 0644]
src/core.h [new file with mode: 0644]
src/core/Makefile [new file with mode: 0644]
src/core/auth-deny.c [new file with mode: 0644]
src/core/auth-smack.c [new file with mode: 0644]
src/core/auth.c [new file with mode: 0644]
src/core/auth.h [new file with mode: 0644]
src/core/console-builtin.c [new file with mode: 0644]
src/core/console-command.c [new file with mode: 0644]
src/core/console-command.h [new file with mode: 0644]
src/core/console-db.c [new file with mode: 0644]
src/core/console-debug.c [new file with mode: 0644]
src/core/console-log.c [new file with mode: 0644]
src/core/console-priv.h [new file with mode: 0644]
src/core/console.c [new file with mode: 0644]
src/core/console.h [new file with mode: 0644]
src/core/context.c [new file with mode: 0644]
src/core/context.h [new file with mode: 0644]
src/core/domain-types.h [new file with mode: 0644]
src/core/domain.c [new file with mode: 0644]
src/core/domain.h [new file with mode: 0644]
src/core/event.c [new file with mode: 0644]
src/core/event.h [new file with mode: 0644]
src/core/lua-bindings/Makefile [new file with mode: 0644]
src/core/lua-bindings/lua-bitwise.c [new file with mode: 0644]
src/core/lua-bindings/lua-console.c [new file with mode: 0644]
src/core/lua-bindings/lua-deferred.c [new file with mode: 0644]
src/core/lua-bindings/lua-env.c [new file with mode: 0644]
src/core/lua-bindings/lua-event.c [new file with mode: 0644]
src/core/lua-bindings/lua-json.c [new file with mode: 0644]
src/core/lua-bindings/lua-json.h [new file with mode: 0644]
src/core/lua-bindings/lua-log.c [new file with mode: 0644]
src/core/lua-bindings/lua-lua.c [new file with mode: 0644]
src/core/lua-bindings/lua-murphy.c [new file with mode: 0644]
src/core/lua-bindings/lua-plugin.c [new file with mode: 0644]
src/core/lua-bindings/lua-sighandler.c [new file with mode: 0644]
src/core/lua-bindings/lua-timer.c [new file with mode: 0644]
src/core/lua-bindings/lua-transport.c [new file with mode: 0644]
src/core/lua-bindings/murphy.h [new file with mode: 0644]
src/core/lua-decision/Makefile [new file with mode: 0644]
src/core/lua-decision/element.c [new file with mode: 0644]
src/core/lua-decision/element.h [new file with mode: 0644]
src/core/lua-decision/mdb.c [new file with mode: 0644]
src/core/lua-decision/mdb.h [new file with mode: 0644]
src/core/lua-decision/murphy-lua-decision.pc.in [new file with mode: 0644]
src/core/lua-decision/tests/Makefile.am [new file with mode: 0644]
src/core/lua-decision/tests/decision-test.c [new file with mode: 0644]
src/core/lua-decision/tests/decision-test.lua [new file with mode: 0644]
src/core/lua-utils/Makefile [new file with mode: 0644]
src/core/lua-utils/error.c [new file with mode: 0644]
src/core/lua-utils/error.h [new file with mode: 0644]
src/core/lua-utils/funcbridge.c [new file with mode: 0644]
src/core/lua-utils/funcbridge.h [new file with mode: 0644]
src/core/lua-utils/include.c [new file with mode: 0644]
src/core/lua-utils/include.h [new file with mode: 0644]
src/core/lua-utils/lua-utils.c [new file with mode: 0644]
src/core/lua-utils/lua-utils.h [new file with mode: 0644]
src/core/lua-utils/murphy-lua-utils.pc.in [new file with mode: 0644]
src/core/lua-utils/object.c [new file with mode: 0644]
src/core/lua-utils/object.h [new file with mode: 0644]
src/core/lua-utils/strarray.c [new file with mode: 0644]
src/core/lua-utils/strarray.h [new file with mode: 0644]
src/core/method.c [new file with mode: 0644]
src/core/method.h [new file with mode: 0644]
src/core/murphy-core.pc.in [new file with mode: 0644]
src/core/plugin.c [new file with mode: 0644]
src/core/plugin.h [new file with mode: 0644]
src/core/scripting.c [new file with mode: 0644]
src/core/scripting.h [new file with mode: 0644]
src/core/tests/Makefile.am [new file with mode: 0644]
src/daemon/Makefile [new file with mode: 0644]
src/daemon/config.c [new file with mode: 0644]
src/daemon/config.h [new file with mode: 0644]
src/daemon/daemon.c [new file with mode: 0644]
src/daemon/daemon.h [new file with mode: 0644]
src/daemon/murphy-lua.conf [new file with mode: 0644]
src/daemon/murphy.conf [new file with mode: 0644]
src/daemon/murphy.lua [new file with mode: 0644]
src/daemon/sample-config/cgroup-test.rules [new file with mode: 0644]
src/daemon/sample-config/common.cfg [new file with mode: 0644]
src/daemon/sample-config/console.cfg [new file with mode: 0644]
src/daemon/sample-config/domain-control.cfg [new file with mode: 0644]
src/daemon/sample-config/glib.cfg [new file with mode: 0644]
src/daemon/sample-config/main.cfg [new file with mode: 0644]
src/daemon/sample-config/murphy.cfg [new file with mode: 0644]
src/daemon/sample-config/resource.cfg [new file with mode: 0644]
src/daemon/sample-config/speed-volume.rules [new file with mode: 0644]
src/daemon/sample-config/system-controller.cfg [new file with mode: 0644]
src/daemon/sample-config/system-monitor.cfg [new file with mode: 0644]
src/daemon/sample-config/system-monitor.rules [new file with mode: 0644]
src/daemon/sample-config/systemd.cfg [new file with mode: 0644]
src/daemon/sample-config/timer-test.rules [new file with mode: 0644]
src/daemon/tests/Makefile.am [new file with mode: 0644]
src/murphy-db/Makefile.am [new file with mode: 0644]
src/murphy-db/include/Makefile.am [new file with mode: 0644]
src/murphy-db/include/murphy-db/assert.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/handle.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/hash.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/list.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mdb.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mqi-types.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mqi.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mql-result.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mql-statement.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mql-trigger.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/mql.h [new file with mode: 0644]
src/murphy-db/include/murphy-db/sequence.h [new file with mode: 0644]
src/murphy-db/mdb/Makefile.am [new file with mode: 0644]
src/murphy-db/mdb/column.c [new file with mode: 0644]
src/murphy-db/mdb/column.h [new file with mode: 0644]
src/murphy-db/mdb/cond.c [new file with mode: 0644]
src/murphy-db/mdb/cond.h [new file with mode: 0644]
src/murphy-db/mdb/handle.c [new file with mode: 0644]
src/murphy-db/mdb/hash.c [new file with mode: 0644]
src/murphy-db/mdb/index.c [new file with mode: 0644]
src/murphy-db/mdb/index.h [new file with mode: 0644]
src/murphy-db/mdb/log.c [new file with mode: 0644]
src/murphy-db/mdb/log.h [new file with mode: 0644]
src/murphy-db/mdb/mqi-types.c [new file with mode: 0644]
src/murphy-db/mdb/row.c [new file with mode: 0644]
src/murphy-db/mdb/row.h [new file with mode: 0644]
src/murphy-db/mdb/sequence.c [new file with mode: 0644]
src/murphy-db/mdb/table.c [new file with mode: 0644]
src/murphy-db/mdb/table.h [new file with mode: 0644]
src/murphy-db/mdb/transaction.c [new file with mode: 0644]
src/murphy-db/mdb/transaction.h [new file with mode: 0644]
src/murphy-db/mdb/trigger.c [new file with mode: 0644]
src/murphy-db/mdb/trigger.h [new file with mode: 0644]
src/murphy-db/mqi/Makefile.am [new file with mode: 0644]
src/murphy-db/mqi/db.h [new file with mode: 0644]
src/murphy-db/mqi/mdb-backend.c [new file with mode: 0644]
src/murphy-db/mqi/mdb-backend.h [new file with mode: 0644]
src/murphy-db/mqi/mqi.c [new file with mode: 0644]
src/murphy-db/mql/Makefile.am [new file with mode: 0644]
src/murphy-db/mql/mql-parser.y [new file with mode: 0644]
src/murphy-db/mql/mql-scanner.l [new file with mode: 0644]
src/murphy-db/mql/result.c [new file with mode: 0644]
src/murphy-db/mql/statement.c [new file with mode: 0644]
src/murphy-db/mql/transaction.c [new file with mode: 0644]
src/murphy-db/mql/trigger.c [new file with mode: 0644]
src/murphy-db/murphy-db.pc.in [new file with mode: 0644]
src/murphy-db/tests/Makefile.am [new file with mode: 0644]
src/murphy-db/tests/check-libmdb.c [new file with mode: 0644]
src/murphy-db/tests/check-libmqi.c [new file with mode: 0644]
src/murphy-db/tests/check-libmql.c [new file with mode: 0644]
src/plugins/Makefile [new file with mode: 0644]
src/plugins/console-protocol.h [new file with mode: 0644]
src/plugins/console/Makefile [new file with mode: 0644]
src/plugins/console/console.html [new file with mode: 0644]
src/plugins/console/console.js [new file with mode: 0644]
src/plugins/console/plugin-console.c [new file with mode: 0644]
src/plugins/domain-control/Makefile [new file with mode: 0644]
src/plugins/domain-control/client.c [new file with mode: 0644]
src/plugins/domain-control/client.h [new file with mode: 0644]
src/plugins/domain-control/domain-control-api.js [new file with mode: 0644]
src/plugins/domain-control/domain-control-test.html [new file with mode: 0644]
src/plugins/domain-control/domain-control-types.h [new file with mode: 0644]
src/plugins/domain-control/domain-control.c [new file with mode: 0644]
src/plugins/domain-control/domain-control.h [new file with mode: 0644]
src/plugins/domain-control/message.c [new file with mode: 0644]
src/plugins/domain-control/message.h [new file with mode: 0644]
src/plugins/domain-control/murphy-domain-controller.pc.in [new file with mode: 0644]
src/plugins/domain-control/notify.c [new file with mode: 0644]
src/plugins/domain-control/notify.h [new file with mode: 0644]
src/plugins/domain-control/plugin-domain-control.c [new file with mode: 0644]
src/plugins/domain-control/proxy.c [new file with mode: 0644]
src/plugins/domain-control/proxy.h [new file with mode: 0644]
src/plugins/domain-control/table.c [new file with mode: 0644]
src/plugins/domain-control/table.h [new file with mode: 0644]
src/plugins/domain-control/test-client.c [new file with mode: 0644]
src/plugins/plugin-dbus.c [new file with mode: 0644]
src/plugins/plugin-glib.c [new file with mode: 0644]
src/plugins/plugin-lua.c [new file with mode: 0644]
src/plugins/plugin-resource-dbus.c [new file with mode: 0644]
src/plugins/plugin-systemd.c [new file with mode: 0644]
src/plugins/plugin-test.c [new file with mode: 0644]
src/plugins/resource-dbus/org.Murphy.conf [new file with mode: 0644]
src/plugins/resource-native/Makefile [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/api_test.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/attribute.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/attribute.h [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/context-create.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/message.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/message.h [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/murphy-resource.pc.in [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/resource-api.h [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/resource-fuzz.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/resource-log.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/resource-private.h [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/resource.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/rset.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/rset.h [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/string_array.c [new file with mode: 0644]
src/plugins/resource-native/libmurphy-resource/string_array.h [new file with mode: 0644]
src/plugins/resource-native/plugin-resource-native.c [new file with mode: 0644]
src/plugins/resource-native/resource-client.c [new file with mode: 0644]
src/plugins/resource-wrt/Makefile [new file with mode: 0644]
src/plugins/resource-wrt/plugin-resource-wrt.c [new file with mode: 0644]
src/plugins/resource-wrt/resource-api.js [new file with mode: 0644]
src/plugins/resource-wrt/resource-test.html [new file with mode: 0644]
src/plugins/resource-wrt/resource-wrt.h [new file with mode: 0644]
src/plugins/tests/Makefile.am [new file with mode: 0644]
src/resolver/console.c [new file with mode: 0644]
src/resolver/events.c [new file with mode: 0644]
src/resolver/events.h [new file with mode: 0644]
src/resolver/fact.c [new file with mode: 0644]
src/resolver/fact.h [new file with mode: 0644]
src/resolver/murphy-resolver.pc.in [new file with mode: 0644]
src/resolver/parser-api.h [new file with mode: 0644]
src/resolver/parser.y [new file with mode: 0644]
src/resolver/resolver-types.h [new file with mode: 0644]
src/resolver/resolver.c [new file with mode: 0644]
src/resolver/resolver.h [new file with mode: 0644]
src/resolver/scanner.h [new file with mode: 0644]
src/resolver/scanner.l [new file with mode: 0644]
src/resolver/scripting/simple/builtins.c [new file with mode: 0644]
src/resolver/scripting/simple/builtins.h [new file with mode: 0644]
src/resolver/scripting/simple/call.c [new file with mode: 0644]
src/resolver/scripting/simple/call.h [new file with mode: 0644]
src/resolver/scripting/simple/simple-parser-api.h [new file with mode: 0644]
src/resolver/scripting/simple/simple-parser.y [new file with mode: 0644]
src/resolver/scripting/simple/simple-scanner.h [new file with mode: 0644]
src/resolver/scripting/simple/simple-scanner.l [new file with mode: 0644]
src/resolver/scripting/simple/simple-script.c [new file with mode: 0644]
src/resolver/scripting/simple/simple-script.h [new file with mode: 0644]
src/resolver/scripting/simple/token.h [new file with mode: 0644]
src/resolver/target-sorter.c [new file with mode: 0644]
src/resolver/target-sorter.h [new file with mode: 0644]
src/resolver/target.c [new file with mode: 0644]
src/resolver/target.h [new file with mode: 0644]
src/resolver/test-input [new file with mode: 0644]
src/resolver/test-input-audio [new file with mode: 0644]
src/resolver/test-input-video [new file with mode: 0644]
src/resolver/tests/Makefile.am [new file with mode: 0644]
src/resolver/tests/parser-test.c [new file with mode: 0644]
src/resolver/tests/test [new file with mode: 0644]
src/resolver/token.h [new file with mode: 0644]
src/resource/Makefile [new file with mode: 0644]
src/resource/application-class.c [new file with mode: 0644]
src/resource/application-class.h [new file with mode: 0644]
src/resource/attribute.c [new file with mode: 0644]
src/resource/attribute.h [new file with mode: 0644]
src/resource/client-api.h [new file with mode: 0644]
src/resource/common-api.h [new file with mode: 0644]
src/resource/config-api.h [new file with mode: 0644]
src/resource/config-lua.c [new file with mode: 0644]
src/resource/config-lua.h [new file with mode: 0644]
src/resource/data-types.h [new file with mode: 0644]
src/resource/lua-resource.c [new file with mode: 0644]
src/resource/manager-api.h [new file with mode: 0644]
src/resource/protocol.h [new file with mode: 0644]
src/resource/resource-client.c [new file with mode: 0644]
src/resource/resource-client.h [new file with mode: 0644]
src/resource/resource-lua.c [new file with mode: 0644]
src/resource/resource-lua.h [new file with mode: 0644]
src/resource/resource-owner.c [new file with mode: 0644]
src/resource/resource-owner.h [new file with mode: 0644]
src/resource/resource-set.c [new file with mode: 0644]
src/resource/resource-set.h [new file with mode: 0644]
src/resource/resource.c [new file with mode: 0644]
src/resource/resource.h [new file with mode: 0644]
src/resource/zone.c [new file with mode: 0644]
src/resource/zone.h [new file with mode: 0644]
utils/Makefile.am [new file with mode: 0644]
utils/collect-symbols.c [new file with mode: 0644]

diff --git a/AUTHORS b/AUTHORS
new file mode 100644 (file)
index 0000000..4ba079b
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,4 @@
+Janos Kovacs <janos.kovacs at intel dot com>
+Jaska Uimonen <jaska.uimonen at intel dot com>
+Ismo Puustinen <ismo.puustinen at intel dot com>
+Krisztian Litkey <krisztian.litkey at intel dot com>
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..fde7dc7
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,14 @@
+All Murphy source code files are licensed under the three-clause BSD
+license. See file LICENSE-BSD for the license text.
+
+It is possible to configure Murphy to use GPLv2-licensed libraries. At
+least libdbus is licensed under the GPLv2, and under some circumstances
+PulseAudio client library might also fall under the GPLv2. If Murphy is
+configured to use these GPL-licensed libraries, Murphy must be
+distributed under the restrictions defined in GPLv2 license text.
+
+To prevent mistakes in licensing, Murphy needs to be configured with
+--enable-gpl configuration option in order to use the GPLv2-licensed
+libaries. This is meant as a heads-up for the system integrator to
+double-check the licensing and distribution of possible proprietary
+Murphy plugins.
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..8d1c8b6
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1 @@
diff --git a/LICENSE-BSD b/LICENSE-BSD
new file mode 100644 (file)
index 0000000..a52ad55
--- /dev/null
@@ -0,0 +1,26 @@
+Copyright (c) 2012, 2013, Intel Corporation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Intel Corporation nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644 (file)
index 0000000..657bda0
--- /dev/null
@@ -0,0 +1,50 @@
+AUTOMAKE_OPTIONS = foreign
+ACLOCAL_AMFLAGS = -I m4
+
+SUBDIRS  = . utils src doc
+doc_DATA = AUTHORS ChangeLog NEWS README
+
+# This is the only way with automake I know of to force 'check-git-hooks'
+# to be evaluated before 'all'. If there is a nicer way, I'm all ears...
+BUILT_SOURCES = install-git-hooks
+
+###################################
+# git hook management
+#
+
+install-git-hooks:
+       if test -d .git; then                                       \
+           for hook in githooks/???*; do                           \
+               case $$hook in                                      \
+                   *.sample|*~|*.swp) continue;;                   \
+               esac;                                               \
+               if test -x $$hook -a                                \
+                     ! -x .git/hooks/$${hook##*/}; then            \
+                   echo "Installing git hook $${hook##*/}...";     \
+                   cp $$hook .git/hooks;                           \
+                   chmod a+x .git/hooks/$${hook##*/};              \
+               fi                                                  \
+           done                                                    \
+       fi
+
+check-git-hooks:
+       if test -d .git; then                                                \
+           for hook in githooks/???*; do                                    \
+               case $$hook in                                               \
+                   *.sample|*~|*.swp) continue;;                            \
+               esac;                                                        \
+               if test -x $$hook -a ! -e .git/hooks/$${hook##*/}; then      \
+                   echo "";                                                 \
+                   echo "WARNING:";                                         \
+                   echo "WARNING: You have an uninstalled git hook $$hook"; \
+                   echo "WARNING: Please, consider taking it into use by";  \
+                   echo "WARNING: running 'make install-git-hooks'...";     \
+                   echo "WARNING:";                                         \
+                   echo "";                                                 \
+               fi                                                           \
+           done                                                             \
+       fi
+
+# cleanup
+clean-local:
+       rm -f *~
diff --git a/NEWS b/NEWS
new file mode 100644 (file)
index 0000000..8d1c8b6
--- /dev/null
+++ b/NEWS
@@ -0,0 +1 @@
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..0f217db
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+Please see README.md.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..1e08b54
--- /dev/null
+++ b/README.md
@@ -0,0 +1,84 @@
+Murphy resource policy manager
+==============================
+
+What is Murphy?
+---------------
+
+Murphy is a centralized resource policy daemon. Murphy assigns system
+resources to applications in an event-based and centralized way, meaning
+that resources are given to applications that request them in a priority
+order.
+
+Murphy works with many different resource domains and is intended to
+handle cross-domain resource conflicts. An example of this might be an
+interdependency between power management resources, audio resources, and
+video resources. If an application requires an instance of all the three
+resource types to work properly, taking a single one away from it should
+result in the release of the entire set of resources, since the
+application anyway requires the whole set to work properly.
+
+Murphy is designed to be extensively scripted and extended. The logic
+behind the decisions is encoded in the scipts in form of policy rules.
+These rules will be provided by the system intergrator. The extensions
+are implemented as Murphy plugins or external domain controllers.
+
+How does Murphy work?
+---------------------
+
+Murphy listens to three input event types:
+
+1.  System events
+2.  Application requests
+3.  User-provided settings
+
+An input event (such as an application requesting permission to play
+audio) may change a value in Murphy internal database. This in turn
+triggers the decision making mechanism. The decision is made and
+communicated to domain controllers, which enforce the resource limits
+for different resource domains, and to the applications competing for
+the resources. Resource-aware applications are expected to comply with
+the decision, but for some domains the decisions can also be enforced.
+For the audio domain it might mean stopping or muting the stream that
+was not allowed to play.
+
+Why is Murphy needed?
+---------------------
+
+The main idea is to move policy responsibilities away from the
+applications. Automatic arbitration of the available resources is
+important especially in embedded systems with limited user interaction
+capabilities.
+
+The applications need to decide by themselves who can access the limited
+resources if there is no centralized resource manager. In order to do
+this, all applications have to:
+
+*  understand the resource limits in the system (both software and
+   hardware)
+*  follow other applications' access to the resources
+*  define application priorities for resource access; who can access the
+   resource is there is a conflict?
+*  handle exceptional cases, such as non-resource aware applications
+   accessing the limited resources
+
+It is clear that it is extremely difficult for the applications to
+cooperate in this way, and implementing and maintaining this support for
+every single application running on a system is a huge undertaking.
+Changing a policy would require changes to all applications. But if the
+applications offload the resource policing responsibilities to a central
+resource manager, the applications only need to use a well-defined
+resource API to request access to resources and follow the resource
+status. In order to do a policy change, the system integrator only needs
+to change the policy in resource manager configuration, and the desired
+behavior should automatically follow.
+
+Compilation of Murphy
+---------------------
+
+Detailed information on building Murphy, the dependencies and options
+can be found at the [documentation](https://01.org/murphy/documentation/compiling-and-installing-murphy).
+
+In general, Murphy is an Autotools-based project, so users who have
+used Autotools before should be relatively "at home" with the process
+of generating the configure script as well as configuring and compiling
+Murphy.
diff --git a/bootstrap b/bootstrap
new file mode 100755 (executable)
index 0000000..ac327fd
--- /dev/null
+++ b/bootstrap
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+aclocal -I m4 && \
+    autoheader && \
+        libtoolize --copy --force && \
+            autoconf && \
+                automake --add-missing --copy
+
+status=$?
+
+if [ $status == 0 ]; then
+    if [ -n "$1" ]; then
+        [ "$1" == "configure" ] && shift || :
+        ./configure $*
+        status=$?
+    fi
+else
+    echo "Failed to bootstrap."
+fi
+
+exit $status
diff --git a/build-aux/enlicense b/build-aux/enlicense
new file mode 100755 (executable)
index 0000000..79b98db
--- /dev/null
@@ -0,0 +1,132 @@
+#!/bin/bash
+
+TOP="${0%/*}/.."
+LICENSE="$TOP/LICENSE-BSD"
+EXCLUDE=""
+
+show_usage () {
+    echo "usage: $0 [options]"
+    echo "The possible options are:"
+    echo "  --dry-run|-n         Just find files lacking license info."
+    echo "  --license|-L <file>  Use file to obtain license text."
+    echo "  --git|-g             Add license only to files in the repository."
+    echo "  --exclude|-e <pat>   Exclude files matching egrep pattern <pat>."
+    echo "  --help|-h            Show this help and exit."
+}
+
+fatal () {
+    local _err _msg
+
+    _err="$1"
+    shift
+    _msg="$*"
+
+    echo "fatal error: $_msg"
+    exit $_err
+}
+
+enlicense () {
+    local _file _in _out
+
+    case $1 in
+        *-func-info.c)
+            return 0
+            ;;
+    esac
+
+    _file="$1"
+    _in="$1.no-license"
+    _out="$1.license"
+
+    cp $_file $_in
+    echo "Inserting licensing information to $_file..."
+    echo "/*"                                    > $_out
+    cat $LICENSE | sed 's/^    /  /g;s/^/ * /g' \
+                 | sed 's/ *$//g'               >> $_out
+    echo " */"                                  >> $_out
+    echo ""                                     >> $_out
+    cat $_in                                    >> $_out
+    cp $_out $_file
+}
+
+find_missing_licenses () {
+    local _lacking _files _f
+
+    _lacking=""
+    if [ -z "$EXCLUDE" ]; then
+        _files="`find . -name '*.[hc]'`"
+    else
+        _files="`find . -name '*.[hc]' | egrep -v -e $EXCLUDE`"
+    fi
+
+    for _f in $_files; do
+        _f="${_f#./}"
+        grep -ql 'Copyright .*Intel .*' $_f
+        if [ $? != 0 ]; then
+            if [ "$GIT" = "y" ]; then
+                git ls-files | grep -q "$_f\$" && _lacking="$_lacking $_f" || :
+            else
+                _lacking="$_lacking $_f"
+            fi
+        fi
+    done
+
+    echo "$_lacking"
+}
+
+
+DRY_RUN=""
+GIT=""
+
+while [ "${1#-}" != "$1" -a -n "$1" ]; do
+    case $1 in
+        --dry-run|-n)
+            DRY_RUN="y"
+            ;;
+        --license|-L)
+            if [ -n "$2" ]; then
+                shift
+                LICENSE="$1"
+            else
+                fatal 1 "missing license argument"
+            fi
+            ;;
+        --git|-g)
+            GIT="y"
+            ;;
+        --exclude|-e)
+            if [ -n "$2" ]; then
+                shift
+                EXCLUDE="$1"
+            else
+                fatal 1 "missing exclusion pattern"
+            fi
+            ;;
+        --help|-h)
+            show_usage
+            exit 0
+            ;;
+         *)
+            echo "Unknown command line option \'$1\'."
+            show_usage
+            exit 1
+            ;;
+    esac
+    shift
+done
+
+if [ ! -f "$LICENSE" ]; then
+    fatal 1 "license file \'$LICENSE\' missing"
+fi
+
+pushd $TOP >& /dev/null
+
+lacking="`find_missing_licenses`"
+
+for f in $lacking; do
+    if [ "$DRY_RUN" != "y" ]; then
+        enlicense $f
+    else
+        echo "$f is lacking licensing information."
+    fi
+done
diff --git a/build-aux/gen-linkedin-loader b/build-aux/gen-linkedin-loader
new file mode 100755 (executable)
index 0000000..3bb4ed5
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/bash
+
+######################################################################
+# Generate helper code for making builtin DSO plugins available.
+#
+# Sometimes it is necessary to split a plugin to several source
+# files. Usually this is the case when the functionality provided
+# by the plugin is complex enough to require subdividing the plugin
+# into internal modules for the best maintainability.
+#
+# In these cases we cannot avoid making several functions and
+# variables globally available within the plugin so we cannot just
+# limit the scope of visibility by making them static. Still, we do
+# want to avoid conflicts between these symbols of different plugins.
+# To accomplish this we compile and link such plugins into shared
+# libraries (with our normal symbol visibility conventions) and then
+# link the murphy daemon against them.
+#
+# Since none of the code linked into murphy proper references
+# any of the symbols in any of these plugins without any further
+# special arrangements these plugins wouldn't be demand-loaded
+# and consequently would be unavailable when the daemon starts up.
+#
+# To overcome this every such plugin gets automatically augmented
+# by a plugin-specifig globally visible symbol and the murphy daemon
+# gets augmented by a symbol that references these plugin-specific
+# symbols. This forces the dynamic loader to load the plugins and
+# the normal builtin-plugin autoregistration mechanism takes care of
+# the rest.
+#
+# A bit too spaceship-ish, I admit... but nevertheless necessary
+# unless we want to give up the idea of builtin/linkedin plugins...
+#
+
+
+error () {
+    echo "error: $*" 1>&2
+}
+
+info () {
+    echo "$*" 1>&2
+}
+
+warning () {
+    echo "warning: $*" 1>&2
+}
+
+usage () {
+    info "usage: $0 -p <plugin> -o <output-file>, or"
+    info "usage: $0 -o <output-file> -d <plugin-list>"
+    exit ${1:-1}
+}
+
+emit () {
+    echo "$*" >> $OUTPUT
+}
+
+emit_plugin_loader () {
+    case $OUTPUT in
+        *.c) emit "int mrp_linkedin_plugin_${1//-/_}_symbol = 0;"
+             ;;
+        *.h) emit "extern int mrp_linkedin_plugin_${1//-/_}_symbol;"
+             ;;
+    esac
+}
+
+emit_murphy_loader () {
+    local _p
+
+    for _p in $*; do
+        emit "#include \"linkedin-$_p-loader.h\""
+    done
+    emit ""
+    emit "int mrp_load_linkedin_plugins(void)"
+    emit "{"
+    emit "    return \\"
+    for _p in $*; do
+        emit "        mrp_linkedin_plugin_${_p//-/_}_symbol + \\"
+    done
+    emit "        0;"
+    emit "}"
+}
+
+
+OUTPUT=""
+PLUGIN=""
+PLUGIN_LIST=""
+daemon=no
+
+#echo "*** $0 $* ***"
+
+# parse command line
+while [ -n "${1#-}" ]; do
+    case $1 in
+        -o)
+            if [ -z "$OUTPUT" ]; then
+                shift
+                OUTPUT="$1"
+            else
+                error "Multiple output files requested."
+                usage
+            fi
+            ;;
+        -p)
+            if [ -z "$PLUGIN" -a "$daemon" = "no" ]; then
+                shift
+                PLUGIN="$1"
+            else
+                if [ -n "$PLUGIN" ]; then
+                    error "Multiple builtin plugins specified."
+                else
+                    error "Both builtin plugin and plugin list specified."
+                fi
+                usage
+            fi
+            ;;
+
+        -h)
+            usage 0
+            ;;
+       -q)
+           QUIET="yes"
+           ;;
+        -d)
+            daemon=yes
+            ;;
+
+        -*)
+            error "Unknown option '$1'."
+            usage
+            ;;
+
+        *)
+            if [ "$daemon" = "yes" ]; then
+                PLUGIN_LIST="$PLUGIN_LIST $1"
+            else
+                error "Unexpected argument '$1'."
+                usage
+            fi
+            ;;
+    esac
+    shift
+done
+
+# check that we've got everything mandatory
+if [ -z "$OUTPUT" ]; then
+    error "No output file specified (use the -o option)."
+    usage
+fi
+
+if [ -z "$PLUGIN" -a "$daemon" = "no" ]; then
+    error "Neither builtin plugin nor plugin list is specified."
+    usage
+fi
+
+# generate the output
+rm -f $OUTPUT
+touch $OUTPUT
+if [ "$daemon" = "no" ]; then
+    emit_plugin_loader $PLUGIN
+else
+    emit_murphy_loader $PLUGIN_LIST
+fi
diff --git a/build-aux/gen-linker-script b/build-aux/gen-linker-script
new file mode 100755 (executable)
index 0000000..8ac31d4
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+#LOG=/tmp/gen-linker-script.log
+#echo "$0 $*" > $LOG
+
+COLLECT_SYMBOLS="${0%gen-linker-script}../utils/collect-symbols"
+
+ARGS=""
+
+while [ -n "$*" ]; do
+#   echo "[$ARGS]" 1>&2
+    case $1 in
+        -c) #echo "  [$1] [$2]" 1>&2;
+            ARGS="$ARGS -c '$2'"; shift 2;;
+         *) #echo "  [$1]" 1>&2;
+            ARGS="$ARGS '$1'"   ; shift 1;;
+    esac
+done
+
+#echo "ARGS: [$ARGS]" 1>&2
+#echo "ARGS: [$ARGS]" >> $LOG
+#echo "$COLLECT_SYMBOLS -g $ARGS" >> $LOG
+
+eval "$COLLECT_SYMBOLS -g $ARGS"
diff --git a/build-aux/gen-linker-script.collect-symbols b/build-aux/gen-linker-script.collect-symbols
new file mode 100755 (executable)
index 0000000..8ac31d4
--- /dev/null
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+#LOG=/tmp/gen-linker-script.log
+#echo "$0 $*" > $LOG
+
+COLLECT_SYMBOLS="${0%gen-linker-script}../utils/collect-symbols"
+
+ARGS=""
+
+while [ -n "$*" ]; do
+#   echo "[$ARGS]" 1>&2
+    case $1 in
+        -c) #echo "  [$1] [$2]" 1>&2;
+            ARGS="$ARGS -c '$2'"; shift 2;;
+         *) #echo "  [$1]" 1>&2;
+            ARGS="$ARGS '$1'"   ; shift 1;;
+    esac
+done
+
+#echo "ARGS: [$ARGS]" 1>&2
+#echo "ARGS: [$ARGS]" >> $LOG
+#echo "$COLLECT_SYMBOLS -g $ARGS" >> $LOG
+
+eval "$COLLECT_SYMBOLS -g $ARGS"
diff --git a/build-aux/gen-linker-script.ctags b/build-aux/gen-linker-script.ctags
new file mode 100755 (executable)
index 0000000..ca2272d
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/bash
+
+###############
+# Generate a linker script for GNU ld.
+#
+#
+#
+
+
+
+error () {
+    echo "error: $*" 1>&2
+}
+
+info () {
+    echo "$*" 1>&2
+}
+
+warning () {
+    echo "warning: $*" 1>&2
+}
+
+usage () {
+    info "usage: $0 [-p <pattern>] [-I <ignore-list>] -o <output> <inputs>"
+    exit ${1:-1}
+}
+
+emit () {
+    echo "$*" >> $OUTPUT
+}
+
+
+# set up defaults
+PATTERN="^mrp_|^_mrp_"                # export everything prefixed with mrp_
+IGNORE="MRP_PRINTF_LIKE,MRP_NULLTERM" # ignore these symbols/macros
+IT=","                                # ignore-list is comma-separated
+SOURCES=""                            # no default input, must be specified
+OUTPUT=""                             # no default output, must be specified
+
+# parse command line
+while [ -n "${1#-}" ]; do
+    case $1 in
+        -o)
+            if [ -z "$OUTPUT" ]; then
+                shift
+                OUTPUT="$1"
+            else
+                error "Multiple output files requested."
+                usage
+            fi
+            ;;
+        -p)
+           shift;
+            PATTERN="$1"
+            ;;
+        -I)
+            shift
+            IGNORE="$IGNORE$IT$1"
+           IT=","
+            ;;
+        -h)
+            usage 0
+            ;;
+       -q)
+           QUIET="yes"
+           ;;
+        -c)
+            # This is only for command-line compatibility with collect-symbols
+            # to minimize the impact of switching back and forth (if needed).
+            # collect-symbols gets compilation flags passed using the -c
+            # option which we simply ignore here when using ctags.
+            shift
+            ;;
+        -*)
+            error "Unknown option '$1'."
+            usage
+            ;;
+        *)
+            SOURCES="$SOURCES $1"
+            ;;
+    esac
+    shift
+done
+
+# check that we've got everything mandatory
+if [ -z "$OUTPUT" ]; then
+    error "No output file specified (use the -o option)."
+    usage
+fi
+
+if [ -z "$SOURCES" ]; then
+    warning "No input files, generating local-only linker script."
+    emit "{"
+    emit "    local:"
+    emit "        *;"
+    emit "};"
+    exit 0
+fi
+
+if [ -z "$PATTERN" ]; then
+    PATTERN="^mrp_"
+fi
+
+if [ -n "$IGNORE" ]; then
+    ignore_opts="-I $IGNORE"
+else
+    ignore_opts=""
+fi
+
+# check that we have ctags
+which ctags >& /dev/null
+if [ "$?" != "0" ]; then
+    error "Needs ctags to regenerate linker script $OUTPUT..."
+    exit 1
+fi
+
+# generate the output
+[ -n "$QUIET" ] || info "Generating linker script $OUTPUT..."
+rm -f $OUTPUT
+touch $OUTPUT
+
+emit "{"
+emit "    global:"
+ctags $ignore_opts -f - --c-kinds=px $SOURCES | \
+    awk "/$PATTERN/ { print \$1; }" | \
+        sort | \
+            while read sym; do
+                emit "        $sym;"
+            done
+
+emit "    local:"
+emit "        *;"
+emit "};"
diff --git a/build-aux/git-version-gen b/build-aux/git-version-gen
new file mode 100755 (executable)
index 0000000..f38082d
--- /dev/null
@@ -0,0 +1,154 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2008-04-08.07
+
+# Copyright (C) 2007-2008 Free Software Foundation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# 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 General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#      echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#      echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+    1) ;;
+    *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || exit 1
+    case $v in
+       *$nl*) v= ;; # reject multi-line output
+       [0-9]*) ;;
+       *) v= ;;
+    esac
+    test -z "$v" \
+       && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+    : # use $v
+elif test -d .git \
+    && v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+         || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && case $v in
+        v[0-9]*) ;;
+        *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+#    case $v in
+#      *-*-*) : git describe is okay three part flavor ;;
+#      *-*)
+#          : git describe is older two part flavor
+#          # Recreate the number of commits and rewrite such that the
+#          # result is the same as if we were using the newer version
+#          # of git describe.
+#          vtag=`echo "$v" | sed 's/-.*//'`
+#          numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+#          v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+#          ;;
+#    esac
+
+    # Change the first '-' to a '.', so version-comparing tools work properly.
+    # Remove the "g" in git describe's output string, to save a byte.
+#    v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+    :
+else
+    #v=UNKNOWN
+    v="0.0.0"
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+    '') ;;
+    *) # Append the suffix only if there isn't one already.
+       case $v in
+         *-dirty) ;;
+         *) v="$v-dirty" ;;
+       esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/build-aux/shave-libtool.in b/build-aux/shave-libtool.in
new file mode 100644 (file)
index 0000000..0f7fb12
--- /dev/null
@@ -0,0 +1,109 @@
+#!/bin/sh
+#
+# Copyright (c) 2009, Damien Lespiau <damien.lespiau@gmail.com>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+# we need sed
+SED=@SED@
+if test -z "$SED" ; then
+SED=sed
+fi
+
+lt_unmangle ()
+{
+   last_result=`echo $1 | $SED -e 's#.libs/##' -e 's#[0-9a-zA-Z_\-\.]*_la-##'`
+}
+
+# the real libtool to use
+LIBTOOL="$1"
+shift
+
+# if 1, don't print anything, the underlaying wrapper will do it
+pass_though=0
+
+# scan the arguments, keep the right ones for libtool, and discover the mode
+preserved_args=
+
+# have we seen the --tag option of libtool in the command line ?
+tag_seen=0
+
+while test "$#" -gt 0; do
+    opt="$1"
+    shift
+
+    case $opt in
+    --mode=*)
+        mode=`echo $opt | $SED -e 's/[-_a-zA-Z0-9]*=//'`
+        preserved_args="$preserved_args $opt"
+        ;;
+    -o)
+        lt_output="$1"
+        preserved_args="$preserved_args $opt"
+       ;;
+    --tag=*)
+        tag_seen=1
+        preserved_args="$preserved_args $opt"
+       ;;
+    *)
+        preserved_args="$preserved_args '$opt'"
+        ;;
+      esac
+done
+
+case "$mode" in
+compile)
+    # shave will be called and print the actual CC/CXX/LINK line
+    preserved_args="$preserved_args --shave-mode=$mode"
+    pass_though=1
+    ;;
+link)
+    preserved_args="$preserved_args --shave-mode=$mode"
+    Q="  LINK  "
+    ;;
+*)
+    # let's u
+    # echo "*** libtool: Unimplemented mode: $mode, fill a bug report"
+    ;;
+esac
+
+lt_unmangle "$lt_output"
+output=$last_result
+
+# automake does not add a --tag switch to its libtool invocation when
+# assembling a .s file and rely on libtool to infer the right action based
+# on the compiler name. As shave is using CC to hook a wrapper, libtool gets
+# confused. Let's detect these cases and add a --tag=CC option.
+tag=""
+if test $tag_seen -eq 0 -a x"$mode" = xcompile; then
+    tag="--tag=CC"
+fi
+
+if test -z $V; then
+    if test $pass_though -eq 0; then
+        echo "$Q$output"
+    fi
+    eval "$LIBTOOL --silent $tag $preserved_args"
+else
+    echo $LIBTOOL $tag $preserved_args
+    eval "$LIBTOOL $tag $preserved_args"
+fi
diff --git a/build-aux/shave.in b/build-aux/shave.in
new file mode 100644 (file)
index 0000000..5150a4f
--- /dev/null
@@ -0,0 +1,133 @@
+#!/bin/sh
+#
+# Copyright (c) 2009, Damien Lespiau <damien.lespiau@gmail.com>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation
+# files (the "Software"), to deal in the Software without
+# restriction, including without limitation the rights to use,
+# copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following
+# conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+# OTHER DEALINGS IN THE SOFTWARE.
+
+# we need sed
+SED=@SED@
+if test -z "$SED" ; then
+SED=sed
+fi
+
+lt_unmangle ()
+{
+   last_result=`echo $1 | $SED -e 's#.libs/##' -e 's#[0-9a-zA-Z_\-\.]*_la-##'`
+}
+
+# the tool to wrap (cc, cxx, ar, ranlib, ..)
+tool="$1"
+shift
+
+# the reel tool (to call)
+REEL_TOOL="$1"
+shift
+
+pass_through=0
+preserved_args=
+while test "$#" -gt 0; do
+    opt="$1"
+    shift
+
+    case $opt in
+    --shave-mode=*)
+        mode=`echo $opt | $SED -e 's/[-_a-zA-Z0-9]*=//'`
+        ;;
+    -o)
+        lt_output="$1"
+        preserved_args="$preserved_args $opt"
+        ;;
+    -out:*|/out:*)
+        lt_output="${opt#-out:}"
+        preserved_args="$preserved_args $opt"
+        ;;
+    *.l)
+         if [ "$tool" = "lex" ]; then
+          lt_output="$opt"
+        fi
+        preserved_args="$preserved_args $opt"
+        ;;
+    *.y)
+         if [ "$tool" = "yacc" ]; then
+          lt_output="$opt"
+        fi
+        preserved_args="$preserved_args $opt"
+        ;;
+    *)
+        preserved_args="$preserved_args '$opt'"
+        ;;
+      esac
+done
+
+# mode=link is handled in the libtool wrapper
+case "$mode,$tool" in
+link,*)
+    pass_through=1
+    ;;
+*,cxx)
+    Q="  CXX   "
+    ;;
+*,ccas)
+    Q="  AS    "
+    ;;
+*,cc)
+    Q="  CC    "
+    ;;
+*,fc)
+    Q="  FC    "
+    ;;
+*,f77)
+    Q="  F77   "
+    ;;
+*,objc)
+    Q="  OBJC   "
+    ;;
+*,mcs)
+    Q="  MCS   "
+    ;;
+*,lex)
+    Q="  LEX   "
+    ;;
+*,yacc)
+    Q="  YACC  "
+    ;;
+*,cc_for_build)
+    Q=" HOSTCC "
+    ;;
+*,*)
+    # should not happen
+    Q="  CC    "
+    ;;
+esac
+
+lt_unmangle "$lt_output"
+output=$last_result
+
+if test -z $V; then
+    if test $pass_through -eq 0; then
+        echo "$Q$output"
+    fi
+    eval "$REEL_TOOL $preserved_args"
+else
+    echo $REEL_TOOL $preserved_args
+    eval "$REEL_TOOL $preserved_args"
+fi
diff --git a/configure.ac b/configure.ac
new file mode 100644 (file)
index 0000000..0327584
--- /dev/null
@@ -0,0 +1,741 @@
+
+#                                               -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ(2.59)
+
+AC_INIT([murphy], m4_esyscmd([build-aux/git-version-gen .tarball-version]))
+
+AC_CONFIG_SRCDIR([src])
+AC_CONFIG_MACRO_DIR([m4])
+AC_CONFIG_HEADER([src/config.h])
+AM_INIT_AUTOMAKE([-Wno-portability])
+
+AC_SUBST(ACLOCAL_AMFLAGS, "-I m4")
+
+m4_define(version_major, `echo $VERSION | cut -d. -f1 | cut -d- -f1`)
+m4_define(version_minor, `echo $VERSION | cut -d. -f2 | cut -d- -f1`)
+m4_define(version_patch, `echo $VERSION | cut -d. -f3 | cut -d- -f1`)
+
+AC_SUBST(VERSION)
+AC_SUBST(VERSION_MAJOR, version_major)
+AC_SUBST(VERSION_MINOR, version_minor)
+AC_SUBST(VERSION_PATCH, version_patch)
+AC_SUBST(VERSION_FULL, version_major.version_minor.version_patch)
+
+MURPHY_VERSION_INFO="0:0:0"
+AC_SUBST(MURPHY_VERSION_INFO)
+
+# Disable static libraries.
+AC_DISABLE_STATIC
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_CC_C99
+# We need AC_PROG_CXX if Qt support is enabled but (at least some
+# versions of autotools) cannot handle conditional use of this.
+AC_PROG_CXX
+AC_PROG_AWK
+AC_PROG_INSTALL
+AM_PROG_CC_C_O
+AM_PROG_LIBTOOL
+AC_PROG_LEX
+AC_PROG_YACC
+AM_PROG_LEX
+AC_SUBST(LEXLIB)
+
+# Check that we have flex/bison and not lex/yacc.
+AC_MSG_CHECKING([for flex vs. lex])
+case $LEX in
+    *missing\ flex*)
+        AC_MSG_ERROR([looks like you're missing flex])
+        ;;
+    *flex*)
+        AC_MSG_RESULT([ok, looks like we have flex])
+        ;;
+    *)
+        AC_MSG_ERROR([flex is required])
+        ;;
+esac
+
+AC_MSG_CHECKING([for bison vs. yacc])
+case $YACC in
+    *missing\ *)
+        AC_MSG_ERROR([looks like you're missing bison])
+        ;;
+    *bison*)
+        AC_MSG_RESULT([ok, looks like we have bison])
+        ;;
+    *)
+        AC_MSG_ERROR([bison is required])
+        ;;
+esac
+
+# Guesstimate native compiler if we're cross-compiling.
+if test "$cross_compiling" != "no"; then
+    AC_MSG_NOTICE([Looks like we're being cross-compiled...])
+    if test -z "$CC_FOR_BUILD"; then
+        CC_FOR_BUILD=cc
+    fi
+else
+    AC_MSG_NOTICE([Looks like we're doing a native compilation...])
+    CC_FOR_BUILD='$(CC)'
+fi
+AC_SUBST(CC_FOR_BUILD)
+UNSHAVED_CC_FOR_BUILD="$CC_FOR_BUILD"
+
+# Make first invocation of PKG_CHECK_MODULES 'if-then-else-fi'-safe.
+PKG_PROG_PKG_CONFIG
+
+# Checks for libraries.
+AC_CHECK_LIB([dl], [dlopen dlclose dlsym dlerror])
+
+# Checks for header files.
+AC_PATH_X
+AC_CHECK_HEADERS([fcntl.h stddef.h stdint.h stdlib.h string.h sys/statvfs.h sys/vfs.h syslog.h unistd.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_HEADER_STDBOOL
+AC_C_INLINE
+AC_TYPE_INT16_T
+AC_TYPE_INT32_T
+AC_TYPE_INT64_T
+AC_TYPE_MODE_T
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_CHECK_MEMBERS([struct stat.st_rdev])
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT64_T
+AC_TYPE_UINT8_T
+AC_CHECK_TYPES([ptrdiff_t])
+
+# Checks for library functions.
+AC_FUNC_ERROR_AT_LINE
+AC_HEADER_MAJOR
+if test "$cross_compiling" = "no"; then
+    AC_FUNC_MALLOC
+fi
+AC_FUNC_STRTOD
+AC_CHECK_FUNCS([clock_gettime memmove memset regcomp strcasecmp strchr strdup strrchr strtol strtoul])
+
+# Check and enable extra compiler warnings if they are supported.
+AC_ARG_ENABLE(extra-warnings,
+              [  --enable-extra-warnings enable extra compiler warnings],
+             [extra_warnings=$enableval], [extra_warnings=auto])
+
+WARNING_CFLAGS=""
+warncflags="-Wall -Wextra"
+if test "$extra_warnings" != "no"; then
+    save_CPPFLAGS="$CPPFLAGS"
+    for opt in $warncflags; do
+        AC_PREPROC_IFELSE([AC_LANG_PROGRAM([])],
+                         [WARNING_CFLAGS="$WARNING_CFLAGS $opt"])
+    done
+    CPPFLAGS="$save_CPPFLAGS"
+fi
+
+AC_SUBST(WARNING_CFLAGS)
+
+# By default try to find the system default Lua (assumed to be
+# called lua(.pc). If that is not found, try to look for
+# packages lua5.2 and lua5.1, which can be found in Debian-based
+# distributions.
+#
+# You can override this using the --with-lua option. For instance
+# to use Lua 5.1 on Ubuntu while having 5.2 installed, you'd use
+# --with-lua=lua5.1.
+AC_ARG_WITH(lua,
+            [  --with-lua                build with specified Lua (pkgconfig filename without .pc suffix)],
+            [with_lua=$withval], [with_lua=default])
+
+if test "x$with_lua" = "xdefault"; then
+    # Check for "lua" first, then "lua5.2" and finally for "lua5.1"
+    AC_MSG_NOTICE([Checking for an installed Lua...])
+    PKG_CHECK_MODULES([LUA], [lua >= 5.1.1],
+        [with_lua=lua],
+        [PKG_CHECK_MODULES([LUA52], [lua5.2],
+            [with_lua=lua5.2
+             LUA_CFLAGS=$LUA52_CFLAGS
+             LUA_LIBS=$LUA52_LIBS],
+            [PKG_CHECK_MODULES([LUA51], [lua5.1 >= 5.1.1],
+                [with_lua=lua5.1
+                 LUA_CFLAGS=$LUA51_CFLAGS
+                 LUA_LIBS=$LUA51_LIBS],
+                [AC_MSG_ERROR(Package requirement (lua >= 5.1.1) was not met!)])
+         ])
+    ])
+else
+    # Check for pre-defined Lua.
+    AC_MSG_NOTICE([Compiling with Lua package $with_lua.])
+    PKG_CHECK_MODULES(LUA, $with_lua >= 5.1.1)
+fi
+
+AC_SUBST(LUA_CFLAGS)
+AC_SUBST(LUA_LIBS)
+
+# Check if potentially GPL bits are allowed to be enabled.
+AC_ARG_ENABLE(gpl,
+              [  --enable-gpl            enable linking against GPL code],
+             [enable_gpl=$enableval], [enable_gpl=no])
+
+# Check if original libdbus-based DBUS support was enabled.
+AC_ARG_ENABLE(libdbus,
+              [  --enable-libdbus        enable libdbus-based D-BUS support],
+             [enable_libdbus=$enableval], [enable_libdbus=no])
+
+if test "$enable_libdbus" = "yes"; then
+    if test "$enable_gpl" = "no"; then
+        AC_MSG_ERROR([libdbus D-Bus support requires the --enable-gpl option.])
+    fi
+    PKG_CHECK_MODULES(LIBDBUS, dbus-1 >= 0.70)
+
+    DBUS_SESSION_DIR="`pkg-config --variable session_bus_services_dir dbus-1`"
+    AC_SUBST(DBUS_SESSION_DIR)
+
+    AC_DEFINE([LIBDBUS_ENABLED], 1, [Enable libdbus D-Bus support ?])
+else
+    AC_MSG_NOTICE([libdbus-based D-Bus support is disabled.])
+fi
+
+AM_CONDITIONAL(LIBDBUS_ENABLED, [test "$enable_libdbus" = "yes"])
+AC_SUBST(LIBDBUS_ENABLED)
+AC_SUBST(LIBDBUS_CFLAGS)
+AC_SUBST(LIBDBUS_LIBS)
+
+# Check if systemd-bus-based D-Bus support was enabled.
+AC_ARG_ENABLE(sdbus,
+              [  --enable-sdbus         enable systemd-based D-BUS support],
+             [enable_sdbus=$enableval], [enable_sdbus=no])
+
+if test "$enable_sdbus" = "yes"; then
+    PKG_CHECK_MODULES(SDBUS, libsystemd-bus)
+    AC_DEFINE([SDBUS_ENABLED], 1, [Enable systemd-bus support ?])
+
+    if test -z "$DBUS_SESSION_DIR"; then
+        # Try to determine the session bus service directory.
+        DBUS_SESSION_DIR="`pkg-config --variable \
+                               session_bus_services_dir dbus-1`"
+        if test "$?" != "0" -o -z "$DBUS_SESSION_DIR"; then
+            DBUS_SESSION_DIR="/usr/share/dbus-1/services"
+        fi
+        AC_SUBST(DBUS_SESSION_DIR)
+    fi
+else
+    AC_MSG_NOTICE([libsystemd-bus based D-Bus support is disabled.])
+fi
+
+AM_CONDITIONAL(SDBUS_ENABLED, [test "$enable_sdbus" = "yes"])
+AC_SUBST(SDBUS_ENABLED)
+AC_SUBST(SDBUS_CFLAGS)
+AC_SUBST(SDBUS_LIBS)
+
+# Check if PulseAudio mainloop support was enabled.
+AC_ARG_ENABLE(pulse,
+              [  --enable-pulse          enable PulseAudio mainloop support],
+             [enable_pulse=$enableval], [enable_pulse=auto])
+
+if test "$enable_pulse" != "no"; then
+    PKG_CHECK_MODULES(PULSE, libpulse >= 0.9.22,
+                            [have_pulse=yes], [have_pulse=no])
+    if test "$have_pulse" = "no" -a "$enable_pulse" = "yes"; then
+        AC_MSG_ERROR([PulseAudio development libraries not found.])
+    fi
+
+    if test "$enable_gpl" = "no"; then
+        if test "$enable_pulse" = "yes"; then
+           AC_MSG_ERROR([PulseAudio support requires the --enable-gpl option.])
+        else
+           enable_pulse="no"
+        fi
+    else
+        enable_pulse="$have_pulse"
+    fi
+else
+    AC_MSG_NOTICE([PulseAudio mainloop support is disabled.])
+fi
+
+if test "$enable_pulse" = "yes"; then
+    AC_DEFINE([PULSE_ENABLED], 1, [Enable PulseAudio mainloop support ?])
+fi
+AM_CONDITIONAL(PULSE_ENABLED, [test "$enable_pulse" = "yes"])
+AC_SUBST(PULSE_ENABLED)
+AC_SUBST(PULSE_CFLAGS)
+AC_SUBST(PULSE_LIBS)
+
+# Check if EFL/ecore mainloop support was enabled.
+AC_ARG_ENABLE(ecore,
+              [  --enable-ecore          enable EFL/ecore mainloop support],
+             [enable_ecore=$enableval], [enable_ecore=auto])
+
+
+if test "$enable_ecore" != "no"; then
+    # We are using features which are present only at ecore 1.2 onwards.
+    PKG_CHECK_MODULES(ECORE, ecore >= 1.2,
+                      [have_ecore=yes], [have_ecore=no])
+    if test "$have_ecore" = "no" -a "$enable_ecore" = "yes"; then
+        AC_MSG_ERROR([EFL/ecore development libraries not found.])
+    fi
+
+    enable_ecore="$have_ecore"
+else
+    AC_MSG_NOTICE([EFL/ecore mainloop support is disabled.])
+fi
+
+if test "$enable_ecore" = "yes"; then
+    AC_DEFINE([ECORE_ENABLED], 1, [Enable EFL/ecore mainloop support ?])
+fi
+AM_CONDITIONAL(ECORE_ENABLED, [test "$enable_ecore" = "yes"])
+AC_SUBST(ECORE_ENABLED)
+AC_SUBST(ECORE_CFLAGS)
+AC_SUBST(ECORE_LIBS)
+
+# Check if glib mainloop support was enabled.
+AC_ARG_ENABLE(glib,
+              [  --enable-glib           enable glib mainloop support],
+             [enable_glib=$enableval], [enable_glib=auto])
+
+if test "$enable_glib" != "no"; then
+    PKG_CHECK_MODULES(GLIB, glib-2.0,
+                      [have_glib=yes], [have_glib=no])
+    if test "$have_glib" = "no" -a "$enable_glib" = "yes"; then
+        AC_MSG_ERROR([glib development libraries not found.])
+    fi
+
+    enable_glib="$have_glib"
+else
+    AC_MSG_NOTICE([glib mainloop support is disabled.])
+fi
+
+if test "$enable_glib" = "yes"; then
+    AC_DEFINE([GLIB_ENABLED], 1, [Enable glib mainloop support ?])
+fi
+AM_CONDITIONAL(GLIB_ENABLED, [test "$enable_glib" = "yes"])
+AC_SUBST(GLIB_ENABLED)
+AC_SUBST(GLIB_CFLAGS)
+AC_SUBST(GLIB_LIBS)
+
+# Check if qt mainloop support was enabled.
+AC_ARG_ENABLE(qt,
+              [  --enable-qt             enable qt mainloop support],
+              [enable_qt=$enableval], [enable_qt=auto])
+
+if test "$enable_qt" != "no"; then
+    PKG_CHECK_MODULES(QTCORE, QtCore,
+                      [have_qt=yes], [have_qt=no])
+    if test "$have_qt" = "no" -a "$enable_qt" = "yes"; then
+        AC_MSG_ERROR([Qt(Core) development libraries not found.])
+    fi
+
+    enable_qt="$have_qt"
+else
+    AC_MSG_NOTICE([Qt mainloop support is disabled.])
+fi
+
+if test "$enable_qt" = "yes"; then
+    AC_DEFINE([QT_ENABLED], 1, [Enable qt mainloop support ?])
+    QT_MOC="`pkg-config --variable moc_location QtCore`"
+    AC_SUBST(QT_MOC)
+fi
+AM_CONDITIONAL(QT_ENABLED, [test "$enable_qt" = "yes"])
+AC_SUBST(QT_ENABLED)
+AC_SUBST(QTCORE_CFLAGS)
+AC_SUBST(QTCORE_LIBS)
+
+# Check if building murphy-console was enabled.
+AC_ARG_ENABLE(console,
+              [  --enable-console        build Murphy console],
+             [enable_console=$enableval], [enable_console=yes])
+
+if test "$enable_console" = "no"; then
+    AC_MSG_NOTICE([Murphy console binary is disabled.])
+else
+    AC_MSG_NOTICE([Murphy console binary is enabled.])
+fi
+
+if test "$enable_console" = "yes"; then
+    AC_DEFINE([CONSOLE_ENABLED], 1, [Build Murphy console ?])
+fi
+AM_CONDITIONAL(CONSOLE_ENABLED, [test "$enable_console" = "yes"])
+AC_SUBST(CONSOLE_ENABLED)
+AC_SUBST(READLINE_CFLAGS)
+AC_SUBST(READLINE_LIBS)
+
+# Check for json(-c).
+PKG_CHECK_MODULES(JSON, [json], [have_json=yes], [have_json=no])
+
+if test "$have_json" = "no"; then
+    PKG_CHECK_MODULES(JSON, [json-c >= 0.11])
+fi
+
+AC_MSG_CHECKING([if json-c has headers under json-c include path])
+saved_CFLAGS="$CFLAGS"
+saved_LIBS="$LIBS"
+CFLAGS="${JSON_CFLAGS}"
+LIBS="${JSON_LIBS}"
+AC_LINK_IFELSE(
+   [AC_LANG_PROGRAM(
+         [[#include <../json-c/json.h>]],
+         [[return 0;]])],
+    [json_include_jsonc=yes],
+    [json_include_jsonc=no])
+AC_MSG_RESULT([$json_include_jsonc])
+CFLAGS="$saved_CFLAGS"
+LIBS="$saved_LIBS"
+
+if test "$json_include_jsonc" = "yes"; then
+    AC_DEFINE([JSON_INCLUDE_PATH_JSONC], 1, [json headers under json-c ?])
+fi
+
+AC_MSG_CHECKING([for json_tokener_get_error()])
+saved_CFLAGS="$CFLAGS"
+saved_LIBS="$LIBS"
+CFLAGS="${JSON_CFLAGS}"
+LIBS="${JSON_LIBS}"
+AC_LINK_IFELSE(
+   [AC_LANG_PROGRAM(
+         [[#include <json.h>]],
+         [[json_tokener *tok = NULL;
+           if (json_tokener_get_error(tok) != json_tokener_success)
+              return 0;
+           else
+               return 1;]])],
+    [have_json_tokener_get_error=yes],
+    [have_json_tokener_get_error=no])
+AC_MSG_RESULT([$have_json_tokener_get_error])
+CFLAGS="$saved_CFLAGS"
+LIBS="$saved_LIBS"
+
+if test "$have_json_tokener_get_error" = "yes"; then
+    AC_DEFINE([HAVE_JSON_TOKENER_GET_ERROR], 1, [json_tokener_get_error ?])
+fi
+
+# Check if websocket support was/can be enabled.
+CHECK_WEBSOCKETS()
+
+# Check if SMACK support should be enabled.
+AC_ARG_ENABLE(smack,
+              [  --enable-smack          enable SMACK support],
+             [enable_smack=$enableval], [enable_smack=auto])
+
+if test "$enable_smack" != "no"; then
+    PKG_CHECK_MODULES(SMACK, libsmack, [have_smack=yes], [have_smack=no])
+    if test "$have_smack" = "no" -a "$enable_smack" = "yes"; then
+        AC_MSG_ERROR([SMACK development libraries not found.])
+    fi
+
+    enable_smack="$have_smack"
+else
+    AC_MSG_NOTICE([SMACK support is disabled.])
+fi
+
+if test "$enable_smack" = "yes"; then
+    AC_DEFINE([SMACK_ENABLED], 1, [Enable SMACK support ?])
+fi
+AM_CONDITIONAL(SMACK_ENABLED, [test "$enable_smack" = "yes"])
+AC_SUBST(SMACK_ENABLED)
+AC_SUBST(SMACK_CFLAGS)
+AC_SUBST(SMACK_LIBS)
+
+# Check if systemd support should be enabled.
+AC_ARG_ENABLE(systemd,
+              [  --enable-systemd          enable systemd support],
+             [enable_systemd=$enableval], [enable_systemd=auto])
+
+if test "$enable_systemd" != "no"; then
+    PKG_CHECK_MODULES(SYSTEMD, libsystemd-journal libsystemd-daemon,
+                      [have_systemd=yes], [have_systemd=no])
+    if test "$have_systemd" = "no" -a "$enable_systemd" = "yes"; then
+        AC_MSG_ERROR([systemd development libraries not found.])
+    fi
+
+    enable_systemd="$have_systemd"
+else
+    AC_MSG_NOTICE([systemd support is disabled.])
+fi
+
+if test "$enable_systemd" = "yes"; then
+    AC_DEFINE([SYSTEMD_ENABLED], 1, [Enable systemd support ?])
+fi
+AM_CONDITIONAL(SYSTEMD_ENABLED, [test "$enable_systemd" = "yes"])
+AC_SUBST(SYSTEMD_ENABLED)
+AC_SUBST(SYSTEMD_CFLAGS)
+AC_SUBST(SYSTEMD_LIBS)
+
+# Set up murphy CFLAGS and LIBS.
+MURPHY_CFLAGS=""
+MURPHY_LIBS=""
+AC_SUBST(MURPHY_CFLAGS)
+AC_SUBST(MURPHY_LIBS)
+
+# Allow substitution for LIBDIR and SYSCONFDIR.
+AC_MSG_CHECKING([libdir])
+AC_MSG_RESULT([$libdir])
+AC_SUBST(LIBDIR, [$libdir])
+AC_MSG_CHECKING([sysconfdir])
+AC_MSG_RESULT([$sysconfdir])
+AC_SUBST(SYSCONFDIR, [$sysconfdir])
+
+#Check whether we build resources or not
+AC_ARG_WITH(resources,
+            [  --with-resources wheter to build resource management support],
+           [with_resources=$withval],[with_resources=yes])
+
+AM_CONDITIONAL(BUILD_RESOURCES,  [ test x$with_resources = "xyes" ])
+
+
+# Check which plugins should be disabled.
+AC_ARG_WITH(disabled-plugins,
+            [  --with-disabled-plugins=<plugin-list> specify which plugins to disable],
+            [disabled_plugins=$withval],[disabled_plugins=none])
+
+# Check which plugins should be compiled as standalone DSOs.
+AC_ARG_WITH(dynamic-plugins,
+            [  --with-dynamic-plugins=<plugin-list>  specify which plugins compile as DSOs],
+            [dynamic_plugins=$withval],[dynamic_plugins=none])
+
+all_plugins=$(find src/plugins/. -name plugin-*.c 2>/dev/null | \
+              sed 's#^.*/plugin-##g;s#\.c$##g' | tr '\n' ' ')
+
+#echo "all plugins: [$all_plugins]"
+
+case $dynamic_plugins in
+    all)  dynamic_plugins="$all_plugins";;
+    none) dynamic_plugins="";;
+esac
+
+internal=""; it=""
+external=""; et=""
+disabled=""; dt=""
+for plugin in $all_plugins; do 
+    type=internal
+
+    for p in ${dynamic_plugins//,/ }; do
+        if test "$plugin" = "$p"; then
+            type=external
+        fi
+    done
+
+    for p in ${disabled_plugins//,/ }; do
+        if test "$plugin" = "$p"; then
+            type=disabled
+        fi
+    done
+
+    case $type in
+        internal) internal="$internal$it$plugin"; it=" ";;
+        external) external="$external$et$plugin"; et=" ";;
+        disabled) disabled="$disabled$dt$plugin"; dt=" ";;
+    esac
+done
+
+DISABLED_PLUGINS="$disabled"
+INTERNAL_PLUGINS="$internal"
+EXTERNAL_PLUGINS="$external"
+
+
+function check_if_disabled() {
+    for p in $DISABLED_PLUGINS; do
+        if test "$1" = "$p"; then
+            return 0
+        fi
+    done
+
+    return 1
+}
+
+function check_if_internal() {
+    for p in $INTERNAL_PLUGINS; do
+        if test "$1" = "$p"; then
+            return 0
+        fi
+    done
+
+    return 1
+}
+
+AM_CONDITIONAL(DISABLED_PLUGIN_TEST,     [check_if_disabled test])
+AM_CONDITIONAL(DISABLED_PLUGIN_DBUS,     [check_if_disabled dbus])
+AM_CONDITIONAL(DISABLED_PLUGIN_GLIB,     [check_if_disabled glib])
+AM_CONDITIONAL(DISABLED_PLUGIN_CONSOLE,  [check_if_disabled console])
+AM_CONDITIONAL(DISABLED_PLUGIN_RESOURCE_DBUS, [check_if_disabled resource-dbus])
+AM_CONDITIONAL(DISABLED_PLUGIN_RESOURCE_WRT, [check_if_disabled resource-wrt])
+AM_CONDITIONAL(DISABLED_PLUGIN_DOMAIN_CONTROL,
+               [check_if_disabled domain-control])
+AM_CONDITIONAL(DISABLED_PLUGIN_SYSTEMD,  [check_if_disabled systemd])
+
+AM_CONDITIONAL(BUILTIN_PLUGIN_TEST,     [check_if_internal test])
+AM_CONDITIONAL(BUILTIN_PLUGIN_DBUS,     [check_if_internal dbus])
+AM_CONDITIONAL(BUILTIN_PLUGIN_GLIB,     [check_if_internal glib])
+AM_CONDITIONAL(BUILTIN_PLUGIN_CONSOLE,  [check_if_internal console])
+AM_CONDITIONAL(BUILTIN_PLUGIN_RESOURCE_DBUS, [check_if_internal resource-dbus])
+AM_CONDITIONAL(BUILTIN_PLUGIN_RESOURCE_WRT, [check_if_internal resource-wrt])
+AM_CONDITIONAL(BUILTIN_PLUGIN_DOMAIN_CONTROL,
+               [check_if_internal domain-control])
+AM_CONDITIONAL(BUILTIN_PLUGIN_LUA,      [check_if_internal lua])
+AM_CONDITIONAL(BUILTIN_PLUGIN_SYSTEMD,  [check_if_internal systemd])
+
+# Check for Check (unit test framework).
+PKG_CHECK_MODULES(CHECK, 
+                  check >= 0.9.4,
+                  [has_check="yes"], [has_check="no"])
+AM_CONDITIONAL(HAVE_CHECK, test "x$has_check" = "xyes")
+
+AC_SUBST(CHECK_CFLAGS)
+AC_SUBST(CHECK_LIBS)
+
+if test "x$has_check" = "xno"; then
+    AC_MSG_WARN([Check framework not found, unit tests are DISABLED.])
+fi
+
+# Check for documentation tools
+AC_ARG_WITH([documentation],
+            [AS_HELP_STRING([--with-documentation],
+                            [generate pdf, html and other doc files])],
+            [],
+            [with_documentation=auto]
+)
+
+AS_IF( [ test x$with_documentation = xno ],
+       [ has_doc_tools="no" ],
+       [ AC_PATH_TOOL([MRP_DOXYGEN], doxygen)
+         AC_PATH_TOOL([MRP_LYX], lyx)
+         AC_PATH_TOOL([MRP_INKSCAPE], inkscape)
+         AC_PATH_TOOL([MRP_PYTHON], python)
+         AC_PATH_TOOL([MRP_TOUCH], touch)
+         AC_PATH_TOOL([MRP_DBLATEX], dblatex)
+         AC_PATH_TOOL([MRP_XMLTO], xmlto)
+
+         AS_IF( [ test x$MRP_DOXYGEN = x -o x$MRP_LYX = x -o \
+                       x$MRP_INKSCAPE = x -o x$MRP_PYTHON = x -o \
+                       x$MRP_TOUCH = x],
+                [ has_doc_tools="no";
+                  AC_MSG_WARN([Some essential doc-tool is missing]) ],
+                [ has_doc_tools="yes";
+                  MRP_DOCINIT() ]
+         ) ]
+)
+
+AS_IF( [ test x$has_doc_tools == "xno" -o x$MRP_DBLATEX = x ],
+       [ can_make_pdfs="no";
+         AC_WARN([No PDF documentation will be generated]) ],
+       [ can_make_pdfs="yes"]
+)
+
+AS_IF([ test x$has_doc_tools == "xno" -o x$MRP_XMLTO = x ],
+      [ can_make_html="no";
+        AC_WARN([No HTML documentation will be generated]) ],
+      [ can_make_html="yes" ]
+)
+
+
+AM_CONDITIONAL(BUILD_DOCUMENTATION,  [ test x$has_doc_tools = "xyes" ])
+AM_CONDITIONAL(BUILD_PDF_DOCUMENTS,  [ test x$can_make_pdfs = "xyes" ])
+AM_CONDITIONAL(BUILD_HTML_DOCUMENTS, [ test x$can_make_html = "xyes" ])
+
+AC_SUBST(MRP_DOCDIR, [`pwd`/doc])
+AC_SUBST(MRP_FIGDIR, [$MRP_DOCDIR/common/figures])
+AC_SUBST(MRP_MAKE_DOCRULES, [$MRP_DOCDIR/Makefile.rules])
+AC_SUBST(MRP_DOCSCRIPT_DIR, [$MRP_DOCDIR/scripts])
+AC_SUBST(MRP_ABNF, [$MRP_DOCSCRIPT_DIR/abnf.py])
+AC_SUBST(MRP_DBLYXFIX, [$MRP_DOCSCRIPT_DIR/dblyxfix.py])
+AC_SUBST(MRP_DOXML2DB, [$MRP_DOCSCRIPT_DIR/doxml2db.py])
+AC_SUBST(MRP_DOXYDEPS, [$MRP_DOCSCRIPT_DIR/doxydeps.py])
+
+
+# Shave by default.
+SHAVE_INIT([build-aux], [enable])
+
+# Create murphy symlink to match domain controller's
+# placing with how it is installed.
+if test ! -L murphy/domain-control; then
+    AC_MSG_NOTICE([Symlinking src/plugins/domain-control to src/domain-control...])
+    cd src
+    ln -s plugins/domain-control domain-control
+    cd ..
+fi
+
+# Create murphy symlink to src.
+if test ! -L murphy; then
+    AC_MSG_NOTICE([Symlinking src to murphy...])
+    ln -s src murphy
+fi
+
+# Generate output.
+AC_CONFIG_FILES([build-aux/shave
+                build-aux/shave-libtool
+                Makefile
+                 utils/Makefile
+                src/Makefile
+                src/common/tests/Makefile
+                src/core/tests/Makefile
+                src/core/lua-decision/tests/Makefile
+                src/daemon/tests/Makefile
+                src/plugins/tests/Makefile
+                src/common/murphy-common.pc
+                src/common/murphy-libdbus.pc
+                src/common/murphy-dbus-libdbus.pc
+                src/common/murphy-dbus-sdbus.pc
+                src/common/murphy-pulse.pc
+                src/common/murphy-ecore.pc
+                src/common/murphy-glib.pc
+                 src/common/murphy-qt.pc
+                src/core/murphy-core.pc
+                 src/core/lua-utils/murphy-lua-utils.pc
+                 src/core/lua-decision/murphy-lua-decision.pc
+                src/breedline/breedline.pc
+                src/breedline/breedline-murphy.pc
+                src/breedline/breedline-glib.pc
+                src/breedline/tests/Makefile
+                src/murphy-db/Makefile
+                src/murphy-db/mdb/Makefile
+                src/murphy-db/mqi/Makefile
+                src/murphy-db/mql/Makefile
+                src/murphy-db/include/Makefile
+                src/murphy-db/tests/Makefile
+                src/resolver/murphy-resolver.pc
+                src/resolver/tests/Makefile
+                src/plugins/domain-control/murphy-domain-controller.pc
+                doc/Makefile
+                doc/plugin-developer-guide/Makefile
+                doc/plugin-developer-guide/db/Makefile
+                doc/plugin-developer-guide/doxml/Makefile
+                src/plugins/resource-native/libmurphy-resource/murphy-resource.pc
+                ])
+AC_OUTPUT
+
+
+# Display the configuration.
+echo "----- configuration -----"
+echo "Extra C warnings flags: $WARNING_CFLAGS"
+echo "Cross-compiling: $cross_compiling"
+if test "$cross_compiling" != "no"; then
+    echo "     * native compiler: $UNSHAVED_CC_FOR_BUILD"
+fi
+echo "Lua (pkgconfig file) to use: $with_lua"
+echo "    * cflags: $LUA_CFLAGS"
+echo "    * libs: $LUA_LIBS"
+echo "D-Bus (libdbus) support: $enable_libdbus"
+echo "D-Bus (systemd-bus) support: $enable_sdbus"
+echo "PulseAudio mainloop support: $enable_pulse"
+echo "EFL/ecore mainloop support: $enable_ecore"
+echo "glib mainloop support: $enable_glib"
+echo "Qt mainloop support: $enable_qt"
+echo "Murphy console plugin and client: $enable_console"
+echo "Resource management support: $with_resources"
+echo "Websockets support: $enable_websockets"
+echo "systemd support: $enable_systemd"
+echo "Plugins:"
+echo "  - linked-in:"
+for plugin in ${INTERNAL_PLUGINS:-none}; do
+    echo "      $plugin"
+done
+echo "  - dynamic:"
+for plugin in ${EXTERNAL_PLUGINS:-none}; do
+    echo "      $plugin"
+done
+echo "  - disabled:"
+for plugin in ${DISABLED_PLUGINS:-none}; do
+    echo "      $plugin"
+done
diff --git a/doc/CODING-STYLE b/doc/CODING-STYLE
new file mode 100644 (file)
index 0000000..4287cd2
--- /dev/null
@@ -0,0 +1,312 @@
+
+1. General Style
+
+Indentation level is 4. The indentation character is space, regardless of
+the overall indentation level. IOW, even if nested indentation causes the
+overall indentation level to be greater or equal to 8 use spaces only.
+Don't leave trailing white space at the end of lines.
+
+Line length is 80 columns. You need to break up longer lines to multiple
+chunks at reasonable boundaries. What counts as a reasonable boundary
+depends on what you are breaking up. There is very seldom, if ever, a
+good enough reason to break the 80-column line rule.
+
+
+2. Layout and Tabulation
+
+2.1 Curly Braces
+
+Opening curly braces are put last on the line, while closing curly braces
+are on lines of their own. Generally this applies to all control-flow
+blocks, IOW to if, for, while, do, and switch statements. The most notable
+exceptions are the while in a do-while statement, if-else-if chains and
+functions.
+
+Some examples:
+
+    if (!condition) {
+        foo();
+        foobar();
+    }
+    else {
+        bar();
+        barfoo();
+    }
+
+    while (check) {
+        do_something();
+        and_something_else();
+    }
+
+Exceptions:
+
+    do {
+        process();
+        and_more();
+    } while (need_to);
+
+    if (foo < bar) {
+        foo();
+        bar();
+    } else if (bar < foo) {
+        foobar();
+        barfoo();
+    } else {
+        frobnicate();
+        xyzzy();
+    }
+
+In a function you put both the opening and the closing braces on lines
+of their own:
+
+    int foo(int x)
+    {
+        if (x < 10)
+            return foobar(x);
+        else
+            return barfoo(x);
+    }
+
+
+If you have a single statement in a block, you can omit the opening
+and closing braces. However in an if-else it is preferred to keep the
+braces unless both branches are single statements. Also, do not omit
+the braces for do-while statements.
+
+Examples:
+
+    if (x < 0)
+        foo(x);
+    else
+        bar(x);
+
+Note however, that we prefer to keep braces here:
+
+    if (y < 10) {
+        foo(y);
+    }
+    else {
+        foobar(y);
+        barfoo(x);
+    }
+
+    do {
+        x = xyzzy();
+    } while (x < 10);
+
+
+2.2 Indentation and Spaces
+
+Individual cases and the default case is indented to align with the
+switch statement itself:
+
+    switch (foo) {
+    case 1:
+        so_its_one();
+        break;
+    case 2:
+        uh_two();
+        break;
+    case 3:
+        oh_boy_three();
+        break;
+    default:
+        oh_no(foo);
+    }
+
+If you have a more complex set of statements in the cases, it is preferable
+to include an extra empty line for clarity before all but the first case and
+the default branch.
+
+    switch (x) {
+    case 1:
+        if (y < 10)
+            horribly(x);
+        else if (y < 20)
+            complex(x);
+        else if (x + y > 200)
+            processing(x, y);
+        break;
+
+    case 2:
+        ...
+        break;
+
+    ...
+    default:
+        ...
+    }
+
+The general rule for space usage is the following:
+
+  - use one space after control-flow keywords (if, for, while, do,
+    switch, case)
+  - use one space around binary and and ternary operators (=, +, -,
+    *, /, %, |, &, ^, <, >, <=, >=, ==, !=, ? :)
+  - don't use space after unary operators (&, *, +, -, ~, !)
+  - don't use space after the following keywords: sizeof, typeof,
+    alignof, __attribute__
+  - don't use spaces before postincrement/postdecrement or pre-
+    increment/predecrement operators (++, --)
+  - don't use spaces around structure or union member operators (., ->)
+  - don't use spaces after the preprocessor directive 'defined'
+
+
+Deviating from these basic preferences is okay if it helps increasing
+readibility, for instance by resulting in more compact code and hence
+more code per editor surface area. For example, one might choose an
+alternative, more compact layout for a switch statement that consists
+only of trivially simple case branches, like this:
+
+    switch (f->value.type) {
+    case SI_TYPE_INT16:  f->value.i16 = va_arg(ap,  int32_t); break;
+    case SI_TYPE_UINT16: f->value.u16 = va_arg(ap, uint32_t); break;
+    case SI_TYPE_INT32:  f->value.i32 = va_arg(ap,  int32_t); break;
+    case SI_TYPE_UINT32: f->value.u32 = va_arg(ap, uint32_t); break;
+    case SI_TYPE_INT64:  f->value.i64 = va_arg(ap,  int64_t); break;
+    case SI_TYPE_UINT64: f->value.u64 = va_arg(ap, uint64_t); break;
+    case SI_TYPE_BOOL:   f->value.bln = va_arg(ap,      int); break;
+    case SI_TYPE_DOUBLE: f->value.dbl = va_arg(ap,   double); break;
+    default:
+        return FALSE;
+}
+
+
+2.3 Accepted Tabulation Schemes
+
+There are two tabulation schemes you can choose from:
+
+  1) obsessive alignitis: the manic alignment of variable definitions,
+     structure and union member definitions, variable assignments within
+     a code block end so forth
+
+  2) traditional K&R-ish reductionist style, minimizing the amount of
+     white space while adhering to the rules of the above sections
+
+But be consistent with yourself, do not try to mix these. Choose one
+and stick with it within a component. Also if you are touching any
+existing code obey the existing style.
+
+
+2.4 Code Layout
+
+Put licensing information to the beginning of every file.
+Within a module put includes and macro definitions in the beginning
+(of course). Then put type definitions, any necessary static function
+prototypes and finally module-local variables before the actual
+functions of the module.
+
+Within a function, define variables in the beginning of the function.
+You can also put variable definitions at the beginning of code blocks
+with curly braces, eg. within if, else, for, while, and switch-constructs.
+Please do not pull variables out of your arse^H^H^H^Hstack in the middle
+of a function at any other place.
+
+
+2.5 Error Handling
+
+There are two acceptable error-indication mechanism from functions:
+boolean style and libc style. It depends on your component which one makes
+sense. If upon failure it is of no use to the caller to get more
+information than the mere fact of failure use boolean style error
+indication returning true on success and false otherwise. In this case,
+include stdbool.h in your public header and use bool as the return type
+of your function to indicate the boolean convention to your readers. If
+you choose the libc-style, return 0 on success, return -1 on failure and
+also set errno to some reasonable error code in this case. Never ever
+clear errno on success. Naturally, functions that return objects should
+return NULL on failure with both of these styles.
+
+Whenever you have a non-trivial function with complex control-flow try
+to organise your code so that there is a single common error cleanup
+and return path. You can put this at the end of your function, label it
+as 'fail', and use goto's to jump from the middle of the function to the
+error-handling branch.
+
+
+2.6 Function Prototypes
+
+Use function prototypes with variable names in public header files.
+
+
+3. Naming Conventions
+
+The basic general naming rule is no CamelCase, use only lower-case
+letters, use underscore ('_') as a word separator within symbol names
+for functions and variables. Macros are all upper-case with the exception
+of function-like macros that take arguments. Just like functions, these
+can be all-lower case at will if this makes more sense. Typical cases
+when this makes sense would be a macro that wraps a function call and
+extends the argument list with additional parameters, or a guarding
+convenience wrapper macro with extra error checks for calling a function
+in some external component that does not handle some common error cases,
+such as being called with a NULL pointer on cleanup code paths.
+
+For Murphy core, all externally visible symbols must be prefixed with
+mrp_ to avoid name-space collisions with other components.
+
+3.1 Type Names
+
+Externally visible types (at least structs, unions and enums) must be
+typedef'd. The chosen type name must be prefixed consistently with the
+other publicly visible types from the same component. All typedef'd
+names are suffixed with _t. If you need to also have a non-typedef'd
+struct, or union for internal use for some reason, suffix it with _s
+or _u depending on its type.
+
+3.2 Function and Variable Names
+
+All externally visible functions and variables must be prefixed with
+a component-specific prefix-. For Murphy core this is mrp_. For libraries
+that can be easily re-used without Murphy, you can chose any prefix you
+like, but keep it short, preferably up to 3 or 4 characters max.
+
+For global variables always use fulle/long descriptive names, keeping in
+mind the lower-case only and underscore restrictions. For local variables
+and function arguments try to strike the right balance between compactness
+and descriptiveness. On one hand too long names hurt readability and waste
+a lot of precious real estate on our 80 character character terminals from
+the 70's. On the other hand too short or unintuitively shortened names
+fail to provide the right association about the usage of the variables for
+the reader. So this is not that black and white and it is not easy to give
+a set of strict rules that one could blindly follow. If someone familiar
+with the subject your code is addressing gets constantly confused with
+what your variables are for, you are probably overdoing the compactness
+part. If the same person gets constantly frustrated/tired reading your
+variable names, you are probably underdoing it...
+
+Library global function and variable names, ie. symbols whose visibility
+is limited to the scope of the library, should be prefixed with the module
+prefix within the library they are defined in.
+
+3.3 File Naming Conventions
+
+Don't prefix your file names with a component name. Use dashes instead
+of underscores in file and directory names.
+
+
+4. Miscallanea
+
+4.1 Use of Goto
+
+There are cases when the careful use of gotos make the code both easier
+to read/follow and less error-prone to modify/extend than with any of
+the alternatives. Typical examples are consolidated bailout/cleanup code
+on error paths from complex functions with nested blocks of if/for/while
+statements. Also it is often cleaner to use directly gotos instead of
+emulating them with an while-(0)-break non-loop construct.
+
+
+
+
+
+
+
+
+Generally speaking, it would be beneficial to come up with a basic set
+of rules that can also be expressed as a set of command line options to
+indent (or any other available alternative). That would provide a canonical
+way for people to check their code for indentation-conformance. Also we
+could use it ourselves in git hooks to warn about code with non-conformant
+style which otherwise might be difficult to avoid in the beginning.
+
diff --git a/doc/Makefile.am b/doc/Makefile.am
new file mode 100644 (file)
index 0000000..69e6854
--- /dev/null
@@ -0,0 +1,34 @@
+MANDEFS  = plugin-developer-guide.x
+MANUALS  = $(MANDEFS:.x=)
+TARGETS  =
+DOCFILES =
+
+vpath %.xml plugin-developer-guide/db
+
+include $(MRP_MAKE_DOCRULES)
+
+
+if BUILD_DOCUMENTATION
+DOCDIRS = plugin-developer-guide
+endif
+
+if BUILD_PDF_DOCUMENTS
+TARGETS += pdf_targets
+DOCFILES += $(MANDEFS:.x=.pdf)
+endif
+
+
+SUBDIRS = $(DOCDIRS)
+
+doc_DATA = CODING-STYLE # $(DOCFILES)
+
+
+all-am: $(TARGETS)
+
+pdf_targets: plugin-developer-guide.pdf
+
+plugin-developer-guide.pdf: plugin-developer-guide.xml
+
+clean-local:
+       rm -f *.pdf scripts/*~
+
diff --git a/doc/Makefile.rules b/doc/Makefile.rules
new file mode 100644 (file)
index 0000000..c53f415
--- /dev/null
@@ -0,0 +1,43 @@
+.SUFFIXES: .svg .pdf .png .lyx .xml
+
+vpath %.svg  $(MRP_FIGDIR)
+
+.svg:
+       echo "  CP    $@" 1>&2
+
+.svg.pdf:
+       echo "  INKSC $@" 1>&2
+       $(MRP_INKSCAPE) --export-area-drawing --export-pdf=$@ $< \
+           > /dev/null 2>&1
+
+.svg.png:
+       echo "  INKSC $@" 1>&2
+       $(MRP_INKSCAPE) --export-area-drawing --export-png=$@ $< \
+           > /dev/null 2>&1
+
+.lyx.xml:
+       $(MRP_LYX) --export docbook-xml $< 2> /tmp/dblyx.log
+       lyx_file=$< ; lyxml_file=$${lyx_file/.lyx/.xml} ; \
+        if [ -f "$$lyxml_file" ] ; then \
+          $(MRP_DBLYXFIX) $$lyxml_file $@ && rm -f $$lyxml_file ; \
+        else \
+           cat /tmp/dblyx.log ; \
+        fi ; \
+        rm -f /tmp/dblyx.log
+
+.xml.pdf:
+       echo "  DBPDF $@" 1>&2
+       rm -f $@
+       $(MRP_DBLATEX) --pdf -P figure.title.top=0 -P doc.section.depth=2 \
+                       -o $@ $< 2> /tmp/dblatex.log 1>&2
+       [ -f "$@" ] || cat /tmp/dblatex.log 1>&2
+       rm -f /tmp/dblatex.log
+
+$(DEPDIR)/Doxyfile.P: Doxyfile
+       $(MRP_DOXYDEPS) doxml_files $< $(DEPDIR)
+
+doxml_files: $(DEPDIR)/Doxyfile.P
+       echo "  DOXYG $@" 1>&2
+       $(MRP_DOXYGEN) Doxyfile
+       $(MRP_TOUCH) doxml_files
+
diff --git a/doc/common/figures/db-layers.svg b/doc/common/figures/db-layers.svg
new file mode 100644 (file)
index 0000000..b3f48b4
--- /dev/null
@@ -0,0 +1,608 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="297mm"
+   height="210mm"
+   id="svg2985"
+   version="1.1"
+   inkscape:version="0.48.1 r9760"
+   sodipodi:docname="db-layers.svg">
+  <defs
+     id="defs2987">
+    <linearGradient
+       id="linearGradient3783"
+       osb:paint="solid">
+      <stop
+         style="stop-color:#000000;stop-opacity:1;"
+         offset="0"
+         id="stop3785" />
+    </linearGradient>
+    <filter
+       id="filter3857"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur3859"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3861"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset3863"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3865">
+        <feMergeNode
+           id="feMergeNode3867"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3869"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter3871"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur3873"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3875"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset3877"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3879">
+        <feMergeNode
+           id="feMergeNode3881"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3883"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter3885"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur3887"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3889"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset3891"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3893">
+        <feMergeNode
+           id="feMergeNode3895"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3897"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter3899"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur3901"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3903"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset3905"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3907">
+        <feMergeNode
+           id="feMergeNode3909"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3911"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter3913"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur3915"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3917"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset3919"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3921">
+        <feMergeNode
+           id="feMergeNode3923"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3925"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter3985"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur3987"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix3989"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset3991"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge3993">
+        <feMergeNode
+           id="feMergeNode3995"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode3997"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter4081"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur4083"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix4085"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset4087"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge4089">
+        <feMergeNode
+           id="feMergeNode4091"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode4093"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       color-interpolation-filters="sRGB"
+       id="filter4081-7"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-0.25"
+       y="-0.25">
+      <feGaussianBlur
+         id="feGaussianBlur4083-6"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix4085-9"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset4087-4"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge4089-0">
+        <feMergeNode
+           id="feMergeNode4091-9"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode4093-5"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter4253"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur4255"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix4257"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset4259"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge4261">
+        <feMergeNode
+           id="feMergeNode4263"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode4265"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+    <filter
+       id="filter4267"
+       inkscape:label="Drop shadow"
+       width="1.5"
+       height="1.5"
+       x="-.25"
+       y="-.25">
+      <feGaussianBlur
+         id="feGaussianBlur4269"
+         in="SourceAlpha"
+         stdDeviation="5.7"
+         result="blur" />
+      <feColorMatrix
+         id="feColorMatrix4271"
+         result="bluralpha"
+         type="matrix"
+         values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.493 0 " />
+      <feOffset
+         id="feOffset4273"
+         in="bluralpha"
+         dx="11.6"
+         dy="10.5"
+         result="offsetBlur" />
+      <feMerge
+         id="feMerge4275">
+        <feMergeNode
+           id="feMergeNode4277"
+           in="offsetBlur" />
+        <feMergeNode
+           id="feMergeNode4279"
+           in="SourceGraphic" />
+      </feMerge>
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     inkscape:document-units="mm"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="676.99722"
+     inkscape:cy="373.08197"
+     inkscape:current-layer="layer1"
+     id="namedview2989"
+     showgrid="false"
+     inkscape:snap-grids="true"
+     inkscape:window-width="1511"
+     inkscape:window-height="1051"
+     inkscape:window-x="249"
+     inkscape:window-y="49"
+     inkscape:window-maximized="0"
+     showguides="true"
+     inkscape:guide-bbox="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3999" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata2991">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <path
+       style="fill:#a18689;fill-opacity:1;stroke:none;filter:url(#filter3985)"
+       d="m 193,698.09448 1,-371 310,0 0,-128 344,0 -2,500 z"
+       id="path3983"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccccc"
+       inkscape:label="#mdb-path" />
+    <rect
+       style="fill:#a02c2c;fill-opacity:1;stroke:none;filter:url(#filter3913)"
+       id="rect3001"
+       width="602.41913"
+       height="116"
+       x="220.72061"
+       y="356.09448"
+       ry="0.5"
+       inkscape:label="#mql-rect" />
+    <rect
+       style="fill:#a05a2c;fill-opacity:1;stroke:none;filter:url(#filter3899)"
+       id="rect3797"
+       width="286.07703"
+       height="110"
+       x="220"
+       y="499.09448"
+       ry="0.5" />
+    <rect
+       style="fill:#a05a2c;fill-opacity:1;stroke:none;filter:url(#filter3885)"
+       id="rect3797-5"
+       width="290.4006"
+       height="110"
+       x="533.09937"
+       y="500.09448"
+       ry="0.5" />
+    <rect
+       style="fill:#c8ab37;fill-opacity:1;stroke:none;filter:url(#filter3871)"
+       id="rect3797-5-8"
+       width="290.4006"
+       height="110"
+       x="531.6582"
+       y="222.09448"
+       ry="0.5" />
+    <path
+       style="fill:#d0b69d;fill-opacity:1;stroke:none;filter:url(#filter3857)"
+       d="m 172.78059,290.09448 282.57583,-1 -0.7206,-129 390.50156,0 -1.3394,-134 -671.01739,0 z"
+       id="path3835"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccccccc"
+       transform="matrix(0.97088079,0,0,1,26.971249,10)" />
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter4081);font-family:Sans"
+       x="429"
+       y="665.09448"
+       id="text4077"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4079"
+         x="429"
+         y="665.09448"
+         style="font-size:32px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans Bold">Murphy-DB</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;filter:url(#filter4081-7);font-family:Sans"
+       x="399.42969"
+       y="117.92261"
+       id="text4077-2"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4079-4"
+         x="399.42969"
+         y="117.92261"
+         style="font-size:32px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans Bold">Murphy Plugin</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="676.5293"
+       y="266.09448"
+       id="text4137"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4139"
+         x="676.5293"
+         y="266.09448"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'">MQL</tspan><tspan
+         sodipodi:role="line"
+         x="676.5293"
+         y="296.09448"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4141">Murphy Query Language</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="529.13477"
+       y="403.63159"
+       id="text4137-2"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4139-2"
+         x="529.13477"
+         y="403.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'">MQI</tspan><tspan
+         sodipodi:role="line"
+         x="529.13477"
+         y="433.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4141-2">Murphy Query Interface</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="365.13477"
+       y="500.63159"
+       id="text4137-2-8"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4139-2-7"
+         x="365.13477"
+         y="500.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'" /><tspan
+         sodipodi:role="line"
+         x="365.13477"
+         y="530.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4195">Temporary Data Backend</tspan><tspan
+         sodipodi:role="line"
+         x="365.13477"
+         y="560.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4201">MDE</tspan><tspan
+         sodipodi:role="line"
+         x="365.13477"
+         y="590.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4141-2-4">Memory Database Engine</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="678.13477"
+       y="502.83667"
+       id="text4137-2-8-7"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         id="tspan4139-2-7-1"
+         x="678.13477"
+         y="502.83667"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'" /><tspan
+         sodipodi:role="line"
+         x="678.13477"
+         y="532.83667"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4195-5">Persistent Data Backend</tspan><tspan
+         sodipodi:role="line"
+         x="678.13477"
+         y="562.83667"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4201-0" /><tspan
+         sodipodi:role="line"
+         x="678.13477"
+         y="592.83667"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4141-2-4-8">SQLite</tspan></text>
+    <path
+       style="fill:#c8ab37;fill-opacity:1;stroke:none;filter:url(#filter4267)"
+       d="m 867,184.09448 30,-49 -1,20 141,-1 0,61 -141,0 0,19 z"
+       id="path4233"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cccccccc" />
+    <path
+       style="fill:#a02c2c;fill-opacity:1;stroke:#7b5555;stroke-opacity:1;filter:url(#filter4253)"
+       d="m 177,314.09448 -30,-49 1,20 -141,-1 0,61 141,0 0,19 z"
+       id="path4233-0"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="cccccccc" />
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="965.13477"
+       y="177.63159"
+       id="text4137-8"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         x="965.13477"
+         y="177.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4141-24">High level</tspan><tspan
+         sodipodi:role="line"
+         x="965.13477"
+         y="207.63159"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4307">API</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-size:40px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans"
+       x="80.035217"
+       y="307.42651"
+       id="text4137-8-0"
+       sodipodi:linespacing="125%"><tspan
+         sodipodi:role="line"
+         x="80.035217"
+         y="307.42651"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4141-24-3">Low level</tspan><tspan
+         sodipodi:role="line"
+         x="80.035217"
+         y="337.42651"
+         style="font-size:24px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;font-family:Times New Roman;-inkscape-font-specification:'Times New Roman,'"
+         id="tspan4307-8">API</tspan></text>
+  </g>
+</svg>
diff --git a/doc/plugin-developer-guide/Makefile.am b/doc/plugin-developer-guide/Makefile.am
new file mode 100644 (file)
index 0000000..97a9a4c
--- /dev/null
@@ -0,0 +1,6 @@
+SUBDIRS =  doxml db
+
+clean-local:
+       rm -f *~
+
+
diff --git a/doc/plugin-developer-guide/db/Makefile.am b/doc/plugin-developer-guide/db/Makefile.am
new file mode 100644 (file)
index 0000000..e621a7c
--- /dev/null
@@ -0,0 +1,62 @@
+TOP_SRCDIR = $(abs_top_srcdir)/src
+
+vpath %.lyx $(MRP_DOCDIR)/plugin-developer-guide/lyx
+vpath %.xml $(MRP_DOCDIR)/plugin-developer-guide/doxml
+
+include $(MRP_MAKE_DOCRULES)
+
+
+FIGURES_SVG = db-layers.svg
+FIGURES_PNG = $(FIGURES_SVG:.svg=.png)
+FIGURES_PDF = $(FIGURES_SVG:.svg=.pdf)
+
+FIGURES = $(FIGURES_SVG) $(FIGURES_PNG) $(FIGURES_PDF)
+
+
+high_level_api_definition_SRC = mql.h
+high_level_api_definition_DOXML = $(high_level_api_definition_SRC:.h=_8h.xml)
+
+
+TARGETS = $(FIGURES) plugin-developer-guide.xml
+
+
+xmldir = $(datadir)/doc/@PACKAGE@
+nodist_xml_DATA = $(TARGETS)
+
+
+all-am: $(TARGETS) copy_svg
+
+
+copy_svg:
+       for f in $(FIGURES_SVG) ; do \
+           echo "  CP    $$f" ; \
+           cp $(MRP_FIGDIR)/$$f . ; \
+       done 1>&2
+
+mql-grammar.xml: $(TOP_SRCDIR)/murphy-db/mql/mql-scanner.l \
+                 $(TOP_SRCDIR)/murphy-db/mql/mql-parser.y
+       $(MRP_ABNF) $+ > $@
+
+high-level-api-definition.xml: $(high_level_api_definition_DOXML)
+       $(MRP_DOXML2DB) ../doxml $^ $@
+
+murphy-db-introduction.xml: murphy-db-introduction.lyx
+
+murphy-db-high-level-api.xml: murphy-db-high-level-api.lyx \
+                              high-level-api-definition.xml
+
+murphy-db-query-language.xml: murphy-db-query-language.lyx \
+                              mql-grammar.xml
+
+
+plugin-developer-guide.xml: plugin-developer-guide.lyx \
+                            murphy-db-introduction.xml \
+                            murphy-db-high-level-api.xml \
+                            murphy-db-query-language.xml
+
+clean-local:
+       rm -f *~ $(TARGETS) \
+                murphy-db-introduction.xml \
+                murphy-db-high-level-api.xml high-level-api-definition.xml \
+                murphy-db-query-language.xml mql-grammar.xml \
+               ../lyx/*.xml ../lyx/*~
diff --git a/doc/plugin-developer-guide/doxml/Doxyfile b/doc/plugin-developer-guide/doxml/Doxyfile
new file mode 100644 (file)
index 0000000..74631c9
--- /dev/null
@@ -0,0 +1,1720 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           =
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER         =
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO           =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       =
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH        =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING      =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT   = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING            = NO
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT   = YES
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = NO
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = YES
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = NO
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES        = NO
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE            =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = YES
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = NO
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE           =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT                  = ../../../src/murphy-db/include/murphy-db
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE              = NO
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS       = YES
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH           =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS       =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+IMAGE_PATH             =
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER           =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = YES
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML          = NO
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is adviced to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER            =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER            =
+
+# If the HTML_TIMESTAMP tag is set to YES then the generated HTML documentation will contain the timesstamp.
+
+HTML_TIMESTAMP         = NO
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES       =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET        = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE               =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING     =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE               =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME   =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS  =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS  =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION           =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+#  will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES       = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH        = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER           =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE      = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML           = YES
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = .
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA             =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD                =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH           =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS  =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED             =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED      =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               =
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       =
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS         = NO
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS        = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH           =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               =
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS           =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS           =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP            = YES
diff --git a/doc/plugin-developer-guide/doxml/Makefile.am b/doc/plugin-developer-guide/doxml/Makefile.am
new file mode 100644 (file)
index 0000000..cce58a5
--- /dev/null
@@ -0,0 +1,12 @@
+SOURCES= mql.h
+
+include $(MRP_MAKE_DOCRULES)
+
+
+all-am: doxml_files
+
+clean-local:
+       rm -f *~ *.doxml *.xml *.xsd *.xslt .deps/Doxyfile.P doxml_files
+
+
+-include .deps/Doxyfile.P
diff --git a/doc/plugin-developer-guide/lyx/murphy-db-high-level-api.lyx b/doc/plugin-developer-guide/lyx/murphy-db-high-level-api.lyx
new file mode 100644 (file)
index 0000000..6824497
--- /dev/null
@@ -0,0 +1,94 @@
+#LyX 2.0 created this file. For more info see http://www.lyx.org/
+\lyxformat 413
+\begin_document
+\begin_header
+\textclass docbook-chapter
+\use_default_options true
+\maintain_unincluded_children false
+\language english
+\language_package default
+\inputencoding auto
+\fontencoding global
+\font_roman default
+\font_sans default
+\font_typewriter default
+\font_default_family default
+\use_non_tex_fonts false
+\font_sc false
+\font_osf false
+\font_sf_scale 100
+\font_tt_scale 100
+
+\graphics default
+\default_output_format default
+\output_sync 0
+\bibtex_command default
+\index_command default
+\paperfontsize default
+\spacing single
+\use_hyperref true
+\pdf_bookmarks true
+\pdf_bookmarksnumbered false
+\pdf_bookmarksopen false
+\pdf_bookmarksopenlevel 1
+\pdf_breaklinks false
+\pdf_pdfborder false
+\pdf_colorlinks true
+\pdf_backref section
+\pdf_pdfusetitle true
+\papersize default
+\use_geometry false
+\use_amsmath 1
+\use_esint 1
+\use_mhchem 1
+\use_mathdots 1
+\cite_engine basic
+\use_bibtopic false
+\use_indices false
+\paperorientation portrait
+\suppress_date false
+\use_refstyle 1
+\index Index
+\shortcut idx
+\color #008000
+\end_index
+\secnumdepth 2
+\tocdepth 3
+\paragraph_separation skip
+\defskip smallskip
+\quotes_language english
+\papercolumns 1
+\papersides 1
+\paperpagestyle default
+\bullet 1 0 9 -1
+\bullet 2 0 15 -1
+\bullet 3 0 7 -1
+\tracking_changes false
+\output_changes false
+\html_math_output 0
+\html_css_as_file 0
+\html_be_strict false
+\end_header
+
+\begin_body
+
+\begin_layout Title
+High level API
+\end_layout
+
+\begin_layout Standard
+blah blah
+\end_layout
+
+\begin_layout Standard
+\begin_inset CommandInset include
+LatexCommand input
+filename "high-level-api-definition.xml"
+
+\end_inset
+
+
+\end_layout
+
+\end_body
+\end_document
diff --git a/doc/plugin-developer-guide/lyx/murphy-db-introduction.lyx b/doc/plugin-developer-guide/lyx/murphy-db-introduction.lyx
new file mode 100644 (file)
index 0000000..6a5d8d1
--- /dev/null
@@ -0,0 +1,120 @@
+#LyX 2.0 created this file. For more info see http://www.lyx.org/
+\lyxformat 413
+\begin_document
+\begin_header
+\textclass docbook-chapter
+\use_default_options true
+\maintain_unincluded_children false
+\language english
+\language_package default
+\inputencoding auto
+\fontencoding global
+\font_roman default
+\font_sans default
+\font_typewriter default
+\font_default_family default
+\use_non_tex_fonts false
+\font_sc false
+\font_osf false
+\font_sf_scale 100
+\font_tt_scale 100
+
+\graphics default
+\default_output_format default
+\output_sync 0
+\bibtex_command default
+\index_command default
+\paperfontsize default
+\spacing single
+\use_hyperref true
+\pdf_bookmarks true
+\pdf_bookmarksnumbered false
+\pdf_bookmarksopen false
+\pdf_bookmarksopenlevel 1
+\pdf_breaklinks false
+\pdf_pdfborder false
+\pdf_colorlinks true
+\pdf_backref section
+\pdf_pdfusetitle true
+\papersize default
+\use_geometry false
+\use_amsmath 1
+\use_esint 1
+\use_mhchem 1
+\use_mathdots 1
+\cite_engine basic
+\use_bibtopic false
+\use_indices false
+\paperorientation portrait
+\suppress_date false
+\use_refstyle 1
+\index Index
+\shortcut idx
+\color #008000
+\end_index
+\secnumdepth 2
+\tocdepth 3
+\paragraph_separation skip
+\defskip smallskip
+\quotes_language english
+\papercolumns 1
+\papersides 1
+\paperpagestyle default
+\bullet 1 0 9 -1
+\bullet 2 0 15 -1
+\bullet 3 0 7 -1
+\tracking_changes false
+\output_changes false
+\html_math_output 0
+\html_css_as_file 0
+\html_be_strict false
+\end_header
+
+\begin_body
+
+\begin_layout Title
+Introduction
+\end_layout
+
+\begin_layout Standard
+blah blah
+\end_layout
+
+\begin_layout Standard
+\begin_inset Float figure
+wide false
+sideways false
+status open
+
+\begin_layout Plain Layout
+\begin_inset Caption
+
+\begin_layout Plain Layout
+Murphy DB components
+\end_layout
+
+\end_inset
+
+
+\end_layout
+
+\begin_layout Plain Layout
+\begin_inset Graphics
+       filename ../../common/figures/db-layers.svg
+
+\end_inset
+
+
+\end_layout
+
+\begin_layout Plain Layout
+
+\end_layout
+
+\end_inset
+
+
+\end_layout
+
+\end_body
+\end_document
diff --git a/doc/plugin-developer-guide/lyx/murphy-db-query-language.lyx b/doc/plugin-developer-guide/lyx/murphy-db-query-language.lyx
new file mode 100644 (file)
index 0000000..9e588cd
--- /dev/null
@@ -0,0 +1,102 @@
+#LyX 2.0 created this file. For more info see http://www.lyx.org/
+\lyxformat 413
+\begin_document
+\begin_header
+\textclass docbook-chapter
+\use_default_options true
+\maintain_unincluded_children false
+\language english
+\language_package default
+\inputencoding auto
+\fontencoding global
+\font_roman default
+\font_sans default
+\font_typewriter default
+\font_default_family default
+\use_non_tex_fonts false
+\font_sc false
+\font_osf false
+\font_sf_scale 100
+\font_tt_scale 100
+
+\graphics default
+\default_output_format default
+\output_sync 0
+\bibtex_command default
+\index_command default
+\paperfontsize default
+\spacing single
+\use_hyperref true
+\pdf_bookmarks true
+\pdf_bookmarksnumbered false
+\pdf_bookmarksopen false
+\pdf_bookmarksopenlevel 1
+\pdf_breaklinks false
+\pdf_pdfborder false
+\pdf_colorlinks true
+\pdf_backref section
+\pdf_pdfusetitle true
+\papersize default
+\use_geometry false
+\use_amsmath 1
+\use_esint 1
+\use_mhchem 1
+\use_mathdots 1
+\cite_engine basic
+\use_bibtopic false
+\use_indices false
+\paperorientation portrait
+\suppress_date false
+\use_refstyle 1
+\index Index
+\shortcut idx
+\color #008000
+\end_index
+\secnumdepth 2
+\tocdepth 3
+\paragraph_separation skip
+\defskip smallskip
+\quotes_language english
+\papercolumns 1
+\papersides 1
+\paperpagestyle default
+\bullet 1 0 9 -1
+\bullet 2 0 15 -1
+\bullet 3 0 7 -1
+\tracking_changes false
+\output_changes false
+\html_math_output 0
+\html_css_as_file 0
+\html_be_strict false
+\end_header
+
+\begin_body
+
+\begin_layout Title
+Murphy Query Language
+\end_layout
+
+\begin_layout Section
+Overview
+\end_layout
+
+\begin_layout Standard
+blah blah
+\end_layout
+
+\begin_layout Section
+Grammar
+\end_layout
+
+\begin_layout Standard
+\begin_inset CommandInset include
+LatexCommand input
+filename "mql-grammar.xml"
+
+\end_inset
+
+
+\end_layout
+
+\end_body
+\end_document
diff --git a/doc/plugin-developer-guide/lyx/plugin-developer-guide.lyx b/doc/plugin-developer-guide/lyx/plugin-developer-guide.lyx
new file mode 100644 (file)
index 0000000..9d7af9e
--- /dev/null
@@ -0,0 +1,146 @@
+#LyX 2.0 created this file. For more info see http://www.lyx.org/
+\lyxformat 413
+\begin_document
+\begin_header
+\textclass docbook-book
+\use_default_options true
+\maintain_unincluded_children false
+\language english
+\language_package default
+\inputencoding auto
+\fontencoding global
+\font_roman default
+\font_sans default
+\font_typewriter default
+\font_default_family default
+\use_non_tex_fonts false
+\font_sc false
+\font_osf false
+\font_sf_scale 100
+\font_tt_scale 100
+
+\graphics default
+\default_output_format default
+\output_sync 0
+\bibtex_command default
+\index_command default
+\paperfontsize default
+\spacing single
+\use_hyperref true
+\pdf_bookmarks true
+\pdf_bookmarksnumbered false
+\pdf_bookmarksopen false
+\pdf_bookmarksopenlevel 1
+\pdf_breaklinks false
+\pdf_pdfborder false
+\pdf_colorlinks true
+\pdf_backref section
+\pdf_pdfusetitle true
+\papersize default
+\use_geometry false
+\use_amsmath 1
+\use_esint 1
+\use_mhchem 1
+\use_mathdots 1
+\cite_engine basic
+\use_bibtopic false
+\use_indices false
+\paperorientation portrait
+\suppress_date false
+\use_refstyle 1
+\index Index
+\shortcut idx
+\color #008000
+\end_index
+\secnumdepth 2
+\tocdepth 3
+\paragraph_separation skip
+\defskip smallskip
+\quotes_language english
+\papercolumns 1
+\papersides 1
+\paperpagestyle default
+\bullet 1 0 9 -1
+\bullet 2 0 15 -1
+\bullet 3 0 7 -1
+\tracking_changes false
+\output_changes false
+\html_math_output 0
+\html_css_as_file 0
+\html_be_strict false
+\end_header
+
+\begin_body
+
+\begin_layout Title
+Plugin Developer Guide
+\end_layout
+
+\begin_layout Part
+Introduction
+\end_layout
+
+\begin_layout Chapter
+Murphy in Brief
+\end_layout
+
+\begin_layout Standard
+blah blah bla ....
+\end_layout
+
+\begin_layout Description
+This will be the first among description thing
+\end_layout
+
+\begin_layout Description
+here we have the second
+\end_layout
+
+\begin_layout Description
+and the third
+\end_layout
+
+\begin_layout Part
+Basic Infrastructure
+\end_layout
+
+\begin_layout Part
+Murphy Database
+\end_layout
+
+\begin_layout Standard
+\begin_inset CommandInset include
+LatexCommand input
+filename "murphy-db-introduction.xml"
+
+\end_inset
+
+
+\end_layout
+
+\begin_layout Standard
+\begin_inset CommandInset include
+LatexCommand input
+filename "murphy-db-high-level-api.xml"
+
+\end_inset
+
+
+\end_layout
+
+\begin_layout Standard
+\begin_inset CommandInset include
+LatexCommand input
+filename "murphy-db-query-language.xml"
+
+\end_inset
+
+
+\end_layout
+
+\begin_layout Part
+Plugin writing conventions
+\end_layout
+
+\end_body
+\end_document
diff --git a/doc/plugins/resource-dbus/dbus-api-resource.txt b/doc/plugins/resource-dbus/dbus-api-resource.txt
new file mode 100644 (file)
index 0000000..993b58e
--- /dev/null
@@ -0,0 +1,114 @@
+D-Bus API for Murphy resource handling
+======================================
+
+
+Service org.murphy
+
+Interface org.murphy.manager
+Path /org/murphy/resource
+
+methods:
+
+    dict getProperties()
+    ObjectPath createResourceSet()
+
+signals:
+
+    propertyChanged(String, Variant)
+
+properties:
+
+    RO [ObjectPath] resourceSets: default []
+    RO [String] availableClasses
+
+
+
+Interface org.murphy.resourceset
+Path varies (from createResourceSet)
+
+methods:
+
+    dict getProperties()
+    void setProperty(String, Variant)
+    ObjectPath addResource(String)
+    void request()
+    void release()
+    void delete()
+
+signals:
+
+    propertyChanged(String, Variant)
+    updatedResources([ObjectPath]) # not yet implemented
+
+properties:
+
+    RW String class: default "default"
+    RO String status: default "pending"
+    RO [String] availableResources
+    RO [ObjectPath] resources: default []
+
+
+
+Interface org.murphy.resource
+Path varies (from addResource)
+
+methods:
+
+    dict getProperties()
+    void setProperty(String, Variant)
+    void delete()
+
+signals:
+
+    propertyChanged(String, Variant)
+
+properties:
+
+    RO String status: default "pending"
+    RO String name
+    RW Boolean mandatory
+    RW Boolean shared
+
+    RO dict attributes (string: variant) {
+        ... properties specified by resource type ...
+    }
+
+    RW dict conf (string: variant) {
+        ... properties specified by resource type ...
+    }
+
+
+
+
+Explanation of values
+=====================
+
+
+The "status" variable on resource set objects can have four different values:
+
+    * "pending", meaning that the request() method call hasn't been called or
+          processed yet
+    * "acquired", meaning that the client is allowed to use the requested
+          resource
+    * "lost", meaning that the client has lost the resource set and is not
+          allowed to use it
+    * "available", meaning that the client is not allowed to use the resource
+          set, but based on the current status the client would get the resource
+          set if it did a request() method call
+
+The difference between "lost" and "available" is subtle. One way to
+think of the difference is this: when a media player resource set having
+an audio resource goes to "lost" state, the media player can dim or gray
+out the play button, since the media player is unable to get access to
+the resources under any circumstances. This can be the case for instance
+during a phone call, depending on murphy configuration. However, if the
+media player resource set is in "available" state, the audio playback
+can continue when the user or the application wishes so. This can be the
+case when another media player application is playing audio. The user
+can control which media player will play audio by pushing play button on
+the UI of the application that the user wants to play.
+
+The "status" variable on resource objects can have three values:
+"pending", "acquired" and "lost". The "available" value is not needed,
+since the resources cannot be indiviually requested, only resource sets.
+
diff --git a/doc/plugins/resource-dbus/resource-client.py b/doc/plugins/resource-dbus/resource-client.py
new file mode 100644 (file)
index 0000000..2e6dbbf
--- /dev/null
@@ -0,0 +1,577 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012, Intel Corporation
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#  * Redistributions of source code must retain the above copyright notice,
+#    this list of conditions and the following disclaimer.
+#  * Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#  * Neither the name of Intel Corporation nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+# This script is an example on how to use the Murphy D-Bus resource API.
+
+
+from __future__ import print_function
+
+import dbus
+import gobject
+import glib
+import sys
+import fcntl
+import os
+from dbus.mainloop.glib import DBusGMainLoop
+
+from itertools import combinations
+from random import choice
+
+USAGE = """
+Available commands:
+
+help
+
+createSet
+
+deleteSet <set>
+changeSetClass <set> <class>
+acquireSet <set>
+releaseSet <set>
+showSet <set>
+
+createResource <set> <type>
+
+deleteResource <set> <resource>
+changeResource <set> <resource> <attribute> <value>
+showResource <set> <resource>
+
+quit"""
+
+manager = None
+bus = None
+mainloop = None
+interactive = None
+n_iterations = None
+limit = None
+
+# mapping from numbers to object paths
+rsets = {}
+resources = {}
+
+
+# pretty printing of D-Bus properties
+
+def pretty_str_dbus_value(val, level=0, suppress=False):
+    if type(val) == dbus.Array:
+        return pretty_str_dbus_array(val, level)
+    elif type(val) == dbus.Dictionary:
+        return pretty_str_dbus_dict(val, level)
+    else:
+        s = ""
+        if not suppress:
+            s += level * "\t"
+        if type(val) == dbus.Boolean:
+            if val:
+                s += "True"
+            else:
+                s += "False"
+        else:
+            s += str(val)
+        return s
+
+
+def pretty_str_dbus_array(arr, level=0):
+    prefix = level * "\t"
+    s = "[\n"
+    for v in arr:
+        s += pretty_str_dbus_value(v, level+1)
+        s += "\n"
+    s += prefix + "]"
+    return s
+
+
+def pretty_str_dbus_dict(d, level=0):
+    prefix = level * "\t"
+    s = "{\n"
+    for k, v in d.items():
+        s += prefix + "\t"
+        s += str(k) + ": "
+        s += pretty_str_dbus_value(v, level+1, True)
+        s += "\n"
+    s += prefix + "}"
+    return s
+
+
+# methods for getting a D-Bus object from path
+
+def get_rset(path):
+    set_obj = bus.get_object('org.Murphy', path)
+    return dbus.Interface(set_obj, dbus_interface='org.murphy.resourceset')
+
+
+def get_res(path):
+    resource_obj = bus.get_object('org.Murphy', path)
+    return dbus.Interface(resource_obj, dbus_interface='org.murphy.resource')
+
+
+# prompt handling
+
+prompt_needed = True
+
+def add_prompt():
+    global prompt_needed
+    global interactive
+
+    if prompt_needed and interactive:
+        print("")
+        print("> ", end="")
+        sys.stdout.flush()
+        prompt_needed = False
+
+
+def add_prompt_later():
+    global prompt_needed
+    global interactive
+
+    if not interactive:
+        return
+
+    prompt_needed = True
+
+    # add in idle loop so that we first process all queued signals
+    glib.idle_add(add_prompt)
+
+
+# signal handlers
+
+def resource_handler(prop, value, path):
+
+    res = int(path.split("/")[-1]) # the resource number
+    set = int(path.split("/")[-2]) # the set number
+
+    print("(%d/%d) property %s -> %s" % (set, res, str(prop), pretty_str_dbus_value(value)))
+    add_prompt_later()
+
+
+def rset_handler(prop, value, path):
+
+    set = int(path.split("/")[-1]) # the set number
+
+    print("(%d) property %s -> %s" % (set, str(prop), pretty_str_dbus_value(value)))
+    add_prompt_later()
+
+
+def mgr_handler(prop, value, path):
+
+    print("(manager) property %s -> %s" % (str(prop), pretty_str_dbus_value(value)))
+    add_prompt_later()
+
+
+# helper functions
+
+def get_set_path(setId):
+    try:
+        return rsets[setId]
+    except:
+        return None
+
+
+def get_res_path(setId, resId):
+    try:
+        return resources[(setId, resId)]
+    except:
+        return None
+
+
+def get_resource_set(set):
+    try:
+        id = int(set)
+    except ValueError:
+        print("ERROR: wrong resource set id type")
+        return None
+
+    set_path = get_set_path(int(set))
+
+    if set_path:
+        return get_rset(set_path)
+    else:
+        print("ERROR: resource set doesn't exist")
+    return None
+
+
+def get_resource(set, res):
+    try:
+        id_s = int(set)
+        id_r = int(res)
+    except ValueERROR:
+        print("ERROR: wrong resource or resource set id type")
+        return None
+
+    res_path = get_res_path(int(set), int(res))
+
+    if res_path:
+        return get_res(res_path)
+    else:
+        print("ERROR: resource doesn't exist")
+    return None
+
+
+# UI functions
+
+def help():
+    print(USAGE)
+
+
+def createSet():
+    try:
+        set_path = manager.createResourceSet()
+        id = int(set_path.split("/")[-1]) # the set number
+        rsets[id] = set_path
+        rset = get_rset(set_path)
+        if interactive:
+            rset.connect_to_signal("propertyChanged", rset_handler, path_keyword='path')
+        print("(%s) Created resource set" % str(id))
+        return id
+    except:
+        print("ERROR: failed to create a new resource set")
+
+
+def deleteSet(set):
+
+    rset = get_resource_set(set)
+
+    if rset:
+        try:
+            rset.delete()
+            del rsets[int(set)]
+            print("(%s) Deleted resource set" % set)
+        except:
+            print("ERROR (%s): failed to delete resource set" % set)
+
+
+def createResource(set, rType):
+    rset = get_resource_set(set)
+
+    if rset:
+        try:
+            res_path = rset.addResource(rType)
+            res = int(res_path.split("/")[-1]) # the resource id number
+            resources[(int(set), res)] = res_path
+
+            resource = get_res(res_path)
+            if interactive:
+                resource.connect_to_signal("propertyChanged", resource_handler, path_keyword='path')
+            print("(%s/%d) added resource '%s'" % (set, res, rType))
+            return res
+        except:
+            print("ERROR (%s): failed to add resource to resource set" % set)
+
+
+def changeSetClass(set, klass):
+
+    rset = get_resource_set(set)
+
+    if rset:
+        try:
+            rset.setProperty("class", dbus.String(klass, variant_level=1))
+            print("(%s) changed the application class to %s" % (set, klass))
+        except:
+            print("ERROR (%s): failed to change resource set class to '%s'" % (set, klass))
+
+
+def acquireSet(set):
+
+    rset = get_resource_set(set)
+
+    if rset:
+        try:
+            rset.request()
+            print("(%s) asked (asynchronously) to acquire" % set)
+        except:
+            print("ERROR (%s): failed to acquire resource set" % set)
+
+
+def releaseSet(set):
+
+    rset = get_resource_set(set)
+
+    if rset:
+        try:
+            rset.release()
+            print("(%s) asked (asynchronously) to release" % set)
+        except:
+            print("ERROR (%s): failed to release resource set" % set)
+
+
+def showSet(set):
+
+    rset = get_resource_set(set)
+
+    if rset:
+        try:
+            print("(%s)" % set)
+            values = rset.getProperties()
+            print(pretty_str_dbus_value(values))
+        except:
+            print("ERROR (%s): failed to query properties of resource set" % set)
+
+
+def deleteResource (set, resource):
+
+    res = get_resource(set, resource)
+
+    if res:
+        try:
+            res.delete()
+            del resources[(int(set), int(resource))]
+            print("(%s/%s) deleted resource" % (set, resource))
+        except:
+            print("ERROR (%s/%s): failed to delete resource" % (set, resource))
+
+
+def changeResource(set, resource, attribute, value):
+
+    res = get_resource(set, resource)
+
+    if res:
+        try:
+            if attribute == "shared" or attribute == "mandatory":
+                val = False
+                if value == "True" or value == "1":
+                    val = True
+
+                res.setProperty(attribute, dbus.Boolean(val, variant_level=1))
+                print("(%s/%s) set attribute '%s' to '%s'" % (set, resource, attribute, val))
+            else:
+                attrs = res.getProperties()["attributes"]
+
+                if attribute in attrs:
+
+                    if type(attrs[attribute]) is dbus.String:
+                        attrs[attribute] = dbus.String(value)
+                    elif type(attrs[attribute]) is dbus.Int32:
+                        attrs[attribute] = dbus.Int32(value)
+                    elif type(attrs[attribute]) is dbus.UInt32:
+                        attrs[attribute] = dbus.UInt32(value)
+                    elif type(attrs[attribute]) is dbus.Double:
+                        attrs[attribute] = dbus.Double(value)
+
+                    res.setProperty("attributes_conf", attrs)
+                    print("(%s/%s) set attribute '%s' to '%s'" %
+                                    (set, resource, attribute, str(attrs[attribute])))
+                else:
+                    print("ERROR (%s/%s): attribute '%s' not supported in resource" % (set, resource, attribute))
+        except dbus.DBusException as e:
+            print("ERROR (%s/%s): failed to set attribute '%s' in resource: %s" % (set, resource, attribute, e))
+        except:
+            print("ERROR (%s/%s): failed to convert value type to D-Bus" % (set, resource))
+
+
+
+def showResource(set, resource):
+
+    res = get_resource(set, resource)
+
+    if res:
+        try:
+            print("(%s/%s)" % (set, resource))
+            values = res.getProperties()
+            print(pretty_str_dbus_value(values))
+        except dbus.DBusException, e:
+            print("ERROR (%s/%s): failed to query properties of resource: %s" % (set, resource, e))
+
+
+def stdin_cb(fd, condition):
+    data = fd.read()
+    tokens = data.split()
+
+    set = None
+    resource = None
+
+    if len(tokens) == 0 or len(tokens) > 5:
+        return True
+
+    if tokens[0] == "createSet" and len(tokens) == 1:
+        createSet()
+    elif tokens[0] == "deleteSet" and len(tokens) == 2:
+        deleteSet(*tokens[1:])
+    elif tokens[0] == "createResource" and len(tokens) == 3:
+        createResource(*tokens[1:])
+    elif tokens[0] == "changeSetClass" and len(tokens) == 3:
+        changeSetClass(*tokens[1:])
+    elif tokens[0] == "acquireSet" and len(tokens) == 2:
+        acquireSet(*tokens[1:])
+    elif tokens[0] == "releaseSet" and len(tokens) == 2:
+        releaseSet(*tokens[1:])
+    elif tokens[0] == "showSet" and len(tokens) == 2:
+        showSet(*tokens[1:])
+    elif tokens[0] == "deleteResource" and len(tokens) == 3:
+        deleteResource(*tokens[1:])
+    elif tokens[0] == "changeResource" and len(tokens) == 5:
+        changeResource(*tokens[1:])
+    elif tokens[0] == "showResource" and len(tokens) == 3:
+        showResource(*tokens[1:])
+    elif tokens[0] == "quit":
+        mainloop.quit()
+        return False
+    else:
+        help()
+
+    add_prompt_later()
+
+    return True
+
+
+def fuzz_test():
+    """ randomly create, acquire, release and destroy resource sets """
+
+    global rsets
+    global n_iterations
+
+    resource_names = [ "audio_playback", "audio_recording" ]
+    operations = [ "create", "delete", "acquire", "release" ]
+    attributes={"pid":range(1, 1000),"role":["music","navigator","game"],"policy":["relaxed","strict"]}
+    coin = [True, False]
+
+    print("fuzz_test, left", n_iterations, "iterations")
+
+    if n_iterations > 0:
+        n_iterations = n_iterations - 1
+
+        if len(rsets) == 0:
+            # no sets, have to create
+            op = "create"
+        elif limit and len(rsets) >= limit:
+            # limit was reached
+            op = "delete"
+        else:
+            op = choice(operations)
+
+        if op == "create":
+            rset_id = createSet()
+
+            if (rset_id != None):
+                n_res = choice(range(1, len(resource_names)))
+                res_choice = choice(list(combinations(resource_names, n_res)))
+
+                print("created rset", str(rset_id))
+
+                for res in res_choice:
+                    res_id = createResource(str(rset_id), res)
+                    print("resource", res)
+
+                    # change some resources
+
+                    # this many attributes
+                    n_attrs = choice(range(1, len(attributes)))
+                    attr_choice = choice(list(combinations(attributes.keys(), n_attrs)))
+
+                    for attr in attr_choice:
+                        values = attributes[attr]
+                        value = choice(values)
+                        changeResource(str(rset_id), str(res_id), attr, value)
+
+                    changeResource(str(rset_id), str(res_id), "mandatory", str(choice(coin)));
+                    changeResource(str(rset_id), str(res_id), "shared", str(choice(coin)));
+
+
+        elif op == "delete":
+            if len(rsets) > 0:
+                id = choice(rsets.keys())
+                print("deleting rset", id)
+                deleteSet(str(id))
+
+        elif op == "acquire":
+            if len(rsets) > 0:
+                id = choice(rsets.keys())
+                print("acquiring rset", id)
+                acquireSet(str(id))
+
+        elif op == "release":
+            if len(rsets) > 0:
+                id = choice(rsets.keys())
+                print("releasing rset", id)
+                releaseSet(str(id))
+
+        return True
+
+    else:
+        return False # do not call again
+
+
+def main(args):
+    global manager
+    global bus
+    global mainloop
+    global interactive
+    global n_iterations
+
+    # D-Bus initialization
+
+    DBusGMainLoop(set_as_default=True)
+    mainloop = gobject.MainLoop()
+
+    bus = dbus.SystemBus()
+
+    if not bus:
+        print("ERROR: failed to get system bus")
+        exit(1)
+
+    # Create the manager for handling resource sets.
+
+    manager_obj = None
+
+    # TODO: get service and manager path from command line?
+    try:
+        manager_obj = bus.get_object('org.Murphy', '/org/murphy/resource')
+    except:
+        pass
+
+    if not manager_obj:
+        print("ERROR: failed get Murphy resource manager object")
+        exit(1)
+
+    manager = dbus.Interface(manager_obj, dbus_interface='org.murphy.manager')
+
+    if (len(args) > 0 and args[0] == "fuzz"):
+        interactive = False
+        n_iterations = 1000
+        if (len(args) == 2):
+            n_iterations = int(args[1])
+            if (len(args) == 3):
+                limit = int(args[2])
+        glib.idle_add(fuzz_test)
+
+    else:
+        # interactive mode
+        interactive = True
+        manager.connect_to_signal("propertyChanged", mgr_handler, path_keyword='path')
+        # make STDIN non-blocking
+        fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, os.O_NONBLOCK)
+        # listen for user input
+        glib.io_add_watch(sys.stdin, glib.IO_IN, stdin_cb)
+        add_prompt()
+
+    mainloop.run()
+
+    # TODO: cleanup
+
+main(sys.argv[1:])
\ No newline at end of file
diff --git a/doc/scripts/abnf.py b/doc/scripts/abnf.py
new file mode 100755 (executable)
index 0000000..8691ef1
--- /dev/null
@@ -0,0 +1,640 @@
+#!/usr/bin/env python
+# -*- coding: latin-1 -*-
+
+#
+# this script produces ABNF description of grammars implemented by flex/bison
+# ABNF stands for Augmented Backus-Naur Form as it is specified by the IETF
+# RFC 5234
+#
+#
+
+import os, sys, re
+
+def regexp_to_abnf(string):
+    return regexp_parse(string, 0)[0]
+
+def regexp_parse(string, index):
+    stack = []
+    precedence = 0
+    escape = False
+
+    while index < len(string):
+        c = string[index]
+
+        insert = False
+
+        if c == "\\":
+            escape = True
+        else:
+            if c.isalnum() or escape:
+                if c == "\"":
+                    new_string = "DQUOTE"
+                elif c == " ":
+                    new_string = "SP"
+                else:
+                    new_string = "\"%s\"" % c
+                new_precedence = 100
+                escape = False
+            elif c == ".":
+                new_precedence = 90
+                new_string = "VCHAR"
+            elif c == "{":
+                new_precedence = 100
+                if index+1 >= len(string) or string.find("}", index+1) < 0:
+                    new_string = "\"{\""
+                else:
+                    begin = index+1
+                    end = string.find("}", begin)
+                    new_string = string[begin:end]
+                    index = end
+            elif c == "(":
+                new_precedence = 80
+                result, index = regexp_parse(string, index+1)
+                if len(result) < 1:
+                    index += 1
+                    continue
+                new_string = "(" + result + ")"
+            elif c == ")":
+                break 
+            elif c == "[":
+                new_precedence = 70
+                new_string, index = regexp_character_class(string, index)
+            elif c == "*":
+                new_precedence = 43
+                new_string = "*"
+                insert = True
+            elif c == "+":
+                new_precedence = 42
+                new_string = "1*"
+                insert = True
+            elif c == "?":
+                new_precedence = 40
+                new_string = "0*1"
+                insert = True
+            elif c == "|":
+                new_precedence = 30
+                new_string = "/"
+            else:
+                new_precedence = 100
+                if c == "\"":
+                    new_string = "DQUOTE"
+                elif ord(c) < ord(' '):
+                    new_string = "\%x%x" % ord(c)
+                else:
+                    new_string = "\"" + c + "\""
+
+            if insert:
+                last = len(stack) - 1
+                if last >= 0:
+                    stack.insert(last, (stack[last][0], new_string))
+                    stack = regexp_merge(new_precedence, stack)
+            else:
+                stack = regexp_merge(new_precedence, stack)
+                stack.append((new_precedence, new_string))
+
+            precedence = new_precedence
+        index += 1
+
+    if len(stack) < 1:
+        result = ""
+    else:
+        result = regexp_merge(-1, stack)[0][1]
+
+
+    if result.startswith("(") and result.endswith(")"):
+        strip = True
+        balance = 0
+        for c in result[1:-1]:
+            if c == "(":
+                balance += 1
+            elif c == ")":
+                balance -= 1
+                if balance < 0:
+                    strip = False
+                    break
+        if strip and balance == 0:
+            result = result[1:-1]
+
+    return (result, index)
+
+def regexp_character_class(string, index):
+    cnt = 0
+    result = ""
+    backslash = False
+    sep = ""
+
+    if string[index+1] == "^":
+        index += 1
+        ranges = [(32, 126)]
+        escape = False
+        while index+1 < len(string):
+            index += 1
+            char = string[index]
+
+            if char == "]":
+                break
+
+            if char == "\\":
+                escape = True
+                continue
+            
+            c = ord(char)
+
+            if escape:
+                if char == "n":
+                    c = 10
+                if char == "t":
+                    c = 9
+                escape = False
+
+            for r in ranges:
+                if c >= r[0] and c <= r[1]:
+                    ranges.remove(r)
+                    if c == r[0]:
+                        if c != r[1]:
+                            ranges.append((r[0]+1, r[1]))
+                        break
+                    elif c == r[1]:
+                        if c != r[0]:
+                            ranges.append((r[0], r[1]-1))
+                        break
+                    else:
+                        ranges.append((r[0], c-1))
+                        ranges.append((c+1, r[1]))
+                        break
+
+        lower = False
+        upper = False
+        digit = False
+        for r in ranges:
+            if r[0] <= 65 and r[1] >= 90:
+                upper = r
+            if r[0] <= 97 and r[1] >= 122:
+                lower = r
+            if r[0] <= 48 and r[1] >= 57:
+                digit = r
+        if lower and upper:
+            ranges = regexp_range_extract(ranges, 97,122)
+            ranges = regexp_range_extract(ranges, 65,90)
+            result += sep + "ALPHA"
+            sep = " / "
+            cnt += 1
+        if digit:
+            ranges = regexp_range_extract(ranges, 48,57)
+            result += sep + "DIGIT"
+            sep = " / "
+            cnt += 1
+        ranges.sort(regexp_range_sort)
+        for r in ranges:
+            if r[0] == r[1]:
+                result += sep + "%x" + "%x" % r[0]
+            else:
+                result += sep + "%x" + "%x-%x" % r
+            sep = " / "
+            cnt += 1
+    else:
+        while index+1 < len(string):
+            index += 1
+            c = string[index]
+
+            if c == "]":
+                break
+
+            if c == "\\":
+                if not backslash:
+                    backslash = True
+                    continue
+            else:
+                if backslash:
+                    backslash = False
+                    c = regexp_escape(c)
+                elif c == " ":
+                    c = "SP"
+                elif c == "\"":
+                    c = "DQUOTE"
+                else:
+                    if string[index:index+3] == "0-9":
+                        index += 2
+                        c = "DIGIT"
+                    elif string[index:index+6] == "a-zA-Z" or \
+                            string[index:index+6] == "A-Za-z":
+                        index += 5
+                        c = "ALPHA"
+                    elif index < len(string)-2 and string[index+1] == "-" and \
+                            ((string[index].isalpha() and \
+                              string[index+2].isalpha()) or \
+                             (string[index].isdigit() and \
+                              string[index+2].isdigit())):
+                        index += 2
+                        start = ord(c)
+                        end = ord(string[index])+1
+                        sep2 = ""
+                        c = ""
+
+                        for i in range(start, end):
+                            c += sep2 +  "\"" + chr(i) + "\""
+                            sep2 = " / "
+                    else:
+                        c = "\"" + c + "\""
+
+            result += sep + c
+            sep = " / "
+            cnt += 1
+
+    if cnt > 1:
+        result = "(" + result + ")"
+
+    return (result, index)
+
+def regexp_escape(char):
+    if char == "n":
+        return "CRLF"
+    elif char == "t":
+        return "HTAB"
+    elif char == " ":
+        return "SP"
+    elif char == "\"":
+        return "DQUOTE"
+    elif char == "\\":
+        return "\"\\\""
+    elif char == "^":
+        return "\"^\""
+
+    return "\"" + char + "\""
+
+
+def regexp_merge(new_precedence, stack):
+    last = len(stack) - 1
+    precedence = 0
+
+    if last >= 0:
+        for merge in range(last,-1,-1):
+            precedence = stack[merge][0]
+
+            if new_precedence >= precedence:
+                break
+
+        append = False
+        string = ""
+        sep = ""
+
+        for i in range(merge,last+1):
+            element = stack[i][1]
+            string += sep + element
+            if element.find("*") == len(element)-1:
+                sep = ""
+            else:
+                sep = " "
+            append = True
+
+
+        for i in range(last,merge-1,-1):
+            stack.pop()
+
+        if append:
+            stack.append((new_precedence, string))
+
+    return stack
+
+def regexp_range_extract(ranges, l,h):
+    for r in ranges:
+        if r[0] <= l and r[1] >= h:
+            ranges.remove(r)
+            if l == r[0] and h < r[1]:
+                ranges.append((h+1, r[1]))
+            elif l > r[0] and h == r[1]:
+                ranges.append((r[0], l-1))
+            elif l > r[0] and h < r[1]:
+                ranges.append((r[0],l-1))
+                ranges.append((h+1,r[1])) 
+            break
+    return ranges
+
+def regexp_range_sort(a,b):
+    if a[0] < b[0]:
+        return -1
+    elif a[0] > b[0]:
+        return +1
+    return 0
+
+
+def component_list(input_list):
+    output_list  = ""
+    sep = ""
+    if len(input_list) > 0:
+        components = input_list.split(' ')
+        ncomponent = len(components)
+        if ncomponent > 0:
+            for component in components:
+                name = component.strip()
+                if len(name) < 1:
+                    continue
+                output_list += sep
+                sep = " "
+                if name.startswith("TKN_"):
+                    output_list += name[4:]
+                else:
+                    output_list += name
+    return output_list
+
+
+def rule_list(rule_def): 
+    stripped_rule_def = rule_def.strip()
+    rlist = ""
+    sep = ""
+    if len(stripped_rule_def) > 0:
+        rules = stripped_rule_def.split('|')
+        nrule = len(rules)
+        if nrule == 1:
+            stripped_rule = rules[0].strip()
+            rlist += component_list(stripped_rule)
+        elif nrule > 1:
+            if len(rules[0].strip()) < 1:
+                rlist = "["
+                close = "]"
+            else:
+                rlist = "("
+                close = ")"
+            for rule in rules:
+                stripped_rule = rule.strip()
+                if len(stripped_rule) > 0:
+                    rlist += sep + component_list(stripped_rule)
+                    sep = "|"
+            rlist += close
+    return rlist
+
+
+def print_abnf_rules():
+    name_len = 0
+    prologue = "<![CDATA["
+    epilogue = "]]>"
+    extra_linefeed = ""
+
+    for rule in abnf:
+        name_len = max(len(rule[0]), name_len)
+
+    for rule in abnf:
+        line   = rule[0].ljust(name_len) + " ="
+        words  = rule[1].split(' ')
+        margin = len(line)
+        width  = margin
+
+        for word in words:
+            wl = len(word) + 1
+            if width + wl > line_width:
+                line += "\n".ljust(margin+2)
+                width = margin
+            line += " " + word
+            width += wl
+
+        if rule[0].isupper():
+            extra_linefeed = ""
+        else:
+            if len(extra_linefeed) < 1:
+                print "\n"
+            extra_linefeed = "\n"
+        
+        print prologue + line + extra_linefeed
+        prologue = ""
+    print epilogue
+
+def make_abnf_rules(topresults):
+    for result in topresults:
+        abnf.append( (result, abnf_rule(topresults, result, 0, " ")) )
+
+
+def abnf_rule(topresults, result, rdepth, gap):
+    canonic   = ""
+    component = result.strip()
+    toplevel  = component in topresults 
+
+    if component in results:
+        rule_list = results[component]
+        if rule_list.startswith("(") and rule_list.endswith(")"):
+            stripped_rule_list = rule_list[1:-1].strip()
+            if rdepth < 1:
+                close = ""
+            else:
+                canonic += " ("
+                close = ")"
+                gap = ""
+        elif rule_list.startswith("[") and rule_list.endswith("]"):
+            stripped_rule_list = rule_list[1:-1].strip()
+            canonic += " ["
+            close = "]"
+            gap = ""
+        else:
+            stripped_rule_list = rule_list.strip()
+            close = ""
+
+        escaped_rule_list = stripped_rule_list.replace("\"|\"","Ö")
+        rules = escaped_rule_list.split("|")
+        sep = ""
+        for rule in rules:
+            components = rule.replace("Ö","\"|\"").split(' ')
+
+            if components[0].strip() == result:
+                rept = ")"
+            else:
+                rept = ""
+                canonic += sep
+                sep = " /"
+
+            first = True
+            for component in components:
+                component_name = component.strip()
+                if len(component_name) < 1:
+                    continue
+                if first and component_name == result:
+                    canonic += " *("
+                    gap = ""
+                elif component_name in topresults:
+                    canonic += gap + component_name
+                    gap = " "
+                else:
+                    canonic += abnf_rule(topresults, component_name, \
+                                         rdepth+1,gap)
+                    gap = " "
+                first = False
+            canonic += rept
+
+        canonic += close
+    else:
+        canonic += gap + component
+
+    return canonic
+
+
+lfile       = sys.argv[1]
+yfile       = sys.argv[2]
+line_width  = 78
+inputdir    = os.path.dirname(yfile)
+scriptdir   = sys.path[0] 
+lname       = os.path.basename(lfile)
+yname       = os.path.basename(yfile)
+start       = ""
+grammar     = False
+prologue    = False
+comment     = False
+toplevel    = False
+depth       = 0
+result      = ""
+result_def  = ""
+results     = {}
+top_results = []
+abnf        = []
+pos         = 0
+end         = 0
+
+
+
+if not inputdir == "" and not inputdir.endswith("/"):
+    inputdir = "%s/" % inputdir
+
+if not scriptdir == "" and not scriptdir.endswith("/"):
+    scriptdir = "%s/" % scriptdir
+
+
+with open(lfile, "r") as l:
+    skip = False
+    for line in l.xreadlines():
+        if skip:
+            if line.startswith("%}"):
+                skip = False
+        else:
+            if line.startswith("%{"):
+                skip = True
+            elif line.startswith("%%"):
+                break
+            elif line.startswith("/*"):
+                continue
+            
+            stripped_line = line[:-1].strip()
+
+            if len(stripped_line) < 3:
+                continue
+
+            sp  = stripped_line.find(" ")
+            tab = stripped_line.find("\t")
+            
+            if sp > 0 and (tab < 0 or tab > sp):
+                delim = sp
+            elif tab > 0 and (sp < 0 or sp > tab):
+                delim = tab
+            else:
+                continue
+
+            key   = stripped_line[:delim].strip()
+            value = stripped_line[delim:].strip()
+
+            if key[0] == "%" or len(value) < 1:
+                continue
+
+            if value.isalpha():
+                abnf.append( (key, " \"" + value.upper() + "\"") )
+            else:
+                if value[0] == "\\" and len(value) == 2:
+                    results[key] = "\"%s\"" % value[1]
+                elif len(value) == 1:
+                    results[key] = "\"%s\"" % value[0]
+                else:
+                    if value.find("[") < 0 and value.find("(") < 0:
+                        results[key] = "\"%s\"" % value.replace("/","")
+                    else:
+                        abnf.append( (key.lower(), " "+regexp_to_abnf(value)) )
+                        results[key] = key.lower()
+
+    l.close()
+
+
+with open(yfile, "r") as y:
+    for line in y.xreadlines():
+        if grammar:
+            if line.startswith("%%"):
+                break
+            elif line.startswith("/*#") and line.endswith("#*/\n"):
+                key = line[3:-4].strip()
+                if key == "toplevel":
+                    toplevel = True
+            else:
+                pos = 0
+                end = len(line)
+                if depth == 0:
+                    colon = line.find(":")
+                    slash_star = line.find("/*")
+                    if colon > 0 and (slash_star < 0 or slash_star > colon):
+                        result = line[:colon]
+                        result_def = ""
+                        pos = colon + 1
+                while pos < end-1:
+                    if comment:
+                        star_slash = line.find("*/", pos)
+                        if star_slash < 0:
+                            break
+                        pos = star_slash + 2
+                        comment = False
+                    else:
+                        slash_star  = line.find("/*", pos)
+                        open_brace  = line.find("{" , pos)
+                        close_brace = line.find("}" , pos)
+                        if slash_star >= 0 and \
+                           (open_brace  < 0 or slash_star < open_brace) and \
+                           (close_brace < 0 or slash_star < close_brace):
+                           
+                           comment = True
+                           pos = slash_star + 2
+                           continue
+                        if open_brace >= 0 and \
+                           (close_brace < 0 or open_brace < close_brace) and \
+                           (slash_star  < 0 or open_brace < slash_star ):
+
+                            if depth == 0:
+                                result_def += line[pos: open_brace]
+                            depth += 1
+                            pos = open_brace + 1
+                            continue
+                        if close_brace >= 0 and \
+                           (open_brace < 0 or close_brace < open_brace) and \
+                           (slash_star < 0 or close_brace < slash_star):
+                            depth -= 1
+                            pos = close_brace + 1
+                            continue
+                        if depth == 0:
+                            semicolon = line.find(";", pos)
+                            if semicolon >= pos:
+                                result_def += line[pos: semicolon]
+                                results[result] = rule_list(result_def)
+                                if toplevel:
+                                    top_results.append(result)
+                                result = ""
+                                result_def = ""
+                                toplevel = False
+                            else:
+                                result_def += line[pos: -1]
+                        break
+        else:
+            if prologue:
+                if line.startswith("%}"):
+                    prologue = False
+            elif line.startswith("%{"):
+                prologue = True
+            elif line.startswith("%start"):
+                start = line[6:].strip()
+            elif line.startswith("%%"):
+                grammar = True
+    y.close()
+
+
+
+make_abnf_rules(top_results)
+
+print "<!-- XML file was automatically generated from %s and from %s -->\n" % \
+    (lname, yname)
+
+print "<screen>"
+print_abnf_rules()
+print "</screen>"
+
+
+
+
diff --git a/doc/scripts/dblyxfix.py b/doc/scripts/dblyxfix.py
new file mode 100755 (executable)
index 0000000..b162094
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+# -*- coding: latin-1 -*-
+
+
+import os, sys, re
+from lxml import etree
+
+def fix_dummy(broken_xml):
+    start = 0
+    end = len(broken_xml)
+    fixed_xml = ""
+
+    for match in re.finditer(dummy_pattern, broken_xml):
+        fixed_xml += broken_xml[start:match.start()]
+        start = match.end()
+
+    if start < end:
+        fixed_xml += broken_xml[start:end]
+    return fixed_xml
+
+def fix_graphs(broken_xml):
+    start = 0
+    end = len(broken_xml)
+    fixed_xml = ""
+
+    for match in re.finditer(graph_pattern, broken_xml):
+        fixed_xml += fix_dummy(broken_xml[start:match.start()])
+        fixed_xml += "<!ENTITY graph%s \"%s\">" % \
+            (match.group(1), os.path.basename(match.group(2)))
+        start = match.end()
+
+    if start < end:
+        fixed_xml += fix_dummy(broken_xml[start:end])
+    return fixed_xml
+
+def fix_files(broken_xml):
+    start = 0
+    end = len(broken_xml)
+    fixed_xml = ""
+
+    for match in re.finditer(file_pattern, broken_xml):
+        fixed_xml += fix_graphs(broken_xml[start:match.start()])
+        fixed_xml += "<!ENTITY file%s SYSTEM \"%s\">" % \
+            (match.group(1), match.group(2))
+        start = match.end()
+
+    if start < end:
+        fixed_xml += fix_graphs(broken_xml[start:end])
+    return fixed_xml
+
+
+if len(sys.argv) >= 2:
+    path = sys.argv[1]
+    fnam = os.path.basename(path)
+else:
+    fnam = "<unknown>"
+
+sys.stderr.write("  DBLYX %s\n" % fnam)
+
+try:
+    input  = open(path, "r")
+    input_xml = input.read()
+    input.close()
+except IOError as (errno, strerror):
+    print "Input error %d - %s" % (errno, strerror)
+    exit(errno)
+
+dummy_pattern = re.compile("<[/]?dummy>")
+file_pattern = re.compile("<!ENTITY file([0-9]*) \"([^\"]*)\">")
+graph_pattern = re.compile("<!ENTITY graph([0-9]*) \"([^\"]*)\">")
+
+modified_xml = fix_files(input_xml)
+
+parser  = etree.XMLParser(strip_cdata=False)
+xmltree = etree.XML(modified_xml, parser)
+
+output_xml = etree.tostring(xmltree, pretty_print=True)
+
+if len(sys.argv) == 3:
+    try:
+        output = open(sys.argv[2], "w")
+        output.write(output_xml)
+        output.close()
+    except IOError as (errno, strerror):
+        print "Output error %d - %s" % (errno, strerror)
+else:
+    print output_xml
diff --git a/doc/scripts/doxml2db.py b/doc/scripts/doxml2db.py
new file mode 100755 (executable)
index 0000000..ab33657
--- /dev/null
@@ -0,0 +1,915 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+   Doxygen XML to DocBook converter
+   ================================
+
+   Usage: doxml2db.py [OPTIONS] <dir> <doxml-files> {<docbook-file>|--}
+
+   <dir>           - is the directory where the doxml files are
+   <doxml-file>    - list of one or more whitespace separated doxml input files
+   <docbook-file>  - the output file
+   --              - docbook is printed to stdout
+
+   OPTIONS
+   -h or --help    - print this help message
+   --title=<title> - main chapter or section title
+   --depth=<depth> - 0 = chapter, 1+ = section
+
+   :license: BSD.
+"""
+import sys
+import os
+import re
+import lxml.etree as ET
+from copy import deepcopy
+
+_files   = set()
+_sources = {}
+_comment_pattern = re.compile(r"(/\*.*\*/)|(//.*)")
+
+def _main():
+    title = None
+    depth = 1
+    sect  = {'enum':[], 'struct':[], 'union':[],
+             'typedef':[], 'function':[], 'define':[]}
+
+    fidx = 1
+    for arg in sys.argv[fidx:]:
+        if arg.startswith('-') and arg != '--':
+            if arg == '-h' or arg == '--help':
+                _usage()
+            elif arg.startswith('--title='):
+                title = arg[8:]
+            elif arg.startswith('--depth='):
+                depth = int(arg[8:])
+            else:
+                _usage("invalid option '%s'" % arg)
+            fidx += 1
+    if len(sys.argv) < fidx + 3:
+        _usage("too few arguments")
+    else:
+        if os.path.isdir(sys.argv[fidx]):
+            doxydir = sys.argv[fidx]
+        else:
+            _usage(sys.argv[fidx] + " not a directory")
+        for f in sys.argv[fidx+1:-1]:
+            _add_doxml_file(doxydir, f, sect)
+        if sys.argv[-1] == '--':
+            dbfile = sys.stdout
+            close_dbfile = False
+        else:
+            dbfile = open(sys.argv[-1], "w")
+            close_dbfile = True
+
+
+    # parse all the doxml files
+    # store the result in variable 'sect'
+    for name, doxml in _files:
+        if name[0] != '<':
+            sys.stderr.write("  DOXML %s\n" % name)
+        ParseDoxmlFile(doxml, sect)
+
+    # process 'sect' (eg. resolve typedefs, skip useless entries)
+    # merge evetrything into a sorted list ('defs')
+    defs = ProcessSections(sect)
+
+    dbroot = BuildDBTree(defs, title, depth)
+
+    # dump the dbtree
+    if dbroot.tag == 'root':
+        for section in dbroot.iterchildren():
+            dbfile.write(ET.tostring(section, pretty_print=True))
+    else:
+        dbfile.write(ET.tostring(dbroot, pretty_print=True))
+
+    if close_dbfile:
+        dbfile.close()
+
+
+def _usage(errmsg):
+    if len(errmsg) < 1:
+        err = 0
+    else:
+        err = 1
+        sys.stderr.write("Error: " + errmsg + "\n\n")
+    sys.stderr.write(__doc__)
+    sys.exit(err)
+
+def _print_sections(sect):
+    for s in sect:
+        _print_item("*** %s" % s, sect[s], 0)
+
+def _print_item(name, value, indent):
+    if value.__class__.__name__ == "list":
+        if len(name) > 0:
+            print "%s%s" % (" "*indent, name)
+        for lval in value:
+            _print_item('', lval, indent+4)
+    elif value.__class__.__name__ == "dict":
+        if len(name) < 1 and 'name' in value:
+            name = value['name']
+        if len(name) > 0:
+            print "%s%s" % (" "*indent, name)
+        for dnam, dval in value.iteritems():
+            _print_item(dnam+':', dval, indent+4)
+    else:
+        print "%s%s %s" % (" "*indent, name, value)
+
+
+####################### parse doxml to internal format #######################
+
+
+def ParseDoxmlFile(doxml_file, sect):
+    parser = ET.XMLParser(remove_comments=True, strip_cdata=False)
+    tree = ET.parse(doxml_file, parser=parser)
+    root = tree.getroot()
+    _traverse(root, sect, {})
+
+def _add_doxml_file(doxydir, path, sect):
+    name = re.sub("(\.)([hc]$)", "_8\\2.xml", os.path.basename(path))
+    doxml = os.path.join(doxydir, name)
+    if os.path.isfile(doxml):
+        _files.add((name, doxml))
+        _find_includes_in(doxydir, doxml, sect)
+        return True
+    return False
+
+def _find_includes_in(doxydir, filnam, sect):
+    tree = ET.parse(filnam)
+    root = tree.getroot()
+    for topel in root.getchildren():
+        if topel.tag == "compounddef" and topel.get("kind") == "file":
+            for el in topel.getchildren():
+                if el.tag == "includes" and len(el.text) > 0:
+                    _add_doxml_file(doxydir, el.text, sect)
+                elif el.tag == "innerclass" and len(el.get("refid")) > 0:
+                    refid = el.get("refid")
+                    fpath = os.path.join(doxydir, refid + ".xml")
+                    if os.path.isfile(fpath):
+                        for snam in sect:
+                            if refid.startswith(snam):
+                                _files.add(("<" + el.text + ">", fpath))
+                                break
+    return
+
+def _traverse(el, sect, entry):
+    if el.tag in globals():
+        entry = globals()[el.tag](el, sect, entry)
+    return entry
+
+def _traverse_children(el, sect, entry):
+    for i in el.getchildren():
+            entry = _traverse(i, sect, entry)
+    return entry
+
+def _attribute_tag(el, entry, name):
+    if el.text is not None and len(el.text) > 0:
+        entry[name] = el.text
+    return entry
+
+def _text_markup(el, sect, entry, name):
+    text = _traverse_children(el, sect, [])
+    if len(text) > 0:
+        entry[name] = text
+    return entry
+
+
+def _list_collector(el, sect, entry, name, init):
+    if name not in entry:
+        entry[name] = []
+    list_entry = _traverse_children(el, sect, {})
+    if len(list_entry) > 0:
+        if init is not None and len(init) > 0:
+            list_entry.update(init)
+        entry[name].append(list_entry)
+    return entry
+
+def _get_code(entry, path, start, end):
+    global _sources, _comment_pattern
+    if path not in _sources:
+        if not os.path.isfile(path):
+            return
+        f = open(path, "r")
+        _sources[path] = f.readlines()
+        f.close()
+    code =  "".join(_sources[path][start-1:end])
+    entry['code'] = re.sub(_comment_pattern, '', code).rstrip()
+    return entry
+
+
+def compounddef(el, sect, entry):
+    t = el.get('kind')
+    if t in ['struct', 'union']:
+        sect[t].append(_traverse_children(el, sect, {}))
+    elif t == 'file':
+        entry = _traverse_children(el, sect, entry)
+    return entry
+
+def compoundname(el, sect, entry):
+    if el.getparent().get('kind') in ['struct', 'union']:
+        entry['name'] = el.text
+    return entry
+    
+def sectiondef(el, sect, entry):
+    if el.get('kind') in ['enum', 'typedef', 'func', 'define'] or \
+       el.getparent().get('kind') in ['struct', 'union']:
+        entry = _traverse_children(el, sect, entry)
+    return entry
+
+
+def memberdef(el, sect, entry):
+    t = el.get('kind')
+    if t in sect:
+        sect[t].append(_traverse_children(el, sect, {}))
+    elif t == 'variable':
+        entry = _list_collector(el, sect, entry, 'variables', None)
+    return entry
+
+def location(el, sect, entry):
+    f = el.get('bodyfile')
+    s = int(el.get('bodystart', -1))
+    e = int(el.get('bodyend', -1))
+    if f is not None and s > 0 and e > 0:
+        p = el.getparent()
+        if p.tag == 'compounddef' and p.get('kind') in ['struct', 'union']:
+            entry['file'] = {'path':f, 'start':s, 'end':e}
+            entry = _get_code(entry, f, s,e)
+    return entry
+
+
+def enumvalue(el, sect, entry):
+    return _list_collector(el, sect, entry, 'enumvalues', None)
+
+def name(el, sect, entry):
+    return _attribute_tag(el, entry, 'name')
+
+def type(el, sect, entry):
+    return _attribute_tag(el, entry, 'type')
+
+def declname(el, sect, entry):
+    return _attribute_tag(el, entry, 'declname')
+
+def definition(el, sect, entry):
+    return _attribute_tag(el, entry, 'def')
+
+def argsstring(el, sect, entry):
+    return _attribute_tag(el, entry, 'args')
+
+def initializer(el, sect, entry):
+    return _attribute_tag(el, entry, 'value')
+
+def briefdescription(el, sect, entry):
+    return _text_markup(el, sect, entry, 'brief')
+
+def detaileddescription(el, sect, entry):
+    return _text_markup(el, sect, entry, 'descr')
+
+def para(el, sect, entry):
+    if entry.__class__.__name__ == 'list':
+        if el.text is not None and len(el.text.strip()) > 0:
+            entry.append({'para' : el.text.strip()})
+        else:
+            entry = _traverse_children(el, sect, entry)
+    return entry
+
+def param(el, sect, entry):
+    return _list_collector(el, sect, entry, 'param', None)
+
+def parameterlist(el, sect, entry):
+    if entry.__class__.__name__ == 'list':
+        entry.append({el.get('kind') : _traverse_children(el, sect, [])})
+    return entry
+
+def parameteritem(el, sect, entry):
+    if entry.__class__.__name__ == 'list':
+        entry.append(_traverse_children(el, sect, {}))
+    return entry
+
+def parametername(el, sect, entry):
+    if el.text is not None:
+        entry['name'] = el.text
+    return entry
+
+def parameterdescription(el, sect, entry):
+    entry['descr'] = _traverse_children(el, sect, [])
+    return entry
+
+def simplesect(el, sect, entry):
+    if entry.__class__.__name__ == 'list':
+        if el.get('kind') in ['return']:
+            entry.append(_text_markup(el, sect, {}, el.get('kind')))
+    return entry
+
+def programlisting(el, sect, entry):
+    program = _traverse_children(el, sect, '')
+    if len(program) > 0:
+        if entry.__class__.__name__ == 'list':
+            entry.append({'code' : program})
+        elif entry.__class__.__name__ == 'dict':
+            entry['code'] = program
+    return entry
+
+def codeline(el, sect, entry):
+    entry += _traverse_children(el, sect, '') + '\n'
+    return entry
+
+def highlight(el, sect, entry):
+    if el.text is not None:
+        entry += el.text
+    entry += _traverse_children(el, sect, '')
+    if el.tail is not None:
+        entry += el.tail
+    return entry
+
+def sp(el, sect, entry):
+    entry += ' '
+    if el.tail is not None:
+        entry += el.tail
+    return entry
+
+
+
+doxygen = _traverse_children
+parameternamelist = _traverse_children
+osectiondef = sectiondef
+
+########################## Build output tree ################################
+
+def ProcessSections(sect):
+    index = {'define':0, 'typedef':1, 'enum':2,
+             'struct':3, 'union':3, 'function':4}
+    #_print_sections(sect)
+    td = {}
+    defs = []
+
+
+    for i in sect['typedef']:
+        if i['type'] not in td:
+            td[i['type']] = []
+        td[i['type']].append({'name':i['name'], 'def':i['def'], 'print':True})
+
+    for i in ['struct', 'union', 'enum']:
+        for s in sect[i]:
+            if 'name' in s and s['name'].startswith('@'):
+                continue
+            de  = {'sect':i, 'index':index[i]}
+            for key, value in s.iteritems():
+                tdk = "%s %s" % (i, value)
+                if key == 'name' and tdk in td and len(td[tdk]) == 1:
+                    value = td[tdk][0]['name']
+                    td[tdk][0]['print'] = False
+                de[key] = value
+            defs.append(de)
+    for t,l in td.iteritems():
+        for d in l:
+            if not d['print']:
+                continue
+            df = {'type':t, 'sect':'typedef', 'index':index['typedef']}
+            for key, value in d.iteritems():
+                if key != 'print':
+                    df[key] = value
+            defs.append(df)
+    for t in ['define', 'function']:
+        extra = {'sect':t, 'index':index[t]}
+        for s in sect[t]:
+            if 'name' in s and not s['name'].startswith('@'):
+                de = deepcopy(s)
+                de.update(extra)
+                defs.append(de)
+    defs.sort(_cmpfunc)
+    return defs
+
+def _cmpfunc(a, b):
+    if 'index' in a and 'index' in b:
+        if a['index'] > b['index']:
+            return 1
+        if a['index'] < b['index']:
+            return -1
+    if 'name' in a and 'name' in b:
+        if a['name'] > b['name']:
+            return 1
+        elif a['name'] < b['name']:
+            return -1
+    return 0
+
+############################# BuildDBTree ###################################
+def BuildDBTree(defs, title, depth):
+    global _escape_pattern, _escape_map
+
+    _escape_pattern = re.compile(r"([\"&<>\x89-\xff])")
+    _escape_map = {"'" : '&apos;',
+                   '"' : '&quot;',
+                   '&' : '&amp;' ,
+                   '<' : '&lt;'  ,
+                   '>' : '&gt;'   }
+
+    index = -1
+    root, section_depth = _make_root(title, depth)
+
+    for d in defs:
+        if d['index'] != index:
+            index = d['index']
+            container, new_depth = _make_container(root, index, section_depth)
+        entry_builder = "build_%s_entry" % d['sect']
+        if entry_builder in globals():
+            globals()[entry_builder](container, d, new_depth)
+
+    return root
+
+
+def _make_root(title, depth):
+    if title is None:
+        section_depth = depth
+        root = ET.Element('root')
+    else:
+        root = _make_section(None, title, depth=depth)
+        section_depth = depth + 1
+    return root, section_depth
+
+def _make_container(parent, index, section_depth):
+    container_builder = ['build_define_container',
+                         'build_typedef_container',
+                         'build_enum_container',
+                         'build_struct_container',
+                         'build_function_container']
+
+    if container_builder[index] in globals():
+        return globals()[container_builder[index]](parent, section_depth)
+
+    return parent, section_depth
+
+
+def _make_section(parent, title, depth):
+    if depth is None or depth.__class__.__name__ != 'int' or depth > 4:
+        ttl = ET.Element('para')
+        ttl.text = _escape_text(title.strip())
+        section = ET.Element('para')
+        if parent is not None:
+            parent.append(ttl)
+    else:
+        if depth == 0:
+            tag = 'chapter'
+        else:
+            tag = "sect%d" % depth
+
+        section = ET.Element(tag)
+
+        ttl = ET.Element('title')
+        ttl.text = _escape_text(title.strip())
+        section.append(ttl)
+
+    if parent is not None:
+        parent.append(section)
+
+    return section
+
+
+def _make_refentry(parent, title, name, volid="3"):
+    if parent is not None:
+        parent.append(ET.Element('beginpage'))
+
+    ref_entry = ET.Element('refentry', {'id':name})
+    parent.append(ref_entry)
+
+    ref_meta = ET.Element('refmeta')
+    ref_entry.append(ref_meta)
+
+    ref_entry_title = ET.Element('refentrytitle')
+    ref_entry_title.text = title.strip()
+    ref_meta.append(ref_entry_title)
+
+    man_volnum = ET.Element('manvolnum')
+    man_volnum.text = volid
+    ref_meta.append(man_volnum)
+
+    if parent is not None:
+        parent.append(ref_entry)
+
+    return ref_entry
+
+
+def _make_refdiv(parent, divnam):
+    div = ET.Element("ref%sdiv" % divnam)
+    if parent is not None:
+        parent.append(div)
+    return div
+
+def _make_refnamediv(parent, names, brief=None):
+    if brief is not None and len(brief.strip()) > 0:
+        div = _make_refdiv(parent, 'name')
+
+        if names.__class__.__name__ == 'str':
+            names = [names]
+
+        for n in names:
+            el = ET.Element('refname')
+            el.text = n.strip()
+            div.append(el)
+
+        ref_purpose = ET.Element('refpurpose')
+        ref_purpose.text = brief.strip()
+        div.append(ref_purpose)
+
+        if parent is not None:
+            parent.append(div)
+    else:
+        div = None
+    return div
+
+def _make_refsynopsisdiv(parent, typ, name, params, info=None):
+    div = _make_refdiv(parent, 'synopsis')
+
+    if info is not None and len(info.strip()) > 0:
+        func_synopsis_info = ET.Element('funcsynopsisinfo')
+        func_synopsis_info.text = _escape_text(info.strip())
+        div.append(func_synopsis_info)
+
+    _make_funcprototype(div, typ, name, params)
+
+    if parent is not None:
+        parent.append(div)
+    return div
+
+def _make_refsection(parent, title):
+    refsect1 = ET.Element('refsect1')
+
+    ttl = ET.Element('title')
+    ttl.text = _escape_text(title.strip())
+    refsect1.append(ttl)
+    
+    if parent is not None:
+        parent.append(refsect1)
+    return refsect1
+
+
+def _make_funcprototype(parent, typ, name, params):
+    def format_type(typ):
+        stripped_type = typ.strip()
+        if " " in stripped_type:
+            return stripped_type
+        return "%s " % stripped_type
+
+    func_prototype = ET.Element('funcprototype')
+
+    func_def = ET.Element('funcdef')
+    func_def.text = format_type(_escape_text(typ.strip()))
+    func_prototype.append(func_def)
+
+    function = ET.Element('function')
+    function.text = _escape_text(name)
+    func_def.append(function)
+
+    for p in params:
+        paramdef = ET.Element('paramdef')
+        paramdef.text = format_type(_escape_text(p['type'].strip()))
+        func_prototype.append(paramdef)
+
+        if 'declname' in p and len(p['declname'].strip()) > 0:
+            parameter = ET.Element('parameter')
+            parameter.text = _escape_text(p['declname'].strip())
+            paramdef.append(parameter)
+
+    if parent is not None:
+        parent.append(func_prototype)
+
+    return func_prototype
+
+def _make_varname(parent, name):
+    varname = ET.Element('varname')
+    varname.text = _escape_text(name.strip())
+    if parent is not None:
+        parent.append(varname)
+    return varname
+
+def _make_para(parent, text=None):
+    para = ET.Element('para')
+    if text is not None:
+        para.text = "%s" % text
+    if parent is not None:
+        parent.append(para)
+    return para
+
+def _make_code(parent, code):
+    screen = ET.Element('programlisting')
+    screen.text = ET.CDATA(code)
+    if parent is not None:
+        parent.append(screen)
+    return screen
+
+def _unmarked_text(elements):
+    return _escape_text(_collect_text(elements))
+
+def _collect_text(elements):
+    text = ''
+
+    if elements is not None:
+        typ = elements.__class__.__name__
+
+        if typ == 'list':
+            for entry in elements:
+                text += _collect_text(entry)
+        elif typ == 'dict':
+            for name, value in elements.iteritems():
+                if name not in ['param', 'return']:
+                    text += (" %s" % value)
+        else:
+            text += (" %s" % elements)
+
+    return text.strip()
+
+def _add_marked_text(parent, elements):
+    added = False
+    for el in elements:
+        for name, value in el.iteritems():
+            if name == 'para':
+                para = _make_para(parent, value)
+                added = True
+    return added
+
+def _make_param_list(parent, descr, render='table'):
+    entries = 0
+    if render == 'table':
+        param_list, tbody = _make_list_table(['1*', '5*'])
+        for en in descr:
+            if 'param' in en:
+                for p in en['param']:
+                    if len(set(p) & set(['name', 'descr'])) != 2:
+                        continue
+                    _add_list_table_row(tbody, p['name'], p['descr'])
+                    entries += 1
+    elif render == 'varlist':
+        param_list = ET.Element('variablelist')
+        for en in descr:
+            if 'param' in en:
+                for p in en['param']:
+                    if len(set(p) & set(['name', 'descr'])) != 2:
+                        continue
+                    name = _make_varname(None, p['name'])
+                    item = _add_varlist_item(param_list, name, '')
+                    _add_marked_text(item, p['descr'])
+                    entries += 1
+    if entries > 0:
+        if parent is not None:
+            parent.append(param_list)
+        return param_list
+    return None
+
+def _make_variable_list(parent, variables, ratio, render='table'):
+    entries = 0
+    if render == 'table':
+        variable_list, tbody = _make_list_table(['1*', '%d*' % ratio])
+        for en in variables:
+            if 'brief' in en:
+                _add_list_table_row(tbody, en['name'], en['brief'])
+                entries += 1
+    elif render == 'varlist':
+        variable_list = ET.Element('variablelist')
+        for en in variables:
+            if 'brief' in en:
+                name = _make_varname(None, en['name'])
+                item = _add_varlist_item(variable_list, name, '')
+                _add_marked_text(item, en['brief'])
+                entries += 1
+    if entries > 0:
+        if parent is not None:
+            parent.append(variable_list)
+        return variable_list
+    return None
+
+
+def _make_list_table(colwidths):
+        table = ET.Element('table',
+                           {'align'     : 'left',
+                            'frame'     : 'none',
+                            'colsep'    : '0',
+                            'rowsep'    : '0',
+                            'rowheader' : 'norowheader'})
+
+        tgroup = ET.Element('tgroup', {'cols':'%s' % len(colwidths)})
+        table.append(tgroup)
+
+        for i in colwidths:
+            tgroup.append(ET.Element('colspec', {'colwidth':i}))
+
+        tbody = ET.Element('tbody')
+        tgroup.append(tbody)
+
+        return table, tbody
+
+def _add_list_table_row(parent, name, descr):
+    row = ET.Element('row')
+    parent.append(row)
+    
+    name_entry = ET.Element('entry', {'align':'left', 'valign':'top'})
+    _make_varname(name_entry, name)
+    row.append(name_entry)
+
+    desc_entry = ET.Element('entry', {'align':'left', 'valign':'top'})
+    _add_marked_text(desc_entry, descr)
+    _make_para(desc_entry, ' ')
+    row.append(desc_entry)
+
+
+def _add_varlist_item(varlist, key, brief=None, descr=None):
+    list_entry = ET.Element('varlistentry')
+    varlist.append(list_entry)
+
+    term = ET.Element('term')
+    if ET.iselement(key):
+        term.append(key)
+    else:
+        term.text = key.strip()
+    list_entry.append(term)
+
+    list_item = ET.Element('listitem')
+    if brief is not None:
+        if len(brief) > 0:
+            list_item.text = "- %s" % brief
+    else:
+        list_item.text = ":"
+
+    if descr is not None:
+        if descr.__class__.__name__ == 'list':
+            list_item.extend(descr)
+        else:
+            list_item.append(descr)
+
+    list_entry.append(list_item)
+                        
+    return list_item
+    
+def _escape_text(text):
+    global _escape_pattern, _escape_map
+
+    def find_replacement(match):
+        pattern = match.group(1)
+        if pattern == None:
+            return ''
+        elif pattern in _escape_map:
+            return _escape_map[pattern]
+        else:
+            return ''
+
+    return re.sub(_escape_pattern, find_replacement, text)
+
+def build_define_container(parent, section_depth):
+    section = _make_section(parent, "Preprocessor definitions", section_depth)
+    parent.append(section)
+    
+    if section_depth < 2:
+        table, container = _make_list_table(['1*', '2*'])
+        section.append(table)
+    else:
+        container = ET.Element('variablelist')
+        section.append(container)
+    return container, section_depth + 1
+
+def build_define_entry(parent, define, depth):
+    if len(set(['name', 'value', 'brief']) & set(define)) != 3:
+        return
+
+    name  = define['name']
+
+    if depth < 3:
+        descr = define['brief']
+        if 'descr' in define:
+            descr += define['descr']
+        _add_list_table_row(parent, name, define['brief'])
+    else:
+        brief = _unmarked_text(define['brief'])
+        cdata = "#define %s %s\n" % (name, define['value'])
+        para = _make_para(None) 
+        code = _make_code(para, cdata)    
+
+        _add_varlist_item(parent, name, brief, para)
+
+
+def build_enum_entry(parent, enum, depth):
+    if 'name' not in enum:
+        return
+    else:
+        name = enum['name']
+
+    if depth < 2:
+        if len(set(['brief', 'enumvalues']) & set(enum)) != 2:
+            return
+        ref_entry = _make_refentry(parent, "Enumeration %s" % name, name, '7')
+
+        _make_refnamediv(ref_entry, name, _unmarked_text(enum['brief']))
+
+        elist = _make_variable_list(None, enum['enumvalues'], 3)
+        if elist is not None:
+            _make_refsection(ref_entry, 'Values').append(elist)
+    else:
+        return
+
+
+def build_struct_entry(parent, struct, depth, kind='struct'):
+    if 'name' not in struct:
+        return
+    else:
+        name = struct['name']
+
+    if depth < 2:
+        if len(set(['brief', 'code', 'variables']) & set(struct)) != 3:
+            return
+        title = "%s %s" % (kind.capitalize(), name)
+        ref_entry = _make_refentry(parent, title, name, '7')
+
+        _make_refnamediv(ref_entry, name, _unmarked_text(struct['brief']))
+
+        synop_div = _make_refdiv(ref_entry, 'synopsis')
+
+        synopsis = ET.Element('synopsis')
+        synopsis.text = ET.CDATA(struct['code'])
+        synop_div.append(synopsis)
+
+        vlist = _make_variable_list(None, struct['variables'], 5)
+        if vlist is not None:
+            _make_para(synop_div, "Where")
+            _make_para(synop_div).append(vlist)
+
+        if 'descr' in struct:
+            descr_div = _make_refsection(None, 'Description')
+            if _add_marked_text(descr_div, struct['descr']):
+                ref_entry.append(descr_div)
+    else:
+        return
+
+
+def build_union_entry(parent, union, depth):
+    build_struct_entry(parent, union, depth, 'union')
+
+def build_function_entry(parent, func, depth):
+    if 'name' not in func:
+        return
+    else:
+        name = func['name']
+
+    if depth < 2:
+        if len(set(['def', 'type', 'brief']) & set(func)) != 3:
+            return
+
+        if 'param' in func:
+            params = func['param']
+        else:
+            params = [{'type':'void', 'declname':''}]
+
+        ref_entry = _make_refentry(parent, "Function %s" % name, name, '3')
+
+        _make_refnamediv(ref_entry, name, _unmarked_text(func['brief']))
+        
+        synop_div = _make_refsynopsisdiv(ref_entry, func['type'], name, params)
+
+        if 'descr' in func:
+            descr = func['descr']
+            plist = _make_param_list(None, descr)
+            if plist is not None:
+                _make_para(synop_div, 'Where')
+                _make_para(synop_div).append(plist)
+
+            descr_div = _make_refsection(None, 'Description')
+            if _add_marked_text(descr_div, descr):
+                ref_entry.append(descr_div)
+                
+            code = ret = None
+            for d in descr:                
+                if 'code' in d:
+                    code = d['code']
+                if 'return' in d:
+                    ret = d['return']
+            if ret is not None:
+                return_div = _make_refsection(ref_entry, 'Return value')
+                _add_marked_text(return_div, ret)
+            if code is not None:
+                example_div = _make_refsection(ref_entry, 'Example')
+                _make_code(example_div, d['code'])
+    else:
+        if len(set(['def', 'args', 'brief']) & set(func)) != 3:
+            return
+
+        section = _make_section(parent, "%s()" % name, depth+1)
+
+        synopsis = _make_section(section, "SYNOPSIS", depth+2)
+        _make_code(_make_para(synopsis), "%s%s" % (func['def'], func['args']))
+        _make_para(synopsis, "%s() %s" % (name, _unmarked_text(func['brief'])))
+
+        if 'descr' not in func:
+            return
+
+        valid = False
+        descr = _make_section(None, "DESCRIPTION", depth+2)
+        plist = _make_param_list(None, func['descr'])
+        if plist is not None:
+            valid = True
+            descr.append(plist)
+            if _add_marked_text(descr, func['descr']):
+                valid = True
+        if valid:
+            section.append(descr)
+
+#############################################################################
+
+if __name__ == '__main__':
+    _main()
diff --git a/doc/scripts/doxydeps.py b/doc/scripts/doxydeps.py
new file mode 100755 (executable)
index 0000000..0dc0b02
--- /dev/null
@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+   Making Doxygen dependencies
+   ===========================
+
+   Usage: doxydeps.py [OPTIONS] [depname] [doxyfile] [dir]
+
+   <depname>       - is the dependency name, ie. the dependency file will
+                     look like:
+                         <depname>: file1.h file2.c ...
+                     Depname defaults to basename of <doxyfile>.
+   <doxyfile>      - is the path to the doxygen configuration file.
+                     The default value is ./Doxyfile
+   <dir>           - where the dependency file will be put.
+                     The default value is .deps
+                     if dir is '--' the dependencies will be printed on stdout
+
+   doxydeps reads the doxygen configuration file and produces a dependency
+   file with identical name + '.P' suffix under dependency directory.
+
+
+   :license: BSD.
+"""
+
+import sys
+import os
+import re
+import glob
+import commands
+import errno
+
+# TODO: get the compilers from autoconf
+c_compiler = "/usr/bin/gcc"
+
+def _main():
+    depname = None
+    doxyfile = 'doxyfile'
+    depdir = '.deps'
+
+    idx = 1
+    for arg in sys.argv[idx:]:
+        if arg == '-h' or arg == '--help':
+            _usage()
+        elif arg.startswith('-'):
+            _usage("invalid option %s" % arg)
+        else:
+            break
+    if len(sys.argv) > idx:
+        depname = sys.argv[idx]
+        idx += 1
+    if len(sys.argv) > idx:
+        doxyfile = sys.argv[idx]
+        idx += 1
+    if len(sys.argv) > idx:
+        depdir = sys.argv[idx]
+        idx += 1
+    if len(sys.argv) > idx:
+        _usage("extra arguments %s" % " ".join(sys.argv[idx:]))
+    if depname == None:
+        depname = os.path.basename(doxyfile)
+
+    if depdir == '--':
+        output = sys.stdout
+        close_output = False
+    else:
+        depfile = "%s.P" % os.path.basename(doxyfile)
+        deppath = os.path.join(depdir, depfile)
+        sys.stderr.write("  DOXYD %s\n" % depfile)
+
+        try:
+            os.makedirs(depdir)
+        except OSError as (errcode, strerror):
+            if errcode != errno.EEXIST:
+                sys.stderr.write("can't create directory '%s': %s\n" % 
+                                 (depdir, strerror))
+                exit(errcode)
+        try:
+            output = open(deppath, "w")
+        except IOError as (errcode, strerror):
+            sys.stderr.write("failed to open '%s': %s\n" %
+                             (deppath, strerror))
+            exit(errcode)
+        close_output = True
+
+    deps = DoxygenDependencies(doxyfile)
+    deps.insert(0, "%s:" % depname)
+    
+    output.write(" ".join(list("%s \\\n" % d for d in deps))[:-3] + "\n")
+
+    if close_output:
+        output.close()
+
+
+def _usage(errmsg):
+    if len(errmsg) < 1:
+        err = 0
+    else:
+        err = 1
+        sys.stderr.write("Error: " + errmsg + "\n\n")
+    sys.stderr.write(__doc__)
+    sys.exit(err)
+
+
+def DoxygenDependencies(doxyfile):
+    dirs = _parse_doxyfile(doxyfile)
+    files = _find_doxygen_input_files(dirs) 
+    deps  = files
+    return deps
+
+def _parse_doxyfile(doxyfile):
+    home = os.path.dirname(doxyfile)
+    filt = re.compile(r"(#.*)")
+    inp  = []
+    iext = []
+    irec = False
+    exa  = []
+    eext = []
+    erec = False
+    try:
+        df = open(doxyfile, "r")
+    except IOError as (errno, strerror):
+        sys.stderr.write("failed to open '%s': %s" %  (doxyfile, strerror))
+        exit(errno)
+
+    for line in df.readlines():
+        assign = re.sub(filt, "", line).strip().split('=')
+        if len(assign) != 2:
+            continue
+        key = assign[0].strip().upper()
+        value = assign[1].strip()
+
+        if len(value) < 1:
+            continue
+
+        if key == 'INPUT':
+            inp += value.split(' ')
+        elif key == 'FILE_PATTERNS':
+            iext += value.split(' ')
+        elif key == 'RECURSIVE':
+            if value == 'YES':
+                irec = True
+        elif key == 'EXAMPLE_PATH':
+            exa += value.split(' ')
+        elif key == 'EXAMPLE_PATTERNS':
+            iext += value.split(' ')
+        elif key == 'EXAMPLE_RECURSIVE':
+            if value == 'YES':
+                recurs = True
+                    
+    df.close()
+
+    dirs = []
+    if len(iext) < 1:
+        iext = ['*']
+    for i in inp:
+        if not i.startswith('/'):
+            i = os.path.join(home, i)
+        dirs.append((os.path.abspath(i), iext, irec))
+    if len(eext) < 1:
+        eext = ['*']
+    for e in exa:
+        if not e.startswith('/'):
+            e = os.path.join(home, e)
+        dirs.append((os.path.abspath(e), eext, erec))
+
+    return dirs
+
+def _find_doxygen_input_files(dirs, includes=[]):
+    allfile = set()
+    for d in dirs:
+        includes.append(os.path.dirname(d[0]))
+    for path, exts, recurse in dirs:
+        if os.path.isfile(path):
+            allfile.add(path)
+            allfile = allfile.union(_find_doxygen_dependencies(path, includes))
+        if not os.path.isdir(path):
+            continue
+        for e in exts:
+            matches = glob.glob(os.path.join(path, e)) 
+            allfile = allfile.union(matches)
+            for m in matches:
+                allfile = allfile.union(_find_doxygen_dependencies(m,includes))
+        if recurse:
+            for d in os.dirlist(path):
+                if os.path.isdir(d):
+                    allfile.union(_find_doxygen_input_files((d,exts,recurse),
+                                                              includes))
+    files = []
+    for f in allfile:
+        if len(f) > 0 and \
+           not f.startswith('/usr/include/') and \
+           not f.startswith('/usr/lib/'):
+            files.append(f)
+    files.sort()
+    return files
+
+def _find_doxygen_dependencies(path, includes):
+    fnam = os.path.basename(path)
+    depgen = "doxygen_%s_dependencies" % fnam[fnam.rfind('.'):][1:]
+    if depgen in globals():
+        return globals()[depgen](path, includes)
+    return set()
+
+def doxygen_c_dependencies(fnam, includes):
+    options = " ".join(list("-I%s" % f for f in includes)) + " -M"
+    cmd = "%s %s %s" % (c_compiler, options, fnam)
+    status, cdeps = commands.getstatusoutput(cmd)
+    if status == 0:
+        deps = re.sub(r" +", " ",
+               re.sub(r"\\\n", "",
+               re.sub(r"^.*\.o: ", "", cdeps)))
+        return set(deps.split(" "))
+    return set()
+
+doxygen_h_dependencies = doxygen_c_dependencies
+
+
+if __name__ == '__main__':
+    _main()
diff --git a/githooks/commit-msg b/githooks/commit-msg
new file mode 100755 (executable)
index 0000000..8836d79
--- /dev/null
@@ -0,0 +1,206 @@
+#!/bin/sh
+# From Gerrit Code Review 2.6.1.20130902
+#
+# Part of Gerrit Code Review (http://code.google.com/p/gerrit/)
+#
+# Copyright (C) 2009 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+unset GREP_OPTIONS
+
+CHANGE_ID_AFTER="Bug|Issue"
+MSG="$1"
+
+# Check for, and add if missing, a unique Change-Id
+#
+add_ChangeId() {
+       clean_message=`sed -e '
+               /^diff --git a\/.*/{
+                       s///
+                       q
+               }
+               /^Signed-off-by:/d
+               /^#/d
+       ' "$MSG" | git stripspace`
+       if test -z "$clean_message"
+       then
+               return
+       fi
+
+       # Does Change-Id: already exist? if so, exit (no change).
+       if grep -i '^Change-Id:' "$MSG" >/dev/null
+       then
+               return
+       fi
+
+       id=`_gen_ChangeId`
+       T="$MSG.tmp.$$"
+       AWK=awk
+       if [ -x /usr/xpg4/bin/awk ]; then
+               # Solaris AWK is just too broken
+               AWK=/usr/xpg4/bin/awk
+       fi
+
+       # How this works:
+       # - parse the commit message as (textLine+ blankLine*)*
+       # - assume textLine+ to be a footer until proven otherwise
+       # - exception: the first block is not footer (as it is the title)
+       # - read textLine+ into a variable
+       # - then count blankLines
+       # - once the next textLine appears, print textLine+ blankLine* as these
+       #   aren't footer
+       # - in END, the last textLine+ block is available for footer parsing
+       $AWK '
+       BEGIN {
+               # while we start with the assumption that textLine+
+               # is a footer, the first block is not.
+               isFooter = 0
+               footerComment = 0
+               blankLines = 0
+       }
+
+       # Skip lines starting with "#" without any spaces before it.
+       /^#/ { next }
+
+       # Skip the line starting with the diff command and everything after it,
+       # up to the end of the file, assuming it is only patch data.
+       # If more than one line before the diff was empty, strip all but one.
+       /^diff --git a/ {
+               blankLines = 0
+               while (getline) { }
+               next
+       }
+
+       # Count blank lines outside footer comments
+       /^$/ && (footerComment == 0) {
+               blankLines++
+               next
+       }
+
+       # Catch footer comment
+       /^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) {
+               footerComment = 1
+       }
+
+       /]$/ && (footerComment == 1) {
+               footerComment = 2
+       }
+
+       # We have a non-blank line after blank lines. Handle this.
+       (blankLines > 0) {
+               print lines
+               for (i = 0; i < blankLines; i++) {
+                       print ""
+               }
+
+               lines = ""
+               blankLines = 0
+               isFooter = 1
+               footerComment = 0
+       }
+
+       # Detect that the current block is not the footer
+       (footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) {
+               isFooter = 0
+       }
+
+       {
+               # We need this information about the current last comment line
+               if (footerComment == 2) {
+                       footerComment = 0
+               }
+               if (lines != "") {
+                       lines = lines "\n";
+               }
+               lines = lines $0
+       }
+
+       # Footer handling:
+       # If the last block is considered a footer, splice in the Change-Id at the
+       # right place.
+       # Look for the right place to inject Change-Id by considering
+       # CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first,
+       # then Change-Id, then everything else (eg. Signed-off-by:).
+       #
+       # Otherwise just print the last block, a new line and the Change-Id as a
+       # block of its own.
+       END {
+               unprinted = 1
+               if (isFooter == 0) {
+                       print lines "\n"
+                       lines = ""
+               }
+               changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):"
+               numlines = split(lines, footer, "\n")
+               for (line = 1; line <= numlines; line++) {
+                       if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) {
+                               unprinted = 0
+                               print "Change-Id: I'"$id"'"
+                       }
+                       print footer[line]
+               }
+               if (unprinted) {
+                       print "Change-Id: I'"$id"'"
+               }
+       }' "$MSG" > "$T" && mv "$T" "$MSG" || rm -f "$T"
+}
+_gen_ChangeIdInput() {
+       echo "tree `git write-tree`"
+       if parent=`git rev-parse "HEAD^0" 2>/dev/null`
+       then
+               echo "parent $parent"
+       fi
+       echo "author `git var GIT_AUTHOR_IDENT`"
+       echo "committer `git var GIT_COMMITTER_IDENT`"
+       echo
+       printf '%s' "$clean_message"
+}
+_gen_ChangeId() {
+       _gen_ChangeIdInput |
+       git hash-object -t commit --stdin
+}
+
+
+del_ChangeId() {
+    T="$MSG.tmp.$$"
+
+    cat "$MSG" | grep -v 'Change-Id: I' > "$T" && mv "$T" "$MSG" || rm -f "$T"
+}
+
+
+gittop=$(while [ ! -d .git -a $(pwd) != "/" ]; do cd ..; done; echo $(pwd))
+
+if [ -f $gittop/.rebase-branch-name ]; then
+    branch=$(cat $gittop/.rebase-branch-name)
+    echo "Taken branch name \"$branch\" from saved branch name file..."
+else
+    branch=$(git branch -l | grep '^\*' | cut -d ' ' -f 2)
+    echo "Using current branch name..."
+fi
+
+case $branch in
+    \(*|-)
+        echo "Can't figure out rebased branch name, not touching anything..."
+        ;;
+    *tizen*)
+        add_ChangeId
+        ;;
+    *)
+        del_ChangeId
+        ;;
+esac
+
+
+exit 0
diff --git a/githooks/post-rewrite b/githooks/post-rewrite
new file mode 100755 (executable)
index 0000000..a23bc51
--- /dev/null
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+gittop=$(while [ ! -d .git -a $(pwd) != "/" ]; do cd ..; done; echo $(pwd))
+
+if [ -f $gittop/.rebase-branch-name ]; then
+    rm -f $gittop/.rebase-branch-name
+    echo "Removed saved branch name file..."
+fi
+
+exit 0
diff --git a/githooks/pre-commit b/githooks/pre-commit
new file mode 100755 (executable)
index 0000000..df133c5
--- /dev/null
@@ -0,0 +1,118 @@
+#!/bin/bash
+#
+# This is a modified version of the stock git sample pre-commit hook.
+# In addition to the stock whitespace error checks, this one will also
+# reject any commits that try to insert TABs to *.c or *.h files.
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+       against=HEAD
+else
+       # Initial commit: diff against an empty tree object
+       against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+# If you want to allow non-ascii filenames set this variable to true.
+allownonascii=$(git config hooks.allownonascii)
+
+# Redirect output to stderr.
+exec 1>&2
+
+# Cross platform projects tend to avoid non-ascii filenames; prevent
+# them from being added to the repository. We exploit the fact that the
+# printable range starts at the space character and ends with tilde.
+if [ "$allownonascii" != "true" ] &&
+       # Note that the use of brackets around a tr range is ok here, (it's
+       # even required, for portability to Solaris 10's /usr/bin/tr), since
+       # the square bracket bytes happen to fall in the designated range.
+       test $(git diff --cached --name-only --diff-filter=A -z $against |
+         LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0
+then
+       echo "Error: Attempt to add a non-ascii file name."
+       echo
+       echo "This can cause problems if you want to work"
+       echo "with people on other platforms."
+       echo
+       echo "To be portable it is advisable to rename the file ..."
+       echo
+       echo "If you know what you are doing you can disable this"
+       echo "check using:"
+       echo
+       echo "  git config hooks.allownonascii true"
+       echo
+       exit 1
+fi
+
+# If there are whitespace errors, print the offending file names and fail.
+git diff-index --check --cached $against --
+status=$?
+
+if [ "$status" != "0" ]; then
+    echo ""
+    echo "WARNING:"
+    echo "WARNING: Your commit would introduce whitespace errors and was"
+    echo "WARNING: hence rejected. Please fix those errors before trying"
+    echo "WARNING: to commit again."
+    echo "WARNING:"
+    exit 1
+fi
+
+# Check if any TABS have been added to .c or .h files...
+file=""
+git diff --cached $against | \
+    while read -r line; do
+        case $line in
+            diff\ --git\ a/*)         # 1st diff line, dig out file name
+                file="${line##*b/}"
+                echo "Checking changes to $file..."
+                continue
+                ;;
+        esac
+        case $file in
+            *.h|*.c) ;;               # we process C source code,
+            *) continue;;             # and skip any other files
+        esac
+        case $line in
+            +*\        *) ;;                 # we flag insertions containing a TAB,
+            *) continue;;             # and skip all other changes
+        esac
+
+        echo "WARNING:"
+        echo "WARNING: In $file: ($line)"
+        echo "WARNING:"
+        echo "WARNING: Your commit would introduce TABS in a *.c or *.h"
+        echo "WARNING: file and was hence rejected. We prefer not to use"
+        echo "WARNING: TABS in source code to avoid TAB-size dependent"
+        echo "WARNING: incorrect indentation to sneak in. Please fix those"
+        echo "WARNING: errors before trying to commit again."
+        echo "WARNING:"
+        exit 1
+    done
+
+# Check if this commit attempts to mix changes to autogenerated files
+# files with changes to ordinary file and give the user a gentle push
+# against the idea...
+auto="`git diff --cached $against | grep ^diff | grep -e -func-info.c`"
+plain="`git diff --cached $against | grep ^diff | grep -v -e -func-info.c`"
+if [ -n "$auto" -a -n "$plain" ]; then
+    echo "WARNING:"
+    echo "WARNING: Your commit tries to mix changes to ordinary and"
+    echo "WARNING: automatically generated files. Doing so makes it"
+    echo "WARNING: more difficult to merge your changes with changes"
+    echo "WARNING: of others working in parallel with you."
+    echo "WARNING:"
+    echo "WARNING: Please consider leaving out the following files"
+    echo "WARNING: from this commit and separating changes to them"
+    echo "WARNING: to a subsequent commit of its own:"
+    echo "WARNING:"
+    git diff --cached $against | grep ^diff | grep func-info.c | \
+        sed 's#^diff .* b/#WARNING:     #g'
+    echo "WARNING:"
+    echo "WARNING: In case you really need to commit all of these"
+    echo "WARNING: changes together you can disable this check by"
+    echo "WARNING: passing the -n option to 'git commit'."
+
+    exit 1
+fi
+
+exit $?
diff --git a/githooks/pre-rebase b/githooks/pre-rebase
new file mode 100755 (executable)
index 0000000..1fe53bf
--- /dev/null
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+# This hook is called with the following parameters:
+#
+# $1 -- the upstream the series was forked from.
+# $2 -- the branch being rebased (or empty when rebasing the current branch).
+
+# Dig out and save the name of the branch being rebased for commit-msg hook.
+# There the branch name is used to add gerrit Change-Id footers to branches
+# matching .*tizen.* and remove from any other branches.
+
+branch=$(git branch -l | grep '^\*' | cut -d ' ' -f 2)
+gittop=$(while [ ! -d .git -a $(pwd) != "/" ]; do cd ..; done; echo $(pwd))
+
+case $branch in
+    \(*)
+        echo "-" > $gittop/.rebase-branch-name
+        ;;
+      *)
+        echo "$branch"   > $gittop/.rebase-branch-name
+        echo "Saved branch name \"$branch\" for commit-msg hook..."
+        ;;
+esac
+
+exit 0
diff --git a/m4/docsetup.m4 b/m4/docsetup.m4
new file mode 100644 (file)
index 0000000..7af4381
--- /dev/null
@@ -0,0 +1,22 @@
+dnl Initiate the documentation directories
+dnl
+dnl MRP_DOCINIT([depdir],[doxyfilename],[docdir])
+dnl
+
+AC_DEFUN([MRP_DOCINIT],
+[
+  m4_ifset([$1], [depdir=$1], [depdir=.deps]) 
+  m4_ifset([$2], [doxyfile=$1], [doxyfile=Doxyfile])
+  m4_ifset([$3], [docdir=$2], [docdir=doc])
+
+  AC_PATH_TOOL( [MRP_FIND], find )
+  AC_PROG_SED
+  AC_PROG_MKDIR_P
+
+  AS_IF( [ test "x$MRP_FIND" = "x" -o "x$MKDIR_P" = "x" ],
+         [ AC_MSG_ERROR([essential programs are missing to init docs]) ],
+         [ found=`$MRP_FIND $docdir -name $doxyfile` 
+           for f in $found ; do
+               $MKDIR_P `echo $f | $SED -e "s/$doxyfile//"`$depdir
+           done])
+])
diff --git a/m4/shave.m4 b/m4/shave.m4
new file mode 100644 (file)
index 0000000..94aec1f
--- /dev/null
@@ -0,0 +1,113 @@
+dnl Make automake/libtool output more friendly to humans
+dnl
+dnl Copyright (c) 2009, Damien Lespiau <damien.lespiau@gmail.com>
+dnl
+dnl Permission is hereby granted, free of charge, to any person
+dnl obtaining a copy of this software and associated documentation
+dnl files (the "Software"), to deal in the Software without
+dnl restriction, including without limitation the rights to use,
+dnl copy, modify, merge, publish, distribute, sublicense, and/or sell
+dnl copies of the Software, and to permit persons to whom the
+dnl Software is furnished to do so, subject to the following
+dnl conditions:
+dnl
+dnl The above copyright notice and this permission notice shall be
+dnl included in all copies or substantial portions of the Software.
+dnl
+dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+dnl EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+dnl OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+dnl NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+dnl HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+dnl WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+dnl FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+dnl OTHER DEALINGS IN THE SOFTWARE.
+dnl
+dnl SHAVE_INIT([shavedir],[default_mode])
+dnl
+dnl shavedir: the directory where the shave scripts are, it defaults to
+dnl           $(top_builddir)
+dnl default_mode: (enable|disable) default shave mode.  This parameter
+dnl               controls shave's behaviour when no option has been
+dnl               given to configure.  It defaults to disable.
+dnl
+dnl * SHAVE_INIT should be called late in your configure.(ac|in) file (just
+dnl   before AC_CONFIG_FILE/AC_OUTPUT is perfect.  This macro rewrites CC and
+dnl   LIBTOOL, you don't want the configure tests to have these variables
+dnl   re-defined.
+dnl * This macro requires GNU make's -s option.
+
+AC_DEFUN([_SHAVE_ARG_ENABLE],
+[
+  AC_ARG_ENABLE([shave],
+    AS_HELP_STRING(
+      [--enable-shave],
+      [use shave to make the build pretty [[default=$1]]]),,
+      [enable_shave=$1]
+    )
+])
+
+AC_DEFUN([SHAVE_INIT],
+[
+  dnl you can tweak the default value of enable_shave
+  m4_if([$2], [enable], [_SHAVE_ARG_ENABLE(yes)], [_SHAVE_ARG_ENABLE(no)])
+
+  if test x"$enable_shave" = xyes; then
+    dnl where can we find the shave scripts?
+    m4_if([$1],,
+      [shavedir="$ac_pwd"],
+      [shavedir="$ac_pwd/$1"])
+    AC_SUBST(shavedir)
+
+    dnl make is now quiet
+    AC_SUBST([MAKEFLAGS], [-s])
+    AC_SUBST([AM_MAKEFLAGS], ['`test -z $V && echo -s`'])
+
+    dnl we need sed
+    AC_CHECK_PROG(SED,sed,sed,false)
+
+    dnl substitute libtool
+    SHAVE_SAVED_LIBTOOL=$LIBTOOL
+    LIBTOOL="${SHELL} ${shavedir}/shave-libtool '${SHAVE_SAVED_LIBTOOL}'"
+    AC_SUBST(LIBTOOL)
+
+    dnl substitute cc/cxx
+    SHAVE_SAVED_CCAS=$CCAS
+    SHAVE_SAVED_CC=$CC
+    SHAVE_SAVED_CXX=$CXX
+    SHAVE_SAVED_FC=$FC
+    SHAVE_SAVED_F77=$F77
+    SHAVE_SAVED_OBJC=$OBJC
+    SHAVE_SAVED_MCS=$MCS
+    SHAVE_SAVED_LEX=$LEX
+    SHAVE_SAVED_YACC=$YACC
+    SHAVE_SAVED_CC_FOR_BUILD=$CC_FOR_BUILD
+    CCAS="${SHELL} ${shavedir}/shave ccas ${SHAVE_SAVED_CCAS}"
+    CC="${SHELL} ${shavedir}/shave cc ${SHAVE_SAVED_CC}"
+    CXX="${SHELL} ${shavedir}/shave cxx ${SHAVE_SAVED_CXX}"
+    FC="${SHELL} ${shavedir}/shave fc ${SHAVE_SAVED_FC}"
+    F77="${SHELL} ${shavedir}/shave f77 ${SHAVE_SAVED_F77}"
+    OBJC="${SHELL} ${shavedir}/shave objc ${SHAVE_SAVED_OBJC}"
+    MCS="${SHELL} ${shavedir}/shave mcs ${SHAVE_SAVED_MCS}"
+    LEX="${SHELL} ${shavedir}/shave lex ${SHAVE_SAVED_LEX}"
+    YACC="${SHELL} ${shavedir}/shave yacc ${SHAVE_SAVED_YACC}"
+    CC_FOR_BUILD="${SHELL} ${shavedir}/shave cc_for_build ${SHAVE_SAVED_CC_FOR_BUILD}"
+    AC_SUBST(CCAS)
+    AC_SUBST(CC)
+    AC_SUBST(CXX)
+    AC_SUBST(FC)
+    AC_SUBST(F77)
+    AC_SUBST(OBJC)
+    AC_SUBST(MCS)
+    AC_SUBST(LEX)
+    AC_SUBST(YACC)
+
+    V=@
+  else
+    V=1
+  fi
+  Q='$(V:1=)'
+  AC_SUBST(V)
+  AC_SUBST(Q)
+])
+
diff --git a/m4/websockets.m4 b/m4/websockets.m4
new file mode 100644 (file)
index 0000000..ab1a41d
--- /dev/null
@@ -0,0 +1,234 @@
+# Macro to check if websockets support was enabled. This macro also
+# takes care of detecting older versions of libwebsockets (lacking
+# pkg-config support, no per-context userdata) and propagating this
+# information to config.h and the compilation process.
+#
+
+AC_DEFUN([CHECK_WEBSOCKETS],
+[
+AC_LANG_PUSH([C])
+AC_ARG_ENABLE(websockets,
+              [  --enable-websockets     enable websockets support],
+              [enable_websockets=$enableval], [enable_websockets=auto])
+
+# Check if we have properly packaged libwebsockets (json-c is now mandatory
+# and already has been tested for).
+if test "$enable_websockets" != "no"; then
+    PKG_CHECK_MODULES(WEBSOCKETS, [libwebsockets],
+                      [have_websockets=yes], [have_websockets=no])
+    if test "$have_websockets" = "yes"; then
+        WEBSOCKETS_CFLAGS="`pkg-config --cflags libwebsockets`"
+        # Check for a couple of recent features we need to adopt to.
+        saved_CFLAGS="$CFLAGS"
+        saved_LDFLAGS="$LDFLAGS"
+        saved_LIBS="$LIBS"
+        # Note that (at least with autoconf 2.69 and gcc 4.7.2), setting
+        # LD_AS_NEEDED to 1 breaks AC_LINK_IFELSE. That macro generates
+        # the compilation command so that the libraries are specified
+        # before the generated C source so all referenced/tested symbols
+        # from any of the libraries end up being undefined. This fools
+        # AC_LINK_IFELSE to consider the test a failure and select the
+        # else branch.
+        # rpmbuild always sets LD_AS_NEEDED to 1. To work around this save
+        # and restore LD_AS_NEEDED for the duration of the AC_LINK_IFELSE
+        # tests.
+        saved_LD_AS_NEEDED="$LD_AS_NEEDED"
+        unset LD_AS_NEEDED
+        CFLAGS="`pkg-config --cflags libwebsockets`"
+        LIBS="`pkg-config --libs libwebsockets`"
+
+        # Check for new context creation API.
+        AC_MSG_CHECKING([for WEBSOCKETS new context creation API])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[struct libwebsocket_context *ctx;
+                   ctx = libwebsocket_create_context(NULL);]])],
+            [websockets_cci=yes],
+            [websockets_cci=no])
+        AC_MSG_RESULT([$websockets_cci])
+
+        # Check for new libwebsockets_get_internal_extensions.
+        AC_MSG_CHECKING([for WEBSOCKETS internal extension query API])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[struct libwebsocket_extension *ext;
+                   ext = libwebsocket_get_internal_extensions();]])],
+            [websockets_query_ext=yes],
+            [websockets_query_ext=no])
+        AC_MSG_RESULT([$websockets_query_ext])
+
+        # Check for newer lws_set_log_level API.
+        # Note that we cheat heavily here: instead of rolling a proper
+        # test, we blindly assume gcc, turn on the -Werror flag (to catch
+        # calls with a mismatching function pointer) and hope that we will
+        # not get false negatives because of other warnings.
+        no_werror_CFLAGS="$CFLAGS"
+        CFLAGS="$CFLAGS -Werror"
+        AC_MSG_CHECKING([for WEBSOCKETS updated logging API.])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>
+                   static void logger(int level, const char *line) {
+                       return;
+                   }]],
+                 [[lws_set_log_level(LLL_INFO, logger);]])],
+            [websockets_log_with_level=yes],
+            [websockets_log_with_level=no])
+        AC_MSG_RESULT([$websockets_log_with_level])
+
+        # Check whether we have libwebsocket_close_and_free_session.
+        AC_MSG_CHECKING([for WEBSOCKETS close_and_free_session API])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[libwebsocket_close_and_free_session(NULL, NULL, 0);]])],
+            [websockets_close_session=yes],
+            [websockets_close_session=no])
+        AC_MSG_RESULT([$websockets_close_session])
+
+        # Check for LWS_CALLBACK_FILTER_HTTP_CONNECTION.
+        AC_MSG_CHECKING([for WEBSOCKETS LWS_CALLBACK_FILTER_HTTP_CONNECTION])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[int foo = LWS_CALLBACK_FILTER_HTTP_CONNECTION;]])],
+            [websockets_filter_http_connection=yes],
+            [websockets_filter_http_connection=no])
+        AC_MSG_RESULT([$websockets_filter_http_connection])
+
+        # Check for LWS_CALLBACK_CHANGE_MODE_POLL_FD.
+        AC_MSG_CHECKING([for WEBSOCKETS LWS_CALLBACK_CHANGE_MODE_POLL_FD])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[int foo = LWS_CALLBACK_CHANGE_MODE_POLL_FD;]])],
+            [websockets_change_poll=yes],
+            [websockets_change_poll=no])
+        AC_MSG_RESULT([$websockets_change_poll])
+
+        # Check the signature of libwebsockets_serve_http_file to see if
+        # it takes an extra (other_headers) argument.
+        AC_MSG_CHECKING([for WEBSOCKETS libwebsockets_serve_http_file other_headers argument])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[return libwebsockets_serve_http_file(NULL, NULL, "", "", "");]])],
+            [websockets_serve_file_extraarg=yes],
+            [websockets_serve_file_extraarg=no])
+        AC_MSG_RESULT([$websockets_serve_file_extraarg])
+
+        CFLAGS="$saved_CFLAGS"
+        LDFLAGS="$saved_LDFLAGS"
+        LIBS="$saved_LIBS"
+        if test -n "$saved_LD_AS_NEEDED"; then
+            export LD_AS_NEEDED="$saved_LD_AS_NEEDED"
+        fi
+    else
+        WEBSOCKETS_CFLAGS=""
+    fi
+
+    # Check for older websockets.
+    if test "$have_websockets" = "no"; then
+        saved_LDFLAGS="$LDFLAGS"
+        saved_LIBS="$LIBS"
+        LIBS="-lwebsockets"
+        AC_MSG_CHECKING([for WEBSOCKETS without pkg-config support])
+        AC_LINK_IFELSE(
+           [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[struct libwebsocket_context *ctx;
+                   ctx = libwebsocket_create_context(0, NULL, NULL, NULL,
+                                                     NULL, NULL, NULL,
+                                                     -1, -1, 0, NULL);]])],
+            [have_websockets=yes;old_websockets=no],
+            [have_websockets=no])
+        AC_MSG_RESULT([$have_websockets])
+    fi
+
+    # Check if we have a really old libwebsockets, still without
+    # per-context user data.
+    if test "$old_websockets" != "no"; then
+        AC_MSG_CHECKING([for really old WEBSOCKETS])
+        AC_LINK_IFELSE(
+            [AC_LANG_PROGRAM(
+                 [[#include <stdlib.h>
+                   #include <libwebsockets.h>]],
+                 [[struct libwebsocket_context *ctx;
+                   ctx = libwebsocket_create_context(0, NULL, NULL, NULL,
+                                                     NULL, NULL,
+                                                     -1, -1, 0);]])],
+            [have_websockets=yes;old_websockets=yes],
+            [old_websockets=no])
+        AC_MSG_RESULT([$old_websockets])
+    fi
+
+    WEBSOCKETS_LIBS="-lwebsockets"
+    if test "$old_websockets" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_OLD"
+    fi
+    if test "$websockets_cci" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_CONTEXT_INFO"
+    fi
+    if test "$websockets_query_ext" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_QUERY_EXTENSIONS"
+    fi
+    if test "$websockets_log_with_level" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_LOG_WITH_LEVEL"
+    fi
+    if test "$websockets_close_session" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_CLOSE_SESSION"
+    fi
+    if test "$websockets_filter_http_connection" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_FILTER_HTTP_CONNECTION"
+    fi
+    if test "$websockets_change_poll" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_CHANGE_MODE_POLL_FD"
+    fi
+    if test "$websockets_serve_file_extraarg" = "yes"; then
+        WEBSOCKETS_CFLAGS="$WEBSOCKETS_CFLAGS -DWEBSOCKETS_SERVE_FILE_EXTRAARG"
+    fi
+
+    LDFLAGS="$saved_LDFLAGS"
+    LIBS="$saved_LIBS"
+else
+    AC_MSG_NOTICE([libwebsockets support is disabled.])
+fi
+
+# Bail out if we lack mandatory support.
+if test "$enable_websockets" = "yes" -a "$have_websockets" = "no"; then
+    AC_MSG_ERROR([libwebsockets development libraries not found.])
+fi
+
+# Enable if found and autosupport requested.
+if test "$enable_websockets" = "auto"; then
+    enable_websockets=$have_websockets
+fi
+
+# If enabled, set up our autoconf variables accordingly.
+if test "$enable_websockets" = "yes"; then
+    AC_DEFINE([WEBSOCKETS_ENABLED], 1, [Enable websockets support ?])
+    if test "$old_websockets" = "yes"; then
+        AC_DEFINE([WEBSOCKETS_OLD], 1, [No per-context userdata ?])
+    fi
+fi
+
+# Finally substitute everything.
+AM_CONDITIONAL(WEBSOCKETS_ENABLED, [test "$enable_websockets" = "yes"])
+AM_CONDITIONAL(WEBSOCKETS_OLD, [test "$old_websockets" = "yes"])
+AC_SUBST(WEBSOCKETS_ENABLED)
+AC_SUBST(WEBSOCKETS_CFLAGS)
+AC_SUBST(WEBSOCKETS_LIBS)
+AC_SUBST(WEBSOCKETS_OLD)
+
+AC_LANG_POP
+])
diff --git a/packaging.in/murphy-lua.conf b/packaging.in/murphy-lua.conf
new file mode 100644 (file)
index 0000000..a348679
--- /dev/null
@@ -0,0 +1 @@
+load-plugin lua config="/etc/murphy/murphy.lua"
diff --git a/packaging.in/murphy-wait-for-launchpad-ready.path b/packaging.in/murphy-wait-for-launchpad-ready.path
new file mode 100644 (file)
index 0000000..d943911
--- /dev/null
@@ -0,0 +1,3 @@
+[Path]
+PathExists=/run/user/%U/wayland-0
+Unit=ico-homescreen.service
diff --git a/packaging.in/murphy.lua b/packaging.in/murphy.lua
new file mode 100644 (file)
index 0000000..b625d1f
--- /dev/null
@@ -0,0 +1,2385 @@
+with_system_controller = false
+with_amb = false
+verbose = 0
+
+m = murphy.get()
+
+-- try loading the various logging plugins
+m:try_load_plugin('systemd')
+m:try_load_plugin('dlog')
+
+-- load the console plugin
+m:try_load_plugin('console')
+
+m:try_load_plugin('console.disabled', 'webconsole', {
+                  address = 'wsck:127.0.0.1:3000/murphy',
+                  httpdir = '/usr/share/murphy/webconsole' });
+
+-- load the dbus plugin
+if m:plugin_exists('dbus') then
+    m:load_plugin('dbus')
+end
+
+-- load the native resource plugin
+if m:plugin_exists('resource-native') then
+    m:load_plugin('resource-native')
+    m:info("native resource plugin loaded")
+else
+    m:info("No native resource plugin found...")
+end
+
+-- load the dbus resource plugin
+m:try_load_plugin('resource-dbus', {
+    dbus_bus = "system",
+    dbus_service = "org.Murphy",
+    dbus_track = true,
+    default_zone = "driver",
+    default_class = "implicit"
+})
+
+-- load the domain control plugin
+if m:plugin_exists('domain-control') then
+    m:load_plugin('domain-control')
+else
+    m:info("No domain-control plugin found...")
+end
+
+if m:plugin_exists('glib') then
+    m:load_plugin('glib')
+else
+    m:info("No glib plugin found...")
+end
+
+if m:plugin_exists("gam-resource-manager") then
+
+function get_general_priorities(self)
+    print("*** get_general_priorities\n")
+    return { "USB Headset", "wiredHeadset", "speakers" }
+end
+
+function get_phone_priorities(self)
+    print("*** get_phone_priorities\n")
+    return { "wiredHeadset", "USB Headset" }
+end
+
+m:load_plugin('gam-resource-manager', {
+    config_dir = '/etc/murphy/gam',
+    decision_names = 'gam-wrtApplication-4',
+    max_active = 4,
+    app_mapping = {
+        ['t8j6HTRpuz.MediaPlayer'] = 'wrtApplication',
+        ['pacat'] = 'icoApplication'
+    },
+    app_default = 'icoApplication'
+})
+
+routing_sink_priority {
+    application_class = "player",
+    priority_queue = get_general_priorities
+}
+
+routing_sink_priority {
+    application_class = "game",
+    priority_queue = get_general_priorities
+}
+
+routing_sink_priority {
+    application_class = "implicit",
+    priority_queue = get_general_priorities
+}
+
+routing_sink_priority {
+    application_class = "phone",
+    priority_queue = get_phone_priorities
+}
+
+routing_sink_priority {
+    application_class = "basic",
+    priority_queue = get_phone_priorities
+}
+
+routing_sink_priority {
+    application_class = "event",
+    priority_queue = get_phone_priorities
+}
+end
+
+-- load the AMB plugin
+if m:plugin_exists('amb') then
+    m:try_load_plugin('amb')
+
+    if builtin.method.amb_initiate and
+       builtin.method.amb_update
+    then
+        with_amb = true
+    end
+else
+    m:info("No amb plugin found...")
+end
+
+-- load the ASM resource plugin
+if m:plugin_exists('resource-asm') then
+    m:try_load_plugin('resource-asm', {
+        zone = "driver",
+        share_mmplayer = "player:AVP,mandatory,exclusive,strict",
+        ignored_argv0 = "WebProcess"
+    })
+else
+    m:info("No audio session manager plugin found...")
+end
+
+if m:plugin_exists('system-controller') then
+    with_system_controller = true
+elseif m:plugin_exists('ivi-resource-manager') then
+    m:load_plugin('ivi-resource-manager')
+    with_system_controller = false
+end
+
+-- define application classes
+application_class {
+    name     = "interrupt",
+    priority = 99,
+    modal    = true ,
+    share    = false,
+    order    = "fifo"
+}
+
+application_class {
+    name     = "emergency",
+    priority = 80,
+    modal    = false,
+    share    = false,
+    order    = "fifo"
+}
+application_class {
+    name     = "system",
+    priority = 52,
+    modal    = false,
+    share    = true,
+    order    = "lifo"
+}
+application_class {
+    name     = "alert",
+    priority = 51,
+    modal    = false,
+    share    = false,
+    order    = "fifo"
+}
+
+application_class {
+    name     = "navigator",
+    priority = 50,
+    modal    = false,
+    share    = true,
+    order    = "fifo"
+}
+
+application_class {
+    name     = "phone",
+    priority = 6 ,
+    modal    = false,
+    share    = false,
+    order    = "lifo"
+}
+application_class {
+    name     = "camera",
+    priority = 5,
+    modal    = false,
+    share    = false,
+    order    = "lifo"
+}
+
+application_class { name="event"    , priority=4 , modal=false, share=true , order="fifo" }
+application_class { name="game"     , priority=3 , modal=false, share=false, order="lifo" }
+--# doesn't need to be created here, ivi-resource-manager creates it if loaded
+--#application_class { name="basic"    , priority=2 , modal=false, share=false, order="lifo" }
+application_class { name="player"   , priority=1 , modal=false, share=true , order="lifo" }
+application_class { name="implicit" , priority=0 , modal=false, share=false, order="lifo" }
+
+-- define zone attributes
+zone.attributes {
+    type = {mdb.string, "common", "rw"},
+    location = {mdb.string, "anywhere", "rw"}
+}
+
+-- define zones
+zone {
+     name = "driver",
+     attributes = {
+         type = "common",
+         location = "front-left"
+     }
+}
+
+zone {
+     name = "passanger1",
+     attributes = {
+         type = "private",
+         location = "front-right"
+     }
+}
+
+zone {
+     name = "passanger2",
+     attributes = {
+         type = "private",
+         location = "back-left"
+     }
+}
+
+zone {
+     name = "passanger3",
+     attributes = {
+         type = "private",
+         location = "back-right"
+     }
+}
+
+zone {
+     name = "passanger4",
+     attributes = {
+         type = "private",
+         location = "back-left"
+     }
+}
+
+
+-- define resource classes
+if not m:plugin_exists('ivi-resource-manager') and
+   not with_system_controller and
+   not m:plugin_exists('gam-resource-manager')
+then
+   resource.class {
+        name = "audio_playback",
+        shareable = true,
+        attributes = {
+            role    = { mdb.string, "music", "rw" },
+            pid     = { mdb.string, "<unknown>", "rw" },
+            policy  = { mdb.string, "relaxed", "rw" },
+            source  = { mdb.string, "webkit", "rw" },
+            conn_id = { mdb.unsigned, 0, "rw" },
+            name    = { mdb.string, "<unknown>", "rw" },
+        }
+   }
+end
+
+if not m:plugin_exists('gam-resource-manager') then
+    resource.class {
+        name = "audio_recording",
+        shareable = true,
+        attributes = {
+             role   = { mdb.string, "music"    , "rw" },
+             pid    = { mdb.string, "<unknown>", "rw" },
+             policy = { mdb.string, "relaxed"  , "rw" },
+             name   = { mdb.string, "<unknown>", "rw" },
+         }
+    }
+end
+
+resource.class {
+     name = "video_playback",
+     shareable = false,
+}
+
+resource.class {
+     name = "video_recording",
+     shareable = false
+}
+
+resource.class {
+     name = "speech_recognition",
+     shareable = true
+}
+
+resource.class {
+     name = "speech_synthesis",
+     shareable = true
+}
+
+-- PulseAudio volume context
+mdb.table {
+    name = "volume_context",
+    index = { "id" },
+    create = true,
+    columns = {
+        { "id", mdb.unsigned },
+        { "value", mdb.string, 64 },
+    }
+}
+
+-- put default volume context to the table
+mdb.table.volume_context:insert({ id = 1, value = "default" })
+
+if not m:plugin_exists('ivi-resource-manager') and
+   not with_system_controller
+then
+    resource.method.veto = {
+        function(zone, rset, grant, owners, req_set)
+            return true
+         end
+    }
+end
+
+-- test for creating selections
+mdb.select {
+           name = "audio_owner",
+           table = "audio_playback_owner",
+           columns = {"application_class"},
+           condition = "zone_name = 'driver'"
+}
+
+mdb.select {
+           name = "vehicle_speed",
+           table = "amb_vehicle_speed",
+           columns = {"value"},
+           condition = "key = 'VehicleSpeed'"
+}
+
+element.lua {
+   name    = "speed2volume",
+   inputs  = { speed = mdb.select.vehicle_speed, param = 9 },
+   outputs = {  mdb.table { name = "speedvol",
+                            index = {"zone", "device"},
+                            columns = {{"zone", mdb.string, 16},
+                                       {"device", mdb.string, 16},
+                                       {"value", mdb.floating}},
+                            create = true
+                           }
+             },
+   oldvolume = 0.0,
+   update  = function(self)
+                speed = self.inputs.speed.single_value
+                if (speed) then
+                    volume = (speed - 144.0) / 7.0
+                else
+                    volume = 0.0
+                end
+                diff = volume - self.oldvolume
+                if (diff*diff > self.inputs.param) then
+                    print("*** element "..self.name.." update "..volume)
+                    self.oldvolume = volume
+                    mdb.table.speedvol:replace({zone = "driver", device = "speakers", value = volume})
+                end
+             end
+}
+
+mdb.select {
+    name = "amb_state",
+    table = "amb_state",
+    columns = { "state" },
+    condition = "id = 0"
+}
+
+-- Night mode processing chain
+
+mdb.select {
+    name = "exterior_brightness",
+    table = "amb_exterior_brightness",
+    columns = { "value" },
+    condition = "key = 'ExteriorBrightness'"
+}
+
+element.lua {
+    name    = "nightmode",
+    inputs  = { brightness = mdb.select.exterior_brightness },
+    oldmode = -1;
+    outputs = {
+        mdb.table {
+            name = "amb_nightmode",
+            index = { "id" },
+            create = true,
+            columns = {
+                { "id", mdb.unsigned },
+                { "night_mode", mdb.unsigned }
+            }
+        }
+    },
+    update = function(self)
+        -- This is a trivial function to calculate night mode. Later, we will
+        -- need a better threshold value and hysteresis to prevent oscillation.
+
+        brightness = self.inputs.brightness.single_value
+
+        if not brightness then
+            return
+        end
+
+        print("*** element "..self.name.." update brightness: "..brightness)
+
+        if brightness > 300 then
+            mode = 0
+        else
+            mode = 1
+        end
+
+        print("*** resulting mode: ".. mode)
+
+        if not (mode == self.oldmode) then
+            mdb.table.amb_nightmode:replace({ id = 0, night_mode = mode })
+        end
+
+        self.oldmode = mode
+    end
+}
+
+mdb.select {
+    name = "select_night_mode",
+    table = "amb_nightmode",
+    columns = { "night_mode" },
+    condition = "id = 0"
+}
+
+if with_amb then
+    sink.lua {
+        name = "night_mode",
+        inputs = { NightMode = mdb.select.select_night_mode,
+                   amb_state = mdb.select.amb_state },
+        property = "NightMode",
+        type = "b",
+        initiate = builtin.method.amb_initiate,
+        update = builtin.method.amb_update
+    }
+end
+
+-- Night mode general handlers
+
+if with_system_controller then
+    sink.lua {
+        name = "nightmode_homescreen",
+        inputs = { owner = mdb.select.select_night_mode },
+        initiate = function(self)
+                -- data = mdb.select.select_night_mode.single_value
+                return true
+            end,
+        update = function(self)
+                send_night_mode_to(homescreen)
+            end
+    }
+end
+
+-- Driving mode processing chain
+
+element.lua {
+    name    = "drivingmode",
+    inputs  = { speed = mdb.select.vehicle_speed },
+    oldmode = -1;
+    outputs = {
+        mdb.table {
+            name = "amb_drivingmode",
+            index = { "id" },
+            create = true,
+            columns = {
+                { "id", mdb.unsigned },
+                { "driving_mode", mdb.unsigned }
+            }
+        }
+    },
+    update = function(self)
+
+        speed = self.inputs.speed.single_value
+
+        if not speed then
+            return
+        end
+
+        if speed == 0 then
+            mode = 0
+        else
+            mode = 1
+        end
+
+        if not (mode == self.oldmode) then
+            mdb.table.amb_drivingmode:replace({ id = 0, driving_mode = mode })
+        end
+
+        self.oldmode = mode
+    end
+}
+
+mdb.select {
+    name = "select_driving_mode",
+    table = "amb_drivingmode",
+    columns = { "driving_mode" },
+    condition = "id = 0"
+}
+
+if with_amb then
+    sink.lua {
+        name = "driving_mode",
+        inputs = { DrivingMode = mdb.select.select_driving_mode,
+                   amb_state = mdb.select.amb_state },
+        property = "DrivingMode",
+        type = "u",
+        initiate = builtin.method.amb_initiate,
+        update = builtin.method.amb_update
+    }
+end
+
+-- turn signals (left, right)
+
+mdb.select {
+    name = "winker",
+    table = "amb_turn_signal",
+    columns = { "value" },
+    condition = "key = 'TurnSignal'"
+}
+
+-- define three categories
+
+mdb.select {
+    name = "undefined_applications",
+    table = "aul_applications",
+    columns = { "appid" },
+    condition = "category = '<undefined>'"
+}
+
+mdb.select {
+    name = "basic_applications",
+    table = "aul_applications",
+    columns = { "appid" },
+    condition = "category = 'basic'"
+}
+
+mdb.select {
+    name = "entertainment_applications",
+    table = "aul_applications",
+    columns = { "appid" },
+    condition = "category = 'entertainment'"
+}
+
+function ft(t)
+    -- filter the object garbage out of the tables
+    ret = {}
+
+    for k,v in pairs(t) do
+        if k ~= "userdata" and k ~= "new" then
+            ret[k] = v
+        end
+    end
+
+    return ret
+end
+
+function getApplication(appid)
+    local conf = nil
+
+    -- find the correct local application definition
+
+    for k,v in pairs(ft(application)) do
+        if appid == v.appid then
+            conf = v
+            break
+        end
+    end
+
+    return conf
+end
+
+function regulateApplications(t, regulation)
+    for k,v in pairs(ft(t)) do
+
+        whitelisted = false
+
+        -- our local application config, which takes precedence
+        local conf = getApplication(v.appid)
+
+        if conf then
+            if conf.resource_class ~= "player" then
+                whitelisted = true
+            end
+
+              if conf.requisites and conf.requisites.screen then
+                if conf.requisites.screen.driving then
+                    whitelisted = true
+                end
+            end
+        end
+
+        if whitelisted then
+            -- override, don't disable
+            resmgr:disable_screen_by_appid("*", "*", v.appid, false, false)
+        else
+            resmgr:disable_screen_by_appid("*", "*", v.appid, regulation == 1, false)
+        end
+    end
+    resource.method.recalc("driver")
+end
+
+-- regulation (on), use "select_driving_mode"
+
+sink.lua {
+    name = "driving_regulation",
+    inputs = { owner = mdb.select.select_driving_mode },
+    initiate = function(self)
+        -- local data = mdb.select.select_driving_mode.single_value
+        return true
+    end,
+    update = function(self)
+        local data = mdb.select.select_driving_mode.single_value
+
+        if verbose > 1 then
+            print("Driving mode updated: " .. tostring(data))
+        end
+
+        if not sc then
+            return true
+        end
+
+        -- tell homescreen that driving mode was updated
+        send_driving_mode_to(homescreen)
+
+        regulateApplications(ft(mdb.select.entertainment_applications), data)
+        regulateApplications(ft(mdb.select.undefined_applications), data)
+
+        return true
+    end
+}
+--[[
+sink.lua {
+    name = "regulated_app_change",
+    inputs = { undef = mdb.select.undefined_applications,
+               entertainment = mdb.select.entertainment_applications },
+    initiate = function(self)
+        return true
+    end,
+    update = function(self)
+        local data = mdb.select.select_driving_mode.single_value
+
+        if not sc then
+            return
+        end
+
+        if verbose > 1 then
+            print("regulated application list was changed")
+        end
+
+        regulateApplications(ft(mdb.select.entertainment_applications), data)
+        regulateApplications(ft(mdb.select.undefined_applications), data)
+
+        return true
+    end
+}
+--]]
+
+-- shift position (parking, reverse, other)
+
+mdb.select {
+    name = "gear_position",
+    table = "amb_gear_position",
+    columns = { "value" },
+    condition = "key = 'GearPosition'"
+}
+
+-- cameras (back, front, left, right)
+
+element.lua {
+    name    = "camera_state",
+    inputs  = { winker = mdb.select.winker, gear = mdb.select.gear_position },
+    oldmode = -1;
+    outputs = {
+        mdb.table {
+            name = "target_camera_state",
+            index = { "id" },
+            create = true,
+            columns = {
+                { "id", mdb.unsigned },
+                { "front_camera", mdb.unsigned },
+                { "back_camera", mdb.unsigned },
+                { "right_camera", mdb.unsigned },
+                { "left_camera", mdb.unsigned }
+            }
+        }
+    },
+    update = function(self)
+
+        front_camera = 0
+        back_camera = 0
+        right_camera = 0
+        left_camera = 0
+
+        if self.inputs.gear == 128 then
+            back_camera = 1
+        elseif self.inputs.winker == 1 then
+            right_camera = 1
+        elseif self.inputs.winker == 2 then
+            left_camera = 1
+        end
+
+        mdb.table.target_camera_state:replace({ id = 0, front_camera = front_camera, back_camera = back_camera, right_camera = right_camera, left_camera = left_camera })
+
+    end
+}
+
+-- system controller test setup
+
+if not with_system_controller then
+   -- ok, we should have 'audio_playback' defined by now
+   m:try_load_plugin('telephony')
+   return
+end
+
+m:load_plugin('system-controller')
+
+onscreen_counter = 0
+
+window_manager_operation_names = {
+    [1] = "create",
+    [2] = "destroy"
+}
+
+function window_manager_operation_name(oper)
+    local name = window_manager_operation_names[oper]
+    if name then return name end
+    return "<unknown " .. tostring(oper) .. ">"
+end
+
+window_operation_names = {
+    [1] = "create",
+    [2] = "destroy",
+    [3] = "name_change",
+    [4] = "visible",
+    [5] = "configure",
+    [6] = "active",
+    [7] = "map",
+    [8] = "hint"
+}
+
+function window_operation_name(oper)
+    local name = window_operation_names[oper]
+    if name then return name end
+    return "<unknown " .. tostring(oper) .. ">"
+end
+
+layer_operation_names = {
+    [1] = "create",
+    [2] = "destroy",
+    [3] = "visible"
+}
+
+function layer_operation_name(oper)
+    local name = layer_operation_names[oper]
+    if name then return name end
+    return "<unknown " .. tostring(oper) .. ">"
+end
+
+input_manager_operation_names = {
+    [1] = "create",
+    [2] = "destroy",
+    [3] = "ready"
+}
+
+function input_manager_operation_name(oper)
+    local name = input_manager_operation_names[oper]
+    if name then return name end
+    return "<unknown " .. tostring(oper) .. ">"
+end
+
+input_operation_names = {
+    [1] = "create",
+    [2] = "destroy",
+    [3] = "update"
+}
+
+function input_operation_name(oper)
+    local name = input_operation_names[oper]
+    if name then return name end
+    return "<unknown " .. tostring(oper) .. ">"
+end
+
+code_operation_names = {
+    [1] = "create",
+    [2] = "destroy",
+    [3] = "state_change"
+}
+
+function code_operation_name(oper)
+    local name = code_operation_names[oper]
+    if name then return name end
+    return "<unknown " .. tostring(oper) .. ">"
+end
+
+command_names = {
+    [0x00001] = "send_appid",
+    [0x10001] = "create",
+    [0x10002] = "destroy",
+    [0x10003] = "show",
+    [0x10004] = "hide",
+    [0x10005] = "move",
+    [0x10006] = "animation",
+    [0x10007] = "change_active",
+    [0x10008] = "change_layer",
+    [0x10009] = "change_attr",
+    [0x10010] = "name",
+    [0x10020] = "map_thumb",
+    [0x10021] = "unmap_thumb",
+    [0x10022] = "map_get",
+    [0x10030] = "show layer",
+    [0x10031] = "hide_layer",
+    [0x10032] = "change_layer_attr",
+    [0x20001] = "add_input",
+    [0x20002] = "del_input",
+    [0x30001] = "change_user",
+    [0x30002] = "get_userlist",
+    [0x30003] = "get_lastinfo",
+    [0x30004] = "set_lastinfo",
+    [0x40001] = "acquire_res",
+    [0x40002] = "release_res",
+    [0x40003] = "deprive_res",
+    [0x40004] = "waiting_res",
+    [0x40005] = "revert_res",
+    [0x40006] = "window_id_res",
+    [0x40011] = "create_res",
+    [0x40012] = "destroy_res",
+    [0x50001] = "set_region",
+    [0x50002] = "unset_region",
+    [0x60001] = "change_state"
+}
+
+function command_name(command)
+    local name = command_names[command]
+    if name then return name end
+    return "<unknown " .. tostring(command) .. ">"
+end
+
+input_layer = {
+   [101] = true, -- input
+   [102] = true, -- touch
+   [103] = true  -- cursor
+}
+
+-- some day this should be merged with wmgr.layers
+ico_layer_type = {
+   [1]   = 0x1000, -- background
+   [2]   = 0x2000, -- application
+   [3]   = 0x2000, -- homescreen
+   [4]   = 0x2000, -- interrupt application
+   [5]   = 0x2000, -- onscreen application
+   [6]   = 0xc000, -- startup
+   [7]   = 0x3000, -- fullscreen
+   [101] = 0x4000, -- input
+   [102] = 0xa000, -- touch
+   [103] = 0xb000  -- cursor
+}
+
+resmgr = resource_manager {
+  screen_event_handler = function(self, ev)
+                             local event = ev.event
+                             local surface = ev.surface
+
+                             if event == "init" then
+                                 if verbose > 0 then
+                                     print("*** init screen resource allocation -- disable all 'player'")
+                                 end
+                                 resmgr:disable_audio_by_appid("*", "player", "*", true, false)
+                             elseif event == "preallocate" then
+                                 if verbose > 0 then
+                                     print("*** preallocate screen resource "..
+                                           "for '" .. ev.appid .. "' -- enable 'player', if any")
+                                 end
+                                 resmgr:disable_audio_by_appid("*", "player", ev.appid, false, false)
+                             elseif event == "grant" then
+                                 if verbose > 0 then
+                                    print("*** make visible surface "..surface)
+                                 end
+                                 local a = animation({})
+                                 local r = m:JSON({surface = surface,
+                                                   visible = 1,
+                                                   raise   = 1})
+                                 if ev.appid == onscreen then
+                                     onscreen_counter = onscreen_counter + 1
+                                     wmgr:layer_request(m:JSON({layer = 5, visible = 1}))
+                                 end
+
+                                 wmgr:window_request(r,a,0)
+                             elseif event == "revoke" then
+                                 if verbose > 0 then
+                                    print("*** hide surface "..surface)
+                                 end
+                                 local a = animation({})
+                                 local r = m:JSON({surface = ev.surface,
+                                                   visible = 0})
+                                 if ev.appid == onscreen then
+                                     onscreen_counter = onscreen_counter - 1
+                                     if onscreen_counter <= 0 then
+                                        onscreen_counter = 0
+                                        wmgr:layer_request(m:JSON({layer = 5, visible = 0}))
+                                     end
+                                 end
+                                 wmgr:window_request(r,a,0)
+
+                             elseif event == "create" then
+
+                                if verbose > 0 then
+                                    print("*** screen resource event: " ..
+                                          tostring(ev))
+                                end
+
+                                local regulation = mdb.select.select_driving_mode.single_value
+
+                                if regulation == 1 then
+
+                                    local blacklisted = false
+
+                                    -- applications which have their category set to "entertainment"
+                                    -- or "undefined" are blacklisted, meaning they should be regulated
+
+                                    for i,v in pairs(ft(mdb.select.undefined_applications)) do
+                                        if v.appid == ev.appid then
+                                            if verbose > 0 then
+                                                print(ev.appid .. " was blacklisted (undefined)")
+                                            end
+                                            blacklisted = true
+                                            break
+                                        end
+                                    end
+
+                                    if not blacklisted then
+                                        for i,v in pairs(ft(mdb.select.entertainment_applications)) do
+                                            if v.appid == ev.appid then
+                                                if verbose > 0 then
+                                                    print(ev.appid .. " was blacklisted (entertainment)")
+                                                end
+                                                blacklisted = true
+                                                break
+                                            end
+                                        end
+                                    end
+
+                                    -- our local application config, which takes precedence
+                                    local conf = getApplication(ev.appid)
+
+                                    if not conf then
+                                        blacklisted = true
+                                    else
+                                        if conf.resource_class == "player" then
+                                            blacklisted = true
+                                        end
+
+                                        -- check the exceptions
+                                        if conf.requisites and conf.requisites.screen then
+                                            if conf.requisites.screen.driving then
+                                                blacklisted = false
+                                            end
+                                        end
+                                    end
+
+                                    -- disable only non-whitelisted applications
+                                                   if blacklisted then
+                                        if verbose > 0 then
+                                            print("disabling screen for " .. ev.appid)
+                                        end
+                                        resmgr:disable_screen_by_appid("*", "*", ev.appid, true, true)
+                                    end
+                                end
+
+                             elseif event == "destroy" then
+                               if verbose > 0 then
+                                    print("*** screen resource event: " ..
+                                          tostring(ev))
+                                 end
+                             else
+                                 if verbose > 0 then
+                                    print("*** screen resource event: " ..
+                                          tostring(ev))
+                                 end
+                             end
+                         end,
+  audio_event_handler = function(self, ev)
+                             local event = ev.event
+                             local appid = ev.appid
+                             local audioid = ev.audioid
+
+                             if event == "grant" then
+                                 if verbose > 0 then
+                                    print("*** grant audio to "..appid..
+                                          " ("..audioid..") in '" ..
+                                          ev.zone .. "' zone")
+                                 end
+                             elseif event == "revoke" then
+                                 if verbose > 0 then
+                                    print("*** revoke audio from "..appid..
+                                          " ("..audioid..") in '" ..
+                                          ev.zone .. "' zone")
+                                 end
+                             else
+                                 if verbose > 0 then
+                                    print("*** audio resource event: " ..
+                                          tostring(ev))
+                                 end
+                             end
+                        end
+}
+
+resclnt = resource_client {}
+
+wmgr = window_manager {
+  geometry = function(self, w,h, v)
+                  if type(v) == "function" then
+                      return v(w,h)
+                  end
+                  return v
+             end,
+
+  application = function(self, appid)
+                     if appid then
+                         local app = application_lookup(appid)
+                         if not app then
+                             app = application_lookup("default")
+                         end
+                         return app
+                     end
+                     return { privileges = {screen="none", audio="none"} }
+                end,
+
+  output_order = { 1, 0 },
+
+  outputs = { { name  = "Mid",
+                id    = 1,
+                zone  = "driver",
+                areas = { Full = {
+                              id     = 20,
+                              pos_x  = 0,
+                              pos_y  = 0,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return h end
+                          },
+                          Left = {
+                              id     = 21,
+                              pos_x  = 0,
+                              pos_y  = 0,
+                              width  = 320,
+                              height = function(w,h) return h end
+                          },
+                          Right = {
+                              id     = 22,
+                              pos_x  = function(w,h) return w-320 end,
+                              pos_y  = 0,
+                              width  = 320,
+                              height = function(w,h) return h end
+                          }
+                        }
+               },
+               { name  = "Center",
+                 id    = 4,
+                 zone  = "driver",
+                 areas = { Status = {
+                              id     = 0,
+                              pos_x  = 0,
+                              pos_y  = 0,
+                              width  = function(w,h) return w end,
+                              height = 64
+                           },
+                           Full = {
+                              id     = 1,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return h-64-128 end
+                           },
+                           Upper = {
+                              id     = 2,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           Lower = {
+                              id     = 3,
+                              pos_x  = 0,
+                              pos_y  = function(w,h) return (h-64-128)/2+64 end,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           UpperLeft = {
+                              id     = 4,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w/2 end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           UpperRight = {
+                              id     = 5,
+                              pos_x  = function(w,h) return w/2 end,
+                              pos_y  = 64,
+                              width  = function(w,h) return w/2 end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           LowerLeft = {
+                              id     = 6,
+                              pos_x  = 0,
+                              pos_y  = function(w,h) return (h-64-128/2)+64 end,
+                              width  = function(w,h) return w/2 end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           LowerRight = {
+                              id     = 7,
+                              pos_x  = function(w,h) return w/2 end,
+                              pos_y  = function(w,h) return (h-64-128/2)+64 end,
+                              width  = function(w,h) return w/2 end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           SysApp = {
+                              id     = 8,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return h-64-128 end
+                           },
+                           ["SysApp.Left"] = {
+                              id     = 9,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w/2-181 end,
+                              height = function(w,h) return h-64-128 end
+                           },
+                           ["SysApp.Right"] = {
+                              id     = 10,
+                              pos_x  = function(w,h) return w/2+181 end,
+                              pos_y  = 64,
+                              width  = function(w,h) return w/2-181 end,
+                              height = function(w,h) return h-64-128 end
+                           },
+                           MobileFull = {
+                              id     = 11,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return h-64-128 end
+                           },
+                           MobileUpper = {
+                              id     = 12,
+                              pos_x  = 0,
+                              pos_y  = 64,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           MobileLower = {
+                              id     = 13,
+                              pos_x  = 0,
+                              pos_y  = function(w,h) return (h-64-128)/2+64 end,
+                              width  = function(w,h) return w end,
+                              height = function(w,h) return (h-64-128)/2 end
+                           },
+                           Control = {
+                              id     = 14,
+                              pos_x  = 0,
+                              pos_y  = function(w,h) return h-128 end,
+                              width  = function(w,h) return w end,
+                              height = 128
+                           },
+                        }
+              }
+  },
+             --    id   name            type  output
+  layers = { {      0, "BackGround"   ,    1, "Center" },
+             {      1, "Application"  ,    2, "Center" },
+             {      2, "HomeScreen"   ,    3, "Center" },
+             {      3, "ControlBar"   ,    3, "Center" },
+             {      4, "InterruptApp" ,    4, "Center" },
+             {      5, "OnScreen"     ,    5, "Center" },
+             {      6, "Touch"        ,  102, "Center" },
+             {      7, "Cursor"       ,  103, "Center" }
+  },
+
+
+  manager_update = function(self, oper)
+                       if verbose > 0 then
+                           print("### <== WINDOW MANAGER UPDATE:" ..
+                                 window_manager_operation_name(oper))
+                       end
+                       if oper == 1 then
+                           local wumask = window_mask { --raise   = true,
+                                                        visible = true,
+                                                        active  = true }
+                           local wrmask = window_mask { raise   = true,
+                                                        active  = true,
+                                                        layer   = true }
+                           local lumask = layer_mask  { visible = true }
+                           local lrmask = layer_mask  { visible = true }
+                           local req = m:JSON({
+                               passthrough_window_update  = wumask:tointeger(),
+                               passthrough_window_request = wrmask:tointeger(),
+                               passthrough_layer_update   = lumask:tointeger(),
+                               passthrough_layer_request  = lrmask:tointeger()
+                           })
+                           self:manager_request(req)
+                       end
+                   end,
+
+  window_update = function(self, oper, win, mask)
+                      if verbose > 0 then
+                          print("### <== WINDOW UPDATE oper:" ..
+                                window_operation_name(oper) ..
+                                " mask: " .. tostring(mask))
+                          if verbose > 1 then
+                              print(win)
+                          end
+                      end
+
+                      local arg = m:JSON({ surface = win.surface,
+                                           winname = win.name
+                      })
+                      local command = 0
+
+                      if oper == 1 then -- create
+                           local layertype = win.layertype
+                           if layertype and input_layer[layertype] then
+                               if verbose > 0 then
+                                   print("ignoring input panel creation")
+                               end
+                               return
+                           end
+                           command     = 0x10001
+                      elseif oper == 2 then -- destroy
+                           command     = 0x10002
+                      elseif oper == 3 then  -- namechange
+                           command     = 0x10010
+                      elseif oper == 4 or oper == 5 then -- visible/configure
+                           command     = 0x10009
+                           arg.zone    = win.area
+                           arg.node    = win.node
+                           if win.layertype then
+                               arg.layertype = win.layertype
+                           end
+                           arg.layer   = win.layer
+                           arg.pos_x   = win.pos_x
+                           arg.pos_y   = win.pos_y
+                           arg.width   = win.width
+                           arg.height  = win.height
+                           arg.raise   = win.raise
+                           arg.visible = win.visible
+                           if win.active == 0 then
+                               arg.active = 0
+                           else
+                               arg.active = 1
+                           end
+                      elseif oper == 6 then -- active
+                           if win.active == 0 then
+                               if verbose > 0 then
+                                   print("ignoring inactive event")
+                               end
+                               return
+                           end
+                           command = 0x10007
+                      elseif oper == 7 then -- map
+                           local map = win.map
+                           if not map then
+                               return
+                           end
+                           if win.mapped == 0 then
+                               command = 0x10021
+                           else
+                               command = 0x10020
+                           end
+                           arg.attr = map.type
+                           --arg.name = map.target
+                           arg.width = map.width
+                           arg.height = map.height
+                           arg.stride = map.stride
+                           arg.format = map.format
+                      else
+                           if verbose > 0 then
+                               print("### nothing to do")
+                           end
+                           return
+                      end
+
+                      local msg = m:JSON({ command = command,
+                                           appid   = win.appid,
+                                           pid     = win.pid,
+                                           arg     = arg
+                      })
+                      if verbose > 0 then
+                          print("### <== sending " ..
+                                command_name(msg.command) ..
+                                " window message to '" .. homescreen .. "'")
+                          if verbose > 1 then
+                              print(msg)
+                          end
+                      end
+                      sc:send_message(homescreen, msg)
+
+                      if oper == 1 then -- create
+                          local i = input_layer[win.layertype]
+                          local p = self:application(win.appid)
+                          local s = p.privileges.screen
+
+                          if s == "system" then
+                              local a = animation({})
+                              local r = m:JSON({surface = win.surface,
+                                                visible = 0,
+                                                raise   = 1})
+                              self:window_request(r,a,0)
+                          else
+                              if i then
+                                  if verbose > 0 then
+                                      print("do not make resource for " ..
+                                            "input window")
+                                  end
+                              else
+                                  resclnt:resource_set_create("screen",
+                                                               "driver",
+                                                               win.appid,
+                                                               win.surface)
+                                  special_screen_sets[win.surface] = true
+                              end
+                          end
+
+                          if onscreen and win.appid == onscreen then
+                              local resmsg = m:JSON({
+                                        command = 0x40006, -- window_id_res
+                                        appid   = win.appid,
+                                        pid     = win.pid,
+                                        res     = m:JSON({
+                                            window  = m:JSON({
+                                                ECU     = "",
+                                                display = "",
+                                                layer   = "",
+                                                layout  = "",
+                                                area    = "",
+                                                dispatchApp = "",
+                                                role        = win.name,
+                                                resourceId  = win.surface
+                                            })
+                                        })
+                              })
+                              if verbose > 0 then
+                                  print("### <== sending " ..
+                                        command_name(resmsg.command) ..
+                                        " message to '" .. onscreen .. "'")
+                                  if verbose > 1 then
+                                      print(resmsg)
+                                  end
+                              end
+                              sc:send_message(onscreen, resmsg);
+                          end
+                      elseif oper == 2 then -- destroy
+                          resclnt:resource_set_destroy("screen", win.surface)
+                          special_screen_sets[win.surface] = nil
+                      elseif oper == 6 then -- active
+                          if win.active then
+                              local i = input_layer[win.layertype]
+                              local p = self:application(win.appid)
+                              local s = p.privileges.screen
+                              local surface = win.surface
+                              if not i and s ~= "system" then
+                                 resclnt:resource_set_acquire("screen",surface)
+                                 resmgr:window_raise(win.appid, surface, 1)
+                              end
+                          end
+                      end
+                  end,
+
+  layer_update = function(self, oper, layer, mask)
+                      if verbose > 0 then
+                          print("### LAYER UPDATE:" ..
+                                layer_operation_name(oper) ..
+                                " mask: " .. tostring(mask))
+                          if verbose > 1 then
+                              print(layer)
+                          end
+                      end
+                      if oper == 3 then -- visible
+                         local command = 0x10008 -- change_layer
+                         local msg = m:JSON({
+                                         command = command,
+                                         appid = "",
+                                         arg = m:JSON({layer = layer.id,
+                                                       visible = layer.visible
+                                         })
+                                })
+                         if verbose > 0 then
+                            print("### <== sending "..command_name(command)..
+                                  " layer message")
+                            if verbose > 1 then
+                                print(msg)
+                            end
+                         end
+                         sc:send_message(homescreen, msg)
+                      else
+                           if verbose > 0 then
+                               print("### nothing to do")
+                           end
+                      end
+                 end,
+
+  output_update = function(self, oper, out, mask)
+                      local idx = out.index
+                      local defidx = self.output_order[idx+1]
+                      if verbose > 0 then
+                          print("### OUTPUT UPDATE:" .. oper ..
+                                " mask: "..tostring(mask))
+                      end
+                      if not defidx then
+                          return
+                      end
+                      print(out)
+                      local outdef = self.outputs[defidx+1]
+                      if (oper == 1) then -- create
+                          if outdef then
+                              self:output_request(m:JSON({index = idx,
+                                                          id    = outdef.id,
+                                                          name  = outdef.name
+                                                          }))
+                          end
+                      elseif (oper == 5) then -- done
+                          local ads = outdef.areas
+                          local on = outdef.name
+                          if ads then
+                              for name,ad in pairs(ads) do
+                                  local can = wmgr:canonical_name(on.."."..name)
+                                  local a = m:JSON({name   = name,
+                                                    output = out.index})
+                                  for fld,val in pairs(ad) do
+                                      a[fld] = self:geometry(out.width,
+                                                             out.height,
+                                                             val)
+                                   end
+                                   self:area_create(a)
+                                   resmgr:area_create(area[can], outdef.zone)
+                              end
+                          end
+                      end
+                  end
+}
+
+
+imgr = input_manager {
+  inputs = {{ name = "G27 Racing Wheel",
+              id = 0,
+              switch = { [2] = {appid="org.tizen.ico.app-soundsample"      },
+                         [3] = {appid="org.tizen.ico.homescreen", keycode=1},
+                         [4] = {appid="org.tizen.ico.app-soundsample"      },
+                         [5] = {appid="org.tizen.ico.homescreen", keycode=2}
+             }}
+  },
+
+  manager_update = function(self, oper)
+                       if verbose > 0 then
+                           print("### <== INPUT MANAGER UPDATE:" ..
+                                 input_manager_operation_name(oper))
+                       end
+                   end,
+
+  input_update = function(self, oper, inp, mask)
+                     if verbose > 0 then
+                         print("### INPUT UPDATE:" ..
+                                input_operation_name(oper) ..
+                                " mask: " .. tostring(mask))
+                          if verbose > 1 then
+                              print(inp)
+                          end
+                      end
+                 end,
+  code_update  = function(self, oper, code, mask)
+                     if verbose > 0 then
+                         print("### CODE UPDATE: mask: " .. tostring(mask))
+                         if verbose > 1 then
+                             print(code)
+                         end
+                     end
+                     local msg = m:JSON({ command = 1,
+                                          appid = "org.tizen.ico.homescreen",
+                                          arg = m:JSON({ device = code.device,
+                                                         time = code.time,
+                                                         input = code.input,
+                                                         code = code.id,
+                                                         state = code.state
+                                           })
+                     })
+                     if verbose > 0 then
+                         print("### <== sending " ..
+                               command_name(msg.command) ..
+                               " input message")
+                         if verbose > 1 then
+                             print(msg)
+                         end
+                     end
+                     sc:send_message(homescreen, msg)
+                 end
+}
+
+sc = m:get_system_controller()
+
+-- resource sets
+sets = {}
+
+-- special screen resource sets
+-- TODO: just rewrite screen resource handling to use regular resource API
+
+special_screen_sets = {}
+
+-- user manager
+um = m:UserManager()
+
+connected = false
+homescreen = ""
+onscreen = ""
+
+cids = {}
+
+-- these shoud be before wmgr:connect() is called
+if verbose > 0 then
+   print("====== creating applications ======")
+end
+application {
+    appid          = "default",
+    area           = "Center.Full",
+    privileges     = { screen = "none", audio = "none" },
+    resource_class = "player",
+    screen_priority = 0
+}
+
+application {
+    appid           = "weston",
+    area            = "Center.Full",
+    privileges      = { screen = "system", audio = "none" },
+    resource_class  = "implicit",
+    screen_priority = 30
+}
+
+application {
+    appid           = "org.tizen.ico.homescreen",
+    area            = "Center.Full",
+    windows         = { {'ico_hs_controlbarwindow', 'Center.Control'} },
+    privileges      = { screen = "system", audio = "system" },
+    resource_class  = "player",
+    screen_priority = 20
+}
+
+application {
+    appid           = "org.tizen.ico.statusbar",
+    area            = "Center.Status",
+    privileges      = { screen = "system", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 20
+}
+
+application {
+    appid           = "org.tizen.ico.onscreen",
+    area            = "Center.Full",
+    privileges      = { screen = "system", audio = "system" },
+    resource_class  = "player",
+    screen_priority = 20
+}
+
+application {
+    appid           = "org.tizen.ico.login",
+    area            = "Center.Full",
+    privileges      = { screen = "system", audio = "system" },
+    resource_class  = "player",
+    screen_priority = 20
+}
+
+application {
+    appid           = "org.tizen.ico.camera_left",
+    area            = "Center.SysApp.Left",
+    privileges      = { screen = "system", audio = "none" },
+    requisites      = { screen = "blinker_left", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 30
+}
+
+application {
+    appid           = "org.tizen.ico.camera_right",
+    area            = "Center.SysApp.Right",
+    privileges      = { screen = "system", audio = "none" },
+    requisites      = { screen = "blinker_right", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 30
+}
+
+application {
+    appid           = "net.zmap.navi",
+    area            = "Center.Full",
+    privileges      = { screen = "none", audio = "none" },
+    resource_class  = "navigator",
+    screen_priority = 30
+}
+
+application {
+    appid           = "GV3ySIINq7.GhostCluster",
+    area            = "Center.Full",
+    privileges      = { screen = "none", audio = "none" },
+    resource_class  = "system",
+    requisites      = { screen = "driving", audio = "none" },
+    screen_priority = 30
+}
+
+application {
+    appid           = "MediaPlayer",
+    area            = "Center.Full",
+    privileges      = { screen = "none", audio = "none" },
+    requisites      = { screen = "driving", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 0
+}
+
+application {
+    appid           = "MyMediaPlayer",
+    area            = "Center.Full",
+    privileges      = { screen = "none", audio = "none" },
+    requisites      = { screen = "driving", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 0
+}
+
+application {
+    appid           = "MeterWidget",
+    area            = "Center.Full",
+    privileges      = { screen = "none", audio = "none" },
+    requisites      = { screen = "driving", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 0
+}
+
+application {
+    appid           = "org.tizen.ico.app-soundsample",
+    area            = "Center.Full",
+    privileges      = { screen = "none", audio = "none" },
+    -- uncomment the next line to make the app exempt from regulation
+    -- requisites      = { screen = "driving", audio = "none" },
+    resource_class  = "player",
+    screen_priority = 0
+}
+
+
+if sc then
+    sc.client_handler = function (self, cid, msg)
+        local command = msg.command
+        local appid = msg.appid
+        if verbose > 0 then
+            print('### ==> client handler:')
+            if verbose > 1 then
+                print(msg)
+            end
+        end
+
+        -- known commands: 1 for SEND_APPID, synthetic command 0xFFFF for
+        -- disconnection
+
+        if command == 0xFFFF then
+            if verbose > 1 then
+                print('client ' .. cid .. ' disconnected')
+            end
+            if msg.appid == homescreen then
+                homescreen = ""
+                for i,v in pairs(special_screen_sets) do
+                    resclnt:resource_set_destroy("screen", i)
+                    special_screen_sets[i] = nil
+                end
+            end
+            return
+        end
+
+        -- handle the connection to weston
+
+        if appid then
+            if appid == "org.tizen.ico.homescreen" then
+                print('Setting homescreen='..appid)
+                homescreen = appid
+                if command and command == 1 then
+                    send_driving_mode_to(homescreen)
+                    send_night_mode_to(homescreen)
+                end
+            elseif appid == "org.tizen.ico.onscreen" then
+                onscreen = appid
+                if command and command == 1 then
+                    send_driving_mode_to(onscreen)
+                    send_night_mode_to(onscreen)
+                end
+            end
+
+        if not connected and appid == "org.tizen.ico.homescreen" then
+                print('Trying to connect to weston...')
+                connected = wmgr:connect()
+            end
+            cids[cid] = appid
+        end
+    end
+
+    sc.generic_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('### ==> generic handler:')
+            if verbose > 1 then
+               print(msg)
+            end
+        end
+    end
+
+    sc.window_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('### ==> received ' ..
+                   command_name(msg.command) .. ' message from ' .. cids[cid])
+            if verbose > 1 then
+                print(tostring(msg))
+            end
+        end
+
+        local a = animation({})
+        local nores = false
+        if msg.command == 0x10003 then       -- ico SHOW command
+            local raise_mask = 0x01000000
+            local lower_mask = 0x02000000
+            local nores_mask = 0x40000000
+            local time_mask  = 0x00ffffff
+
+            local surface        = msg.arg.surface
+            local system_surface = false
+            local appdb          = wmgr:application(msg.appid)
+            system_surface       = appdb.privileges.screen == "system"
+
+            msg.arg.visible = 1
+
+            if msg.arg then
+                local time = 200
+                if  msg.arg.anim_time then
+                    local t = msg.arg.anim_time
+                    -- the actual time for the animation
+                    time = m:AND(t, time_mask)
+                    -- flag for ignoring resource control
+                    nores = m:AND(t, nores_mask)
+                    if m:AND(t, raise_mask) then
+                        msg.arg.raise = 1
+                    elseif m:AND(t, lower_mask) then
+                        msg.arg.raise = 0
+                    end
+                end
+                if msg.arg.anim_name then
+                    a.show = { msg.arg.anim_name, time }
+                    print('time: ' .. tostring(time))
+                end
+            end
+
+            -- all show messages from system surfaces are forced
+            if not nores and system_surface then
+                nores = true
+                if not msg.arg.raise then
+                    msg.arg.raise = 1
+                end
+            end
+
+            if verbose > 2 then
+                print('### ==> SHOW')
+                print(tostring(msg.arg) .. ", system_surface=" .. tostring(system_surface))
+            end
+
+            if nores then
+                wmgr:window_request(msg.arg, a, 0)
+
+                -- only non-system surfaces have resource sets
+                if not system_surface then
+                    resclnt:resource_set_acquire("screen", surface)
+                end
+            else
+                resclnt:resource_set_acquire("screen", surface)
+                resmgr:window_raise(msg.appid, surface, 1)
+            end
+        elseif msg.command == 0x10004 then   -- ico HIDE command
+            local raise_mask = 0x01000000
+            local lower_mask = 0x02000000
+            local nores_mask = 0x40000000
+            local time_mask  = 0x00ffffff
+            msg.arg.visible = 0
+            if msg.arg then
+                local time = 200
+                if msg.arg.anim_time then
+                    local t = msg.arg.anim_time
+                    -- the actual time for the animation
+                    time = m:AND(t, time_mask)
+                    -- flag for ignoring resource control
+                    nores = m:AND(t, nores_mask)
+                end
+                if msg.arg.anim_name then
+                    a.hide = { msg.arg.anim_name, time }
+                    print('hide animation time: ' .. tostring(a.hide[2]))
+                end
+            end
+            if not nores then
+                local p = wmgr:application(msg.appid)
+                local s = p.privileges.screen
+                if s == "system" then
+                    nores = true
+                    msg.arg.raise = 0
+                end
+            end
+            if verbose > 2 then
+                print('### ==> HIDE REQUEST')
+                print(tostring(msg.arg))
+            end
+            if nores then
+                wmgr:window_request(msg.arg, a, 0)
+            else
+                resmgr:window_raise(msg.appid, msg.arg.surface, -1)
+            end
+        elseif msg.command == 0x10005 then   -- ico MOVE
+            if verbose > 2 then
+                print('### ==> MOVE REQUEST')
+                print(tostring(msg.arg))
+            end
+            if msg.arg.zone then
+                msg.arg.area = msg.arg.zone
+            end
+            wmgr:window_request(msg.arg, a, 0)
+            -- TODO: handle if area changed
+        elseif msg.command == 0x10007 then   -- ico CHANGE_ACTIVE
+            if not msg.arg.active then
+                msg.arg.active = 3 -- pointer + keyboard
+            end
+            if verbose > 2 then
+                print('### ==> CHANGE_ACTIVE REQUEST')
+                print(tostring(msg.arg))
+            end
+            wmgr:window_request(msg.arg, a, 0)
+        elseif msg.command == 0x10008 then   -- ico CHANGE_LAYER
+            if verbose > 2 then
+                print('### ==> CHANGE_LAYER REQUEST')
+                print(tostring(msg.arg))
+            end
+            --[[
+            if msg.arg.layer ~= 4 or msg.arg.layer ~= 5 then
+                print("do not change layer for other than cursor or touch")
+                return
+            end
+            --]]
+            wmgr:window_request(msg.arg, a, 0)
+        elseif msg.command == 0x10020 then   -- ico MAP_THUMB
+            local framerate = msg.arg.framerate
+            local animname  = msg.arg.anim_name
+            if animname then
+               a.map = { animname, 1 }
+               if not framerate or framerate < 0 then
+                  framerate = 5
+               end
+               msg.arg.mapped = 1
+               if verbose > 2 then
+                  print('### ==> MAP_THUMB REQUEST')
+                  print(msg.arg)
+                  print('framerate: '..framerate)
+               end
+               wmgr:window_request(msg.arg, a, framerate)
+            end
+        elseif msg.command == 0x10021 then   -- ico UNMAP_THUMB
+            msg.arg.mapped = 0
+            if verbose > 2 then
+                print('### ==> UNMAP_THUMB REQUEST')
+                print(msg.arg)
+            end
+            wmgr:window_request(msg.arg, a, 0)
+--[[
+        elseif msg.command == 0x10013 then -- ico MAP_BUFFER command
+            local shmname = msg.arg.anim_name
+            local bufsize = msg.arg.width
+            local bufnum  = msg.arg.height
+            if shmname and bufsize and bufnum then
+                if verbose > 2 then
+                    print('### ==> MAP_BUFFER REQUEST')
+                    print("shmaname='" .. shmname ..
+                          "' bufsize='" .. bufsize ..
+                          " bufnum=" .. bufnum)
+                end
+                wmgr:buffer_request(shmname, bufsize, bufnum)
+            end
+--]]
+        elseif msg.command == 0x10030 then   -- ico SHOW_LAYER command
+            msg.arg.visible = 1
+            if verbose > 2 then
+                print('### ==> SHOW_LAYER REQUEST')
+                print(msg.arg)
+            end
+            wmgr:layer_request(msg.arg)
+        elseif msg.command == 0x10031 then   -- ico HIDE_LAYER command
+            msg.arg.visible = 0
+            if verbose > 2 then
+                print('### ==> HIDE_LAYER REQUEST')
+                print(msg.arg)
+            end
+            wmgr:layer_request(msg.arg)
+        end
+    end
+
+    sc.input_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('### ==> input handler: ' .. command_name(msg.command))
+            if verbose > 1 then
+                print(msg)
+            end
+        end
+        if msg.command == 0x20001 then -- add_input
+            msg.arg.appid = msg.appid
+            if verbose > 2 then
+                print('### ==> ADD_INPUT REQUEST')
+                print(tostring(msg.arg))
+            end
+            imgr:input_request(msg.arg)
+        elseif msg.command == 0x20002 then -- del_input
+            msg.arg.appid = ''
+            if verbose > 2 then
+                print('### ==> DEL_INPUT REQUEST')
+                print(tostring(msg.arg))
+            end
+            imgr:input_request(msg.arg)
+        -- elseif msg.command == 0x20003 then -- send_input
+        end
+    end
+
+    sc.user_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('### ==> user handler: ' .. command_name(msg.command))
+            if verbose > 1 then
+                print(msg)
+            end
+        end
+
+        if not um then
+            print("User Manager not initialized")
+            return
+        end
+
+        if msg.command == 0x30001 then -- change_user
+            print("command CHANGE_USER")
+            if not msg.arg then
+                print("invalid message")
+                return
+            end
+
+            username = msg.arg.user
+            passwd = msg.arg.pass
+
+            if not username then
+                username = ""
+            end
+
+            if not passwd then
+                passwd = ""
+            end
+
+            success = um:changeUser(username, passwd)
+
+            if not success then
+                reply = m.JSON({
+                    appid = msg.appid,
+                    command = cmd
+                })
+                if sc:send_message(msg.appid, reply) then
+                    print('*** sent authentication failed message')
+                else
+                    print('*** failed to send authentication failed message')
+                end
+            end
+
+        elseif msg.command == 0x30002 then -- get_userlist
+            print("command GET_USERLIST")
+            if not msg.appid then
+                print("invalid message")
+                return
+            end
+
+            users, currentUser = um:getUserList()
+
+            if not users then
+                print("failed to get user list")
+                return
+            end
+
+            nUsers = 0
+
+            for i,v in pairs(users) do
+                nUsers = nUsers + 1
+            end
+
+            if not currentUser then
+                currentUser = ""
+            end
+
+            if verbose > 1 then
+                print("current user: " .. currentUser)
+                print("user list:")
+                for i,v in pairs(users) do
+                    print(v)
+                end
+            end
+
+            reply = m.JSON({
+                appid = msg.appid,
+                command = cmd,
+                arg = m.JSON({
+                    user_num = nUsers,
+                    user_list = users,
+                    user_login = currentUser
+                })
+            })
+
+            if verbose > 1 then
+                print("### <== GetUserList reply: " .. tostring(reply))
+            end
+
+            if sc:send_message(msg.appid, reply) then
+                print('*** reply OK')
+            else
+                print('*** reply FAILED')
+            end
+
+        elseif msg.command == 0x30003 then -- get_lastinfo
+            print("command GET_LASTINFO")
+            if not msg.appid then
+                print("invalid message")
+                return
+            end
+
+            lastInfo = um:getLastInfo(msg.appid)
+
+            if not lastInfo then
+                print("failed to get last info for app" .. msg.appid)
+                return
+            end
+
+            reply = m.JSON({
+                appid = msg.appid,
+                command = cmd,
+                arg = m.JSON({
+                    lastinfo = lastinfo
+                })
+            })
+
+            if sc:send_message(msg.appid, reply) then
+                print('*** reply OK')
+            else
+                print('*** reply FAILED')
+            end
+
+        elseif msg.command == 0x30004 then -- set_lastinfo
+            print("command SET_LASTINFO")
+            if not msg.arg or not msg.appid then
+                print("invalid message")
+                return
+            end
+
+            lastInfo = um:setLastInfo(msg.appid, msg.arg.lastinfo)
+        end
+    end
+
+    sc.resource_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('### ==> resource handler: ' .. command_name(msg.command))
+            if verbose > 1 then
+                print(msg)
+            end
+        end
+
+        getKey = function (msg)
+            -- Field rset.key is an id for distinguishing between rsets. All
+            -- resource types appear to contain a different key. Just pick one
+            -- based on what we have on the message.
+
+            key = nil
+
+            if msg and msg.res then
+                if msg.res.sound then
+                    key = msg.res.sound.id
+                elseif msg.res.input then
+                    key = msg.res.input.name
+                elseif msg.res.window then
+                    -- alarm! this appars to be non-unique?
+                    key = msg.res.window.resourceId
+                end
+            end
+
+            return key
+        end
+
+        createResourceSet = function (ctl, client, msg)
+            cb = function(rset)
+
+                -- m:info("*** resource_cb: client = '" .. msg.appid .. "'")
+
+                -- type is either basic (0) or interrupt (1)
+                requestType = 0
+                if msg.res.type then
+                    requestType = msg.res.type
+                end
+
+                if rset.data.filter_first then
+                    rset.data.filter_first = false
+                    return
+                end
+
+                if rset.acquired then
+                    cmd = 0x00040001 -- acquire
+
+                    if msg.appid == onscreen then
+                        -- notifications are valid only for a number of seconds
+                        rset.timer = m:Timer({
+                            interval = 5000,
+                            oneshot = true,
+                            callback = function (t)
+                                m:info("notification timer expired")
+
+                                if rset.data then
+                                    reply.res.window = rset.data.window
+                                end
+
+                                rset:release()
+                                rset.timer = nil
+
+                                -- Send a "RELEASE" message to client.
+                                -- This triggers the resource deletion
+                                -- cycle in OnScreen.
+
+                                reply = m.JSON({
+                                    appid = msg.appid,
+                                    command = cmd,
+                                    res = {
+                                        type = requestType
+                                    }
+                                })
+
+                                sc:send_message(client, reply)
+                            end
+                        })
+                    end
+                else
+                    cmd = 0x00040002 -- release
+                    if rset.timer then
+                       rset.timer.callback = nil
+                       rset.timer = nil
+                    end
+                end
+
+                reply = m.JSON({
+                        appid = msg.appid,
+                        command = cmd,
+                        res = {
+                            type = requestType
+                        }
+                    })
+
+                if rset.data.sound then
+                    reply.res.sound = rset.data.sound
+                end
+
+                if rset.data.window then
+                    reply.res.window = rset.data.window
+                end
+
+                if rset.data.input then
+                    reply.res.input = rset.data.input
+                end
+
+                if rset.acquired then
+                    m:info("resource cb: 'acquire' reply to client " .. client)
+                else
+                    m:info("resource cb: 'release' reply to client " .. client)
+                end
+
+                if not sc:send_message(client, reply) then
+                    m:info('*** reply FAILED')
+                end
+            end
+
+            local rset = m:ResourceSet({
+                    application_class = "player",
+                    zone = "driver", -- msg.zone ("full")
+                    callback = cb
+                })
+
+            rset.data = {
+                cid = cid,
+                ctl = ctl,
+                filter_first = true
+            }
+
+            if msg.res.sound then
+                rset:addResource({
+                        resource_name = "audio_playback"
+                    })
+                rset.resources.audio_playback.attributes.pid = tostring(msg.pid)
+                rset.resources.audio_playback.attributes.appid = msg.appid
+                print("sound name: " .. msg.res.sound.name)
+                print("sound zone:" .. msg.res.sound.zone)
+                print("sound adjust: " .. tostring(msg.res.sound.adjust))
+
+                rset.data.sound = msg.res.sound
+            end
+
+            if msg.res.input then
+                rset:addResource({
+                        resource_name = "input"
+                    })
+                rset.resources.input.attributes.pid = tostring(msg.pid)
+                rset.resources.input.attributes.appid = msg.appid
+                print("input name: " .. msg.res.input.name)
+                print("input event:" .. tostring(msg.res.input.event))
+
+                rset.data.input = msg.res.input
+            end
+
+            if msg.res.window then
+                rset:addResource({
+                        resource_name = "screen",
+                        shared = true
+                    })
+                rset.resources.screen.attributes.pid = tostring(msg.pid)
+                rset.resources.screen.attributes.appid = msg.appid
+                rset.resources.screen.attributes.surface = msg.res.window.resourceId
+                complete_area = msg.res.window.display .. '.' .. msg.res.window.area
+                rset.resources.screen.attributes.area = complete_area
+                if msg.appid == onscreen then
+                    rset.resources.screen.attributes.classpri = 1
+                else
+                    rset.resources.screen.attributes.classpri = 0
+                end
+
+                rset.data.window = msg.res.window
+            end
+
+            rset.key = getKey(msg)
+
+            return rset
+        end
+
+        -- parse the message
+
+        -- fields common to all messages:
+        --      msg.command
+        --      msg.appid
+        --      msg.pid
+
+        if msg.command == 0x40011 then -- create_res
+            print("command CREATE_RES")
+            key = getKey(msg)
+
+            if key then
+                if not sets.cid then
+                    sets.cid = {}
+                end
+                sets.cid[key] = createResourceSet(self, cid, msg)
+            end
+
+        elseif msg.command == 0x40012 then -- destroy_res
+            print("command DESTROY_RES")
+            key = getKey(msg)
+
+            if key then
+                if sets.cid and sets.cid[key] then
+                    sets.cid[key]:release()
+                    sets.cid[key].timer = nil
+                    sets.cid[key] = nil -- garbage collecting
+                end
+            end
+
+        elseif msg.command == 0x40001 then -- acquire_res
+            print("command ACQUIRE_RES")
+            key = getKey(msg)
+
+            if key then
+                if not sets.cid then
+                    sets.cid = {}
+                end
+                if not sets.cid[key] then
+                    sets.cid[key] = createResourceSet(self, cid, msg)
+                end
+                print("acquiring the resource set")
+                sets.cid[key]:acquire()
+            end
+
+        elseif msg.command == 0x40002 then -- release_res
+            print("command RELEASE_RES")
+
+            key = getKey(msg)
+
+            if key then
+                if sets.cid and sets.cid[key] then
+                    sets.cid[key]:release()
+
+                    if msg.appid == onscreen then
+                        -- in case of OnScreen, this actually means that the
+                        -- resource set is never used again; let gc do its job
+                        sets.cid[key].timer = nil
+                        sets.cid[key] = nil -- garbage collecting
+                    end
+                end
+            end
+
+        elseif msg.command == 0x40003 then -- deprive_res
+            print("command DEPRIVE_RES")
+
+        elseif msg.command == 0x40004 then -- waiting_res
+            print("command WAITING_RES")
+
+        elseif msg.command == 0x40005 then -- revert_res
+            print("command REVERT_RES")
+        end
+    end
+
+    sc.inputdev_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('*** inputdev handler: ' .. command_name(msg.command))
+            if verbose > 1 then
+                print(msg)
+            end
+        end
+    end
+
+    sc.notify_handler = function (self, cid, msg)
+        if verbose > 0 then
+            print('### notify handler: ' .. command_name(msg.command))
+            if verbose > 1 then
+                print(msg)
+            end
+        end
+    end
+end
+
+function send_driving_mode_to(client)
+    if client == "" then
+        return
+    end
+
+    local driving_mode = mdb.select.select_driving_mode.single_value
+
+    if not driving_mode then driving_mode = 0 end
+
+    local reply = m:JSON({ command = 0x60001,
+                           arg     = m:JSON({ stateid = 1,
+                                              state   = driving_mode
+                                     })
+                  })
+
+    if verbose > 0 then
+        print("### <== sending " .. command_name(reply.command) .. " message")
+        if verbose > 1 then
+            print(reply)
+        end
+    end
+
+    sc:send_message(client, reply)
+end
+
+function send_night_mode_to(client)
+    if client == "" then
+        return
+    end
+
+    local night_mode = mdb.select.select_night_mode.single_value
+
+    if not night_mode then night_mode = 0 end
+
+    local reply = m:JSON({ command = 0x60001,
+                           arg     = m:JSON({ stateid = 2,
+                                              state   = night_mode
+                                     })
+                  })
+
+    if verbose > 0 then
+        print("### <== sending " .. command_name(reply.command) .. " message")
+        if verbose > 1 then
+            print(reply)
+        end
+     end
+
+     sc:send_message(client, reply)
+end
+
+-- we should have 'audio_playback' defined by now
+m:try_load_plugin('telephony')
diff --git a/packaging.in/murphy.spec.in b/packaging.in/murphy.spec.in
new file mode 100644 (file)
index 0000000..445452b
--- /dev/null
@@ -0,0 +1,596 @@
+# By default we build with distro-default compilation flags which
+# enables optimizations. If you want to build with full debugging
+# ie. with optimization turned off and full debug info (-O0 -g3)
+# pass '--with debug' to rpmbuild on the command line. Similary
+# you can chose to compile with/without pulse, ecore, glib, qt,
+# dbus, and telephony support. --without squashpkg will prevent
+# squashing the -core and -plugins-base packages into the base
+# murphy package.
+
+%{!?_with_debug:%{!?_without_debug:%define _without_debug 0}}
+%{!?_with_lua:%{!?_without_lua:%define _with_lua 1}}
+%{!?_with_pulse:%{!?_without_pulse:%define _with_pulse 1}}
+%{!?_with_ecore:%{!?_without_ecore:%define _with_ecore 1}}
+%{!?_with_glib:%{!?_without_glib:%define _with_glib 1}}
+%{!?_with_qt:%{!?_without_qt:%define _without_qt 1}}
+%{!?_with_dbus:%{!?_without_dbus:%define _with_dbus 1}}
+%{!?_with_telephony:%{!?_without_telephony:%define _with_telephony 1}}
+%{!?_with_audiosession:%{!?_without_audiosession:%define _with_audiosession 1}}
+%{!?_with_websockets:%{!?_without_websockets:%define _with_websockets 1}}
+%{!?_with_smack:%{!?_without_smack:%define _with_smack 1}}
+%{!?_with_squashpkg:%{!?_without_squashpkg:%define _with_squashpkg 1}}
+
+# TODO: take care of /lib vs /lib64...
+%define systemddir /lib/systemd
+
+Summary: Murphy policy framework
+Name: murphy
+Version: @VERSION@
+Release: 1
+License: BSD-3-Clause
+Group: System/Service
+URL: http://01.org/murphy/
+Source0: %{name}-%{version}.tar.gz
+@DECLARE_PATCHES@
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%endif
+
+Requires(post): /bin/systemctl
+Requires(postun): /bin/systemctl
+
+BuildRequires: flex
+BuildRequires: bison
+BuildRequires: pkgconfig(lua)
+BuildRequires: pkgconfig(libsystemd-daemon)
+BuildRequires: pkgconfig(libsystemd-journal)
+
+BuildRequires: pkgconfig(wayland-client)
+BuildRequires: pkgconfig(ivi-extension-protocol)
+BuildRequires: ico-uxf-weston-plugin-devel
+
+%if %{?_with_pulse:1}%{!?_with_pulse:0}
+BuildRequires: pkgconfig(libpulse)
+%endif
+%if %{?_with_ecore:1}%{!?_with_ecore:0}
+BuildRequires: pkgconfig(ecore)
+BuildRequires: mesa-libEGL
+BuildRequires: mesa-libGLESv2
+%endif
+%if %{?_with_glib:1}%{!?_with_glib:0}
+BuildRequires: pkgconfig(glib-2.0)
+%endif
+%if %{?_with_qt:1}%{!?_with_qt:0}
+BuildRequires: pkgconfig(QtCore)
+%endif
+%if %{?_with_dbus:1}%{!?_with_dbus:0}
+BuildRequires: pkgconfig(dbus-1)
+%endif
+%if %{?_with_telephony:1}%{!?_with_telephony:0}
+BuildRequires: pkgconfig(ofono)
+%endif
+%if %{?_with_audiosession:1}%{!?_with_audiosession:0}
+BuildRequires: pkgconfig(audio-session-mgr)
+%endif
+%if %{?_with_websockets:1}%{!?_with_websockets:0}
+BuildRequires: libwebsockets-devel
+%endif
+BuildRequires: pkgconfig(json)
+
+%if %{?_with_smack:1}%{!?_with_smack:0}
+BuildRequires: pkgconfig(libsmack)
+%endif
+
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+%package core
+Summary: Murphy core runtime libraries
+Group: System/Libraries
+
+%package plugins-base
+Summary: The basic set of Murphy plugins
+Group: System/Service
+Requires: %{name} = %{version}
+Requires: %{name}-core = %{version}
+%endif
+
+%package devel
+Summary: The header files and libraries needed for Murphy development
+Group: System/Libraries
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+Requires: libjson-devel
+
+%package doc
+Summary: Documentation for Murphy
+Group: SDK/Documentation
+
+%if %{?_with_pulse:1}%{!?_with_pulse:0}
+%package pulse
+Summary: Murphy PulseAudio mainloop integration
+Group: System/Libraries
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+
+%package pulse-devel
+Summary: Murphy PulseAudio mainloop integration development files
+Group: System/Libraries
+Requires: %{name}-pulse = %{version}
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+%endif
+
+%if %{?_with_ecore:1}%{!?_with_ecore:0}
+%package ecore
+Summary: Murphy EFL/ecore mainloop integration
+Group: System/Libraries
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+
+%package ecore-devel
+Summary: Murphy EFL/ecore mainloop integration development files
+Group: System/Libraries
+Requires: %{name}-ecore = %{version}
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+%endif
+
+%if %{?_with_glib:1}%{!?_with_glib:0}
+%package glib
+Summary: Murphy glib mainloop integration
+Group: System/Libraries
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+
+%package glib-devel
+Summary: Murphy glib mainloop integration development files
+Group: System/Libraries
+Requires: %{name}-glib = %{version}
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+%endif
+
+%if %{?_with_qt:1}%{!?_with_qt:0}
+%package qt
+Summary: Murphy Qt mainloop integration
+Group: System/Libraries
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+
+%package qt-devel
+Summary: Murphy Qt mainloop integration development files
+Group: System/Libraries
+Requires: %{name}-qt = %{version}
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+%endif
+
+%package tests
+Summary: Various test binaries for Murphy
+Group: System/Testing
+Requires: %{name} = %{version}
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+Requires: %{name}-core = %{version}
+%else
+Requires: %{name} = %{version}
+%endif
+
+%description
+This package contains the basic daemon.
+
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+%description core
+This package contains the core runtime libraries.
+
+%description plugins-base
+This package contains a basic set of plugins.
+%endif
+
+%description devel
+This package contains header files and libraries necessary for development.
+
+%description doc
+This package contains documentation.
+
+%if %{?_with_pulse:1}%{!?_with_pulse:0}
+%description pulse
+This package contains the Murphy PulseAudio mainloop integration runtime files.
+
+%description pulse-devel
+This package contains the Murphy PulseAudio mainloop integration development
+files.
+%endif
+
+%if %{?_with_ecore:1}%{!?_with_ecore:0}
+%description ecore
+This package contains the Murphy EFL/ecore mainloop integration runtime files.
+
+%description ecore-devel
+This package contains the Murphy EFL/ecore mainloop integration development
+files.
+%endif
+
+%if %{?_with_glib:1}%{!?_with_glib:0}
+%description glib
+This package contains the Murphy glib mainloop integration runtime files.
+
+%description glib-devel
+This package contains the Murphy glib mainloop integration development
+files.
+%endif
+
+%if %{?_with_qt:1}%{!?_with_qt:0}
+%description qt
+This package contains the Murphy Qt mainloop integration runtime files.
+
+%description qt-devel
+This package contains the Murphy Qt mainloop integration development
+files.
+%endif
+
+%description tests
+This package contains various test binaries for Murphy.
+
+%prep
+%setup -q
+@APPLY_PATCHES@
+
+%build
+%if %{?_with_debug:1}%{!?_with_debug:0}
+export CFLAGS="-O0 -g3"
+V="V=1"
+%endif
+
+CONFIG_OPTIONS=""
+DYNAMIC_PLUGINS="domain-control"
+
+%if %{?_with_pulse:1}%{!?_with_pulse:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-pulse"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-pulse"
+%endif
+
+%if %{?_with_ecore:1}%{!?_with_ecore:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-ecore"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-ecore"
+%endif
+
+%if %{?_with_glib:1}%{!?_with_glib:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-glib"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-glib"
+%endif
+
+%if %{?_with_qt:1}%{!?_with_qt:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-qt"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-qt"
+%endif
+
+%if %{?_with_dbus:1}%{!?_with_dbus:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-dbus"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-dbus"
+%endif
+
+%if %{?_with_telephony:1}%{!?_with_telephony:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-telephony"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-telephony"
+%endif
+
+%if %{?_with_audiosession:1}%{!?_with_audiosession:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-resource-asm"
+DYNAMIC_PLUGINS="$DYNAMIC_PLUGINS,resource-asm"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-resource-asm"
+%endif
+
+%if %{?_with_websockets:1}%{!?_with_websockets:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-websockets"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-websockets"
+%endif
+
+%if %{?_with_smack:1}%{!?_with_smack:0}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-smack"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-smack"
+%endif
+
+NUM_CPUS="`cat /proc/cpuinfo | tr -s '\t' ' ' | \
+               grep '^processor *:' | wc -l`"
+[ -z "$NUM_CPUS" ] && NUM_CPUS=1
+
+./bootstrap && \
+    %configure $CONFIG_OPTIONS --with-dynamic-plugins=$DYNAMIC_PLUGINS && \
+    make clean && \
+    make -j$(($NUM_CPUS + 1)) $V
+
+%install
+rm -rf $RPM_BUILD_ROOT
+%make_install
+
+# Make sure we have a plugin dir even if all the basic plugins
+# are configured to be built in.
+mkdir -p $RPM_BUILD_ROOT%{_libdir}/murphy/plugins
+
+# Get rid of any *.la files installed by libtool.
+rm -f $RPM_BUILD_ROOT%{_libdir}/*.la
+
+# Clean up also the murphy DB installation.
+rm -f $RPM_BUILD_ROOT%{_libdir}/murphy/*.la
+
+# Generate list of linkedin plugins (depends on the configuration).
+outdir="`pwd`"
+pushd $RPM_BUILD_ROOT >& /dev/null && \
+    find ./%{_libdir}/murphy/plugins -name libmurphy-plugin-*.so* | \
+        sed 's#^./*#/#g' > $outdir/filelist.plugins-base && \
+popd >& /dev/null
+
+# Generate list of header files, filtering ones that go to subpackages.
+outdir="`pwd`"
+pushd $RPM_BUILD_ROOT >& /dev/null && \
+    find ./%{_includedir}/murphy | \
+    egrep -v '((pulse)|(ecore)|(glib)|(qt))-glue' | \
+        sed 's#^./*#/#g' > $outdir/filelist.devel-includes && \
+popd >& /dev/null
+
+# Replace the default sample/test config files with the packaging ones.
+cp packaging.in/murphy-lua.conf $RPM_BUILD_ROOT%{_sysconfdir}/murphy/murphy.conf
+cp packaging.in/murphy.lua      $RPM_BUILD_ROOT%{_sysconfdir}/murphy/murphy.lua
+
+# Copy plugin configuration files in place.
+mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/murphy/plugins/amb
+cp packaging.in/amb-config.lua \
+    $RPM_BUILD_ROOT%{_sysconfdir}/murphy/plugins/amb/config.lua
+
+# Copy the systemd service file in place.
+mkdir -p $RPM_BUILD_ROOT%{systemddir}/system
+cp packaging.in/murphyd.service $RPM_BUILD_ROOT%{systemddir}/system
+
+%if %{?_with_dbus:1}%{!?_with_dbus:0}
+mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/dbus-1/system.d
+cp packaging.in/org.Murphy.conf $RPM_BUILD_ROOT%{_sysconfdir}/dbus-1/system.d/org.Murphy.conf
+%endif
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%post
+/bin/systemctl enable murphyd.service
+
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+%post core
+%endif
+ldconfig
+
+%postun
+if [ "$1" = "0" ]; then
+    /bin/systemctl disable murphyd.service
+fi
+
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+%postun core
+%endif
+ldconfig
+
+%if %{?_with_glib:1}%{!?_with_glib:0}
+%post glib
+ldconfig
+
+%postun glib
+ldconfig
+%endif
+
+%if %{?_with_pulse:1}%{!?_with_pulse:0}
+%post pulse
+ldconfig
+
+%postun pulse
+ldconfig
+%endif
+
+%if %{?_with_ecore:1}%{!?_with_ecore:0}
+%post ecore
+ldconfig
+
+%postun ecore
+ldconfig
+%endif
+
+%if %{?_with_qt:1}%{!?_with_qt:0}
+%post qt
+lfconfig
+
+%postun qt
+ldconfig
+%endif
+
+%if %{?_with_squashpkg:1}%{!?_with_squashpkg:0}
+%files -f filelist.plugins-base
+%else
+%files
+%endif
+%defattr(-,root,root,-)
+%{_bindir}/murphyd
+%config %{_sysconfdir}/murphy
+%{systemddir}/system/murphyd.service
+%if %{?_with_audiosession:1}%{!?_with_audiosession:0}
+%{_sbindir}/asm-bridge
+%endif
+%if %{?_with_dbus:1}%{!?_with_dbus:0}
+%{_sysconfdir}/dbus-1/system.d
+%config %{_sysconfdir}/dbus-1/system.d/org.Murphy.conf
+%endif
+%if %{?_with_websockets:1}%{!?_with_websockets:0}
+%{_datadir}/murphy
+%endif
+
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+%files core
+%defattr(-,root,root,-)
+%endif
+%{_libdir}/libmurphy-common.so.*
+%{_libdir}/libmurphy-core.so.*
+%{_libdir}/libmurphy-resolver.so.*
+%{_libdir}/libmurphy-resource.so.*
+%{_libdir}/libmurphy-resource-backend.so.*
+%if %{?_with_lua:1}%{!?_with_lua:0}
+%{_libdir}/libmurphy-lua-utils.so.*
+%{_libdir}/libmurphy-lua-decision.so.*
+%endif
+%{_libdir}/libmurphy-domain-controller.so.*
+%{_libdir}/murphy/*.so.*
+%{_libdir}/libbreedline*.so.*
+%if %{?_with_dbus:1}%{!?_with_dbus:0}
+%{_libdir}/libmurphy-dbus.so.*
+%endif
+
+%if %{?_with_squashpkg:0}%{!?_with_squashpkg:1}
+%files plugins-base -f filelist.plugins-base
+%defattr(-,root,root,-)
+%endif
+%{_libdir}/murphy/plugins
+
+%files devel -f filelist.devel-includes
+%defattr(-,root,root,-)
+# %{_includedir}/murphy/config.h
+# %{_includedir}/murphy/common.h
+# #%{_includedir}/murphy/core.h
+# %{_includedir}/murphy/common
+# %{_includedir}/murphy/core
+# %{_includedir}/murphy/resolver
+# %{_includedir}/murphy/resource
+# # hmmm... should handle disabled plugins properly.
+# %{_includedir}/murphy/domain-control
+# %{_includedir}/murphy/plugins
+%{_includedir}/murphy-db
+%{_libdir}/libmurphy-common.so
+%{_libdir}/libmurphy-core.so
+%{_libdir}/libmurphy-resolver.so
+%{_libdir}/libmurphy-resource.so
+%{_libdir}/libmurphy-resource-backend.so
+%if %{?_with_lua:1}%{!?_with_lua:0}
+%{_libdir}/libmurphy-lua-utils.so
+%{_libdir}/libmurphy-lua-decision.so
+%endif
+%{_libdir}/libmurphy-domain-controller.so
+%{_libdir}/murphy/*.so
+%{_libdir}/pkgconfig/murphy-common.pc
+%{_libdir}/pkgconfig/murphy-core.pc
+%{_libdir}/pkgconfig/murphy-resolver.pc
+#%{_libdir}/pkgconfig/murphy-resource.pc
+%if %{?_with_lua:1}%{!?_with_lua:0}
+%{_libdir}/pkgconfig/murphy-lua-utils.pc
+%{_libdir}/pkgconfig/murphy-lua-decision.pc
+%endif
+%{_libdir}/pkgconfig/murphy-domain-controller.pc
+%{_libdir}/pkgconfig/murphy-db.pc
+%{_libdir}/pkgconfig/murphy-resource.pc
+%{_includedir}/breedline
+%{_libdir}/libbreedline*.so
+%{_libdir}/pkgconfig/breedline*.pc
+%if %{?_with_dbus:1}%{!?_with_dbus:0}
+#%{_includedir}/murphy/dbus
+%{_libdir}/libmurphy-dbus.so
+%{_libdir}/pkgconfig/murphy-dbus.pc
+%endif
+
+%files doc
+%defattr(-,root,root,-)
+%doc %{_docdir}/../murphy/AUTHORS
+%doc %{_docdir}/../murphy/CODING-STYLE
+%license %{_docdir}/../murphy/COPYING
+%doc %{_docdir}/../murphy/ChangeLog
+%doc %{_docdir}/../murphy/INSTALL
+%doc %{_docdir}/../murphy/NEWS
+%doc %{_docdir}/../murphy/README
+
+%if %{?_with_pulse:1}%{!?_with_pulse:0}
+%files pulse
+%defattr(-,root,root,-)
+%{_libdir}/libmurphy-pulse.so.*
+
+%files pulse-devel
+%defattr(-,root,root,-)
+%{_includedir}/murphy/common/pulse-glue.h
+%{_libdir}/libmurphy-pulse.so
+%{_libdir}/pkgconfig/murphy-pulse.pc
+%endif
+
+%if %{?_with_ecore:1}%{!?_with_ecore:0}
+%files ecore
+%defattr(-,root,root,-)
+%{_libdir}/libmurphy-ecore.so.*
+
+%files ecore-devel
+%defattr(-,root,root,-)
+%{_includedir}/murphy/common/ecore-glue.h
+%{_libdir}/libmurphy-ecore.so
+%{_libdir}/pkgconfig/murphy-ecore.pc
+%endif
+
+%if %{?_with_glib:1}%{!?_with_glib:0}
+%files glib
+%defattr(-,root,root,-)
+%{_libdir}/libmurphy-glib.so.*
+
+%files glib-devel
+%defattr(-,root,root,-)
+%{_includedir}/murphy/common/glib-glue.h
+%{_libdir}/libmurphy-glib.so
+%{_libdir}/pkgconfig/murphy-glib.pc
+%endif
+
+%if %{?_with_qt:1}%{!?_with_qt:0}
+%files qt
+%defattr(-,root,root,-)
+%{_libdir}/libmurphy-qt.so.*
+
+%files qt-devel
+%defattr(-,root,root,-)
+%{_includedir}/murphy/common/qt-glue.h
+%{_libdir}/libmurphy-qt.so
+%{_libdir}/pkgconfig/murphy-qt.pc
+%endif
+
+%files tests
+%defattr(-,root,root,-)
+%{_bindir}/resource-client
+%{_bindir}/resource-api-test
+%{_bindir}/resource-api-fuzz
+%{_bindir}/test-domain-controller
+%{_bindir}/murphy-console
+
+%changelog
+* Tue Nov 27 2012 Krisztian Litkey <krisztian.litkey@intel.com> -
+- Initial build for 2.0alpha.
diff --git a/packaging.in/murphyd.conf b/packaging.in/murphyd.conf
new file mode 100644 (file)
index 0000000..75395b7
--- /dev/null
@@ -0,0 +1,2 @@
+d /tmp/murphy 0755 app app - -
+d /var/run/murphy/processes 0755 app app - -
diff --git a/packaging.in/murphyd.init b/packaging.in/murphyd.init
new file mode 100755 (executable)
index 0000000..d19b931
--- /dev/null
@@ -0,0 +1,156 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          skeleton
+# Required-Start:    $remote_fs $syslog
+# Required-Stop:     $remote_fs $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Example initscript
+# Description:       This file should be used to construct scripts to be
+#                    placed in /etc/init.d.
+### END INIT INFO
+
+# Author: Foo Bar <foo@bar.org>
+#
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="Murphy Daemon"
+NAME=murphyd
+DAEMON=/usr/bin/$NAME
+DAEMON_ARGS="-t dlog -vvv"
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+       mkdir -p /tmp/murphy
+       mkdir -p /var/run/murphy/processes
+       # Return
+       #   0 if daemon has been started
+       #   1 if daemon was already running
+       #   2 if daemon could not be started
+       start-stop-daemon --start --quiet --exec $DAEMON --test > /dev/null \
+               || return 1
+       start-stop-daemon --start --quiet --exec $DAEMON -- \
+               $DAEMON_ARGS \
+               || return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+       # Return
+       #   0 if daemon has been stopped
+       #   1 if daemon was already stopped
+       #   2 if daemon could not be stopped
+       #   other if a failure occurred
+       start-stop-daemon --stop -s 9 --quiet --oknodo --exec $DAEMON
+       RETVAL="$?"
+
+    # clean up
+    rm -f /var/run/murphy/processes/*
+
+       [ "$RETVAL" = 2 ] && return 2
+       # Wait for children to finish too if this is a daemon that forks
+       # and if the daemon is only ever run from this initscript.
+       # If the above conditions are not satisfied then add some other code
+       # that waits for the process to drop all resources that could be
+       # needed by services started subsequently.  A last resort is to
+       # sleep for some time.
+       start-stop-daemon --stop --quiet --oknodo --exec $DAEMON
+       [ "$?" = 2 ] && return 2
+       return "$RETVAL"
+}
+
+#
+# Function that sends a SIGHUP to the daemon/service
+#
+do_reload() {
+       #
+       # If the daemon can reload its configuration without
+       # restarting (for example, when it is sent a SIGHUP),
+       # then implement that here.
+       #
+       start-stop-daemon --stop --signal 1 --quiet --name $NAME
+       return 0
+}
+
+case "$1" in
+  start)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+       do_start
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  stop)
+       [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+       do_stop
+       case "$?" in
+               0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+               2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+       esac
+       ;;
+  status)
+       status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+       ;;
+  #reload|force-reload)
+       #
+       # If do_reload() is not implemented then leave this commented out
+       # and leave 'force-reload' as an alias for 'restart'.
+       #
+       #log_daemon_msg "Reloading $DESC" "$NAME"
+       #do_reload
+       #log_end_msg $?
+       #;;
+  restart|force-reload)
+       #
+       # If the "reload" option is implemented then remove the
+       # 'force-reload' alias
+       #
+       log_daemon_msg "Restarting $DESC" "$NAME"
+       do_stop
+       case "$?" in
+         0|1)
+               do_start
+               case "$?" in
+                       0) log_end_msg 0 ;;
+                       1) log_end_msg 1 ;; # Old process is still running
+                       *) log_end_msg 1 ;; # Failed to start
+               esac
+               ;;
+         *)
+               # Failed to stop
+               log_end_msg 1
+               ;;
+       esac
+       ;;
+  *)
+       #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
+       echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+       exit 3
+       ;;
+esac
+
+:
diff --git a/packaging.in/murphyd.service b/packaging.in/murphyd.service
new file mode 100644 (file)
index 0000000..8b6fda2
--- /dev/null
@@ -0,0 +1,10 @@
+[Unit]
+Description=Murphy Resource Policy Daemon
+
+[Service]
+Type=simple
+ExecStart=/usr/bin/murphyd -t systemd -vvv -f
+KillSignal=SIGTERM
+
+[Install]
+WantedBy=default.target
diff --git a/packaging.in/org.Murphy.conf.in b/packaging.in/org.Murphy.conf.in
new file mode 100644 (file)
index 0000000..fa6ac40
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+        <policy group="@TZ_SYS_USER_GROUP@">
+                <allow own="org.Murphy"/>
+                <allow receive_sender="org.Murphy"/>
+                <allow send_destination="org.Murphy"/>
+        </policy>
+        <policy context="default">
+                <allow receive_sender="org.Murphy"/>
+                <allow send_destination="org.Murphy"/>
+        </policy>
+</busconfig>
diff --git a/packaging/murphy.manifest b/packaging/murphy.manifest
new file mode 100644 (file)
index 0000000..86dbb26
--- /dev/null
@@ -0,0 +1,5 @@
+<manifest>
+    <request>
+        <domain name="_" />
+    </request>
+</manifest>
diff --git a/packaging/murphy.spec b/packaging/murphy.spec
new file mode 100644 (file)
index 0000000..4cf6cc9
--- /dev/null
@@ -0,0 +1,601 @@
+# These are on by default, unless explicitly disabled.
+%bcond_without lua
+#%bcond_without pulse
+#%bcond_without ecore
+%bcond_without glib
+%bcond_without dbus
+#%bcond_without telephony
+#%bcond_without audiosession
+#%bcond_without websockets
+#%bcond_without smack
+#%bcond_without sysmon
+
+# These are off by default, unless explicitly enabled.
+#
+# Notes:
+#   By default we build with distro-default compilation flags which
+#   enables optimizations. If you want to build with full debugging
+#   ie. with optimization turned off and full debug info (-O0 -g3)
+#   pass '--with debug' to rpmbuild on the command line. Similary
+#   you can chose to compile with/without pulse, ecore, glib, qt,
+#   dbus, and telephony support.
+#
+#   qt is the macro for controlling Qt4 support, which is not supported
+#   in Tizen any more. qt5 is the corrsponding macro for controlling
+#   Qt5 support.
+#
+#%bcond_with icosyscon
+#%bcond_with qt
+#%bcond_with debug
+
+
+Summary: Resource policy framework
+Name: murphy
+Version: 0.0.74
+Release: 0
+License: BSD-3-Clause
+Group: System/Service
+URL: http://01.org/murphy/
+Source0: %{name}-%{version}.tar.gz
+Source1001: %{name}.manifest
+
+Requires(post): /bin/systemctl
+#Requires(post): libcap-tools
+Requires(postun): /bin/systemctl
+
+BuildRequires: flex
+BuildRequires: bison
+BuildRequires: pkgconfig(lua)
+BuildRequires: pkgconfig(libsystemd-daemon)
+BuildRequires: pkgconfig(libsystemd-journal)
+#BuildRequires: pkgconfig(libcap)
+#BuildRequires: pkgconfig(libtzplatform-config)
+#%if %{with pulse}
+#BuildRequires: pkgconfig(libpulse)
+#%endif
+#%if %{with ecore}
+#BuildRequires: pkgconfig(ecore)
+#BuildRequires: mesa-libEGL
+#BuildRequires: mesa-libGLESv2
+#%endif
+%if %{with glib}
+BuildRequires: pkgconfig(glib-2.0)
+%endif
+#%if %{with qt}
+#BuildRequires: pkgconfig(QtCore)
+#%endif
+%if %{with dbus}
+BuildRequires: pkgconfig(dbus-1)
+%endif
+#%if %{with telephony}
+#BuildRequires: pkgconfig(ofono)
+#%endif
+#%if %{with audiosession}
+#BuildRequires: pkgconfig(audio-session-mgr)
+#BuildRequires: pkgconfig(aul)
+#%endif
+#%if %{with websockets}
+#BuildRequires: libwebsockets-devel
+#%endif
+BuildRequires: pkgconfig(json)
+#%if %{with smack}
+#BuildRequires: pkgconfig(libsmack)
+#%endif
+#%if %{with icosyscon}
+#BuildRequires: ico-uxf-weston-plugin-devel
+#BuildRequires: weston-ivi-shell-devel
+#BuildRequires: genivi-shell-devel
+#BuildRequires: pkgconfig(ail)
+#BuildRequires: pkgconfig(aul)
+#BuildRequires: libxml2-devel
+#%endif
+
+%description
+This package contains the basic Murphy daemon.
+
+%package devel
+Summary: The header files and libraries needed for Murphy development
+Group: System/Libraries
+Requires: %{name} = %{version}-%{release}
+Requires: libjson-devel
+
+%description devel
+This package contains header files and libraries necessary for development.
+
+%package doc
+Summary: Documentation for Murphy
+Group: SDK/Documentation
+
+%description doc
+This package contains documentation.
+
+#%if %{with pulse}
+#%package pulse
+#Summary: Murphy PulseAudio mainloop integration
+#Group: System/Libraries
+#Requires: %{name} = %{version}-%{release}
+
+#%description pulse
+#This package contains the Murphy PulseAudio mainloop integration runtime files.
+
+#%package pulse-devel
+#Summary: Murphy PulseAudio mainloop integration development files
+#Group: System/Libraries
+#Requires: %{name}-pulse = %{version}-%{release}
+#Requires: %{name} = %{version}-%{release}
+
+#%description pulse-devel
+#This package contains the Murphy PulseAudio mainloop integration development
+#files.
+#%endif
+
+#%if %{with ecore}
+#%package ecore
+#Summary: Murphy EFL/ecore mainloop integration
+#Group: System/Libraries
+#Requires: %{name} = %{version}-%{release}
+
+#%description ecore
+#This package contains the Murphy EFL/ecore mainloop integration runtime files.
+
+#%package ecore-devel
+#Summary: Murphy EFL/ecore mainloop integration development files
+#Group: System/Libraries
+#Requires: %{name}-ecore = %{version}-%{release}
+#Requires: %{name} = %{version}-%{release}
+
+#%description ecore-devel
+#This package contains the Murphy EFL/ecore mainloop integration development
+#files.
+#%endif
+
+%if %{with glib}
+%package glib
+Summary: Murphy glib mainloop integration
+Group: System/Libraries
+Requires: %{name} = %{version}-%{release}
+
+%description glib
+This package contains the Murphy glib mainloop integration runtime files.
+
+%package glib-devel
+Summary: Murphy glib mainloop integration development files
+Group: System/Libraries
+Requires: %{name}-glib = %{version}-%{release}
+Requires: %{name} = %{version}-%{release}
+
+%description glib-devel
+This package contains the Murphy glib mainloop integration development
+files.
+%endif
+
+#%if %{with qt}
+#%package qt
+#Summary: Murphy Qt mainloop integration
+#Group: System/Libraries
+#Requires: %{name} = %{version}-%{release}
+
+#%description qt
+#This package contains the Murphy Qt mainloop integration runtime files.
+
+#%package qt-devel
+#Summary: Murphy Qt mainloop integration development files
+#Group: System/Libraries
+#Requires: %{name}-qt = %{version}-%{release}
+#Requires: %{name} = %{version}-%{release}
+#
+#%description qt-devel
+#This package contains the Murphy Qt mainloop integration development
+#files.
+#%endif
+
+#%package gam
+#Summary: Murphy support for Genivi Audio Manager
+#Group: System/Libraries
+#Requires: %{name} = %{version}-%{release}
+
+#%description gam
+#This package contains the Murphy plugins for necessary for supporting
+#Genivi Audio Manager.
+
+#%package gam-devel
+#Summary: Murphy support for Genivi Audio Manager development files
+#Group: System/Libraries
+#Requires: %{name}-gam = %{version}-%{release}
+
+#%description gam-devel
+#This package contains development files for Murphy Genivi Audio Manager
+#plugins.
+
+%package tests
+Summary: Various test binaries for Murphy
+Group: System/Testing
+Requires: %{name} = %{version}-%{release}
+Requires: %{name} = %{version}-%{release}
+
+%description tests
+This package contains various test binaries for Murphy.
+
+#%if %{with icosyscon}
+#%package system-controller
+#Summary: Murphy IVI System Controller plugin
+#Group: System/Service
+#Requires: ico-uxf-homescreen
+#Conflicts: murphy-ivi-resource-manager
+#Provides: system-controller
+#Conflicts: ico-uxf-homescreen-system-controller
+
+#%description system-controller
+#This package contains the Murphy IVI resource manager plugin.
+#%endif
+
+%prep
+%setup -q
+cp %{SOURCE1001} .
+#%if %{with icosyscon}
+#echo "Build with icosyscon"
+#%else
+#echo "Build without icosyscon"
+#%endif
+
+%build
+%if %{with debug}
+export CFLAGS="-O0 -g3"
+V="V=1"
+%endif
+
+CONFIG_OPTIONS=""
+DYNAMIC_PLUGINS="domain-control,system-controller"
+
+#%if %{with pulse}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-pulse"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-pulse"
+#%endif
+
+#%if %{with ecore}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-ecore"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-ecore"
+#%endif
+
+%if %{with glib}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-glib"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-glib"
+%endif
+
+#%if %{with qt}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-qt"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-qt"
+#%endif
+
+%if %{with dbus}
+CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-libdbus"
+%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-libdbus"
+%endif
+
+#%if %{with telephony}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-gpl --enable-telephony"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-telephony"
+#%endif
+
+#%if %{with audiosession}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-resource-asm"
+#DYNAMIC_PLUGINS="$DYNAMIC_PLUGINS,resource-asm"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-resource-asm"
+#%endif
+
+#%if %{with websockets}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-websockets"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-websockets"
+#%endif
+
+#%if %{with smack}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-smack"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-smack"
+#%endif
+
+#%if %{with icosyscon}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-system-controller"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-system-controller"
+#%endif
+
+#%if %{with sysmon}
+#CONFIG_OPTIONS="$CONFIG_OPTIONS --enable-system-monitor"
+#%else
+CONFIG_OPTIONS="$CONFIG_OPTIONS --disable-system-monitor"
+#%endif
+
+./bootstrap
+%configure $CONFIG_OPTIONS --with-dynamic-plugins=$DYNAMIC_PLUGINS
+%__make clean
+%__make %{?_smp_mflags} $V
+
+%install
+rm -rf %{buildroot}
+%make_install
+
+# Make sure we have a plugin dir even if all the basic plugins
+# are configured to be built in.
+mkdir -p %{buildroot}%{_libdir}/murphy/plugins
+
+# Get rid of any *.la files installed by libtool.
+rm -f %{buildroot}%{_libdir}/*.la
+
+# Clean up also the murphy DB installation.
+rm -f %{buildroot}%{_libdir}/murphy/*.la
+
+# Generate list of linkedin plugins (depends on the configuration).
+outdir="`pwd`"
+pushd %{buildroot}
+find ./%{_libdir} -name libmurphy-plugin-*.so* | \
+sed 's#^./*#/#g' > $outdir/filelist.plugins-base
+popd
+echo "Found the following linked-in plugin files:"
+cat $outdir/filelist.plugins-base | sed 's/^/    /g'
+
+# Generate list of header files, filtering ones that go to subpackages.
+outdir="`pwd`"
+pushd %{buildroot}
+find ./%{_includedir}/murphy | \
+grep -E -v '((pulse)|(ecore)|(glib)|(qt))-glue' | \
+sed 's#^./*#/#g' > $outdir/filelist.devel-includes
+popd
+
+# Replace the default sample/test config files with the packaging ones.
+rm -f %{buildroot}%{_sysconfdir}/murphy/*
+cp packaging.in/murphy-lua.conf %{buildroot}%{_sysconfdir}/murphy/murphy.conf
+cp packaging.in/murphy.lua      %{buildroot}%{_sysconfdir}/murphy/murphy.lua
+
+# Copy plugin configuration files in place.
+#mkdir -p %{buildroot}%{_sysconfdir}/murphy/plugins/amb
+#cp packaging.in/amb-config.lua \
+#%{buildroot}%{_sysconfdir}/murphy/plugins/amb/config.lua
+
+# Copy tmpfiles.d config file in place
+mkdir -p %{buildroot}%{_tmpfilesdir}
+cp packaging.in/murphyd.conf %{buildroot}%{_tmpfilesdir}
+
+# Copy the systemd files in place.
+#mkdir -p %%{buildroot}%%{_unitdir}
+mkdir -p %{buildroot}%{_unitdir_user}
+cp packaging.in/murphyd.service %{buildroot}%{_unitdir_user}
+
+%if %{with dbus}
+mkdir -p %{buildroot}%{_sysconfdir}/dbus-1/system.d
+sed "s/@TZ_SYS_USER_GROUP@/%{TZ_SYS_USER_GROUP}/g" \
+    packaging.in/org.Murphy.conf.in > packaging.in/org.Murphy.conf
+cp packaging.in/org.Murphy.conf \
+    %{buildroot}%{_sysconfdir}/dbus-1/system.d/org.Murphy.conf
+%endif
+
+# copy (experimental) GAM resource backend configuration files
+#mkdir -p %{buildroot}%{_sysconfdir}/murphy/gam
+#cp packaging.in/gam-*.names packaging.in/gam-*.tree \
+#    %{buildroot}%{_sysconfdir}/murphy/gam
+
+%clean
+rm -rf %{buildroot}
+
+%post
+/bin/systemctl --user enable --global murphyd.service
+setcap 'cap_net_admin=+ep' %{_bindir}/murphyd
+ldconfig
+
+%postun
+if [ "$1" = "0" ]; then
+systemctl --user disable --global murphyd.service
+fi
+ldconfig
+
+%if %{with glib}
+%post glib
+ldconfig
+
+%postun glib
+ldconfig
+%endif
+
+#%if %{with pulse}
+#%post pulse
+#ldconfig
+
+#%postun pulse
+#ldconfig
+#%endif
+
+#%if %{with ecore}
+#%post ecore
+#ldconfig
+
+#%postun ecore
+#ldconfig
+#%endif
+
+#%if %{with qt}
+#%post qt
+#ldconfig
+
+#%postun qt
+#ldconfig
+#%endif
+
+#%post gam
+#ldconfig
+
+#%postun gam
+#ldconfig
+
+%files -f filelist.plugins-base
+%defattr(-,root,root,-)
+%manifest murphy.manifest
+%{_bindir}/murphyd
+%config %{_sysconfdir}/murphy
+%{_unitdir_user}/murphyd.service
+%{_tmpfilesdir}/murphyd.conf
+#%if %{with audiosession}
+#%{_sbindir}/asm-bridge
+#%endif
+%if %{with dbus}
+%{_sysconfdir}/dbus-1/system.d
+%config %{_sysconfdir}/dbus-1/system.d/org.Murphy.conf
+%endif
+#%if %{with websockets}
+#%{_datadir}/murphy
+#%endif
+
+%{_libdir}/libmurphy-common.so.*
+%{_libdir}/libmurphy-core.so.*
+%{_libdir}/libmurphy-resolver.so.*
+%{_libdir}/libmurphy-resource.so.*
+%{_libdir}/libmurphy-resource-backend.so.*
+%if %{with lua}
+%{_libdir}/libmurphy-lua-utils.so.*
+%{_libdir}/libmurphy-lua-decision.so.*
+%endif
+%{_libdir}/libmurphy-domain-controller.so.*
+%{_libdir}/murphy/*.so.*
+%{_libdir}/libbreedline*.so.*
+%if %{with dbus}
+%{_libdir}/libmurphy-libdbus.so.*
+%{_libdir}/libmurphy-dbus-libdbus.so.*
+%endif
+#%if %{with sysmon}
+#%{_libdir}/libmurphy-libdbus.so.*
+#%endif
+
+%{_libdir}/murphy/plugins/plugin-domain-control.so
+#%{_libdir}/murphy/plugins/plugin-resource-asm.so
+%{_libdir}/murphy/plugins/plugin-resource-native.so
+
+%files devel -f filelist.devel-includes
+%defattr(-,root,root,-)
+%{_includedir}/murphy-db
+%{_libdir}/libmurphy-common.so
+%{_libdir}/libmurphy-core.so
+%{_libdir}/libmurphy-resolver.so
+%{_libdir}/libmurphy-resource.so
+%{_libdir}/libmurphy-resource-backend.so
+%if %{with lua}
+%{_libdir}/libmurphy-lua-utils.so
+%{_libdir}/libmurphy-lua-decision.so
+%endif
+%{_libdir}/libmurphy-domain-controller.so
+%{_libdir}/murphy/*.so
+%{_libdir}/pkgconfig/murphy-common.pc
+%{_libdir}/pkgconfig/murphy-core.pc
+%{_libdir}/pkgconfig/murphy-resolver.pc
+%if %{with lua}
+%{_libdir}/pkgconfig/murphy-lua-utils.pc
+%{_libdir}/pkgconfig/murphy-lua-decision.pc
+%endif
+%{_libdir}/pkgconfig/murphy-domain-controller.pc
+%{_libdir}/pkgconfig/murphy-db.pc
+%{_libdir}/pkgconfig/murphy-resource.pc
+%{_includedir}/breedline
+%{_libdir}/libbreedline*.so
+%{_libdir}/pkgconfig/breedline*.pc
+%if %{with dbus}
+%{_libdir}/libmurphy-libdbus.so
+%{_libdir}/libmurphy-dbus-libdbus.so
+%{_libdir}/pkgconfig/murphy-libdbus.pc
+%{_libdir}/pkgconfig/murphy-dbus-libdbus.pc
+%endif
+
+%files doc
+%defattr(-,root,root,-)
+%doc %{_datadir}/doc/murphy/AUTHORS
+%doc %{_datadir}/doc/murphy/CODING-STYLE
+%doc %{_datadir}/doc/murphy/ChangeLog
+%doc %{_datadir}/doc/murphy/NEWS
+%doc %{_datadir}/doc/murphy/README
+%license COPYING LICENSE-BSD
+
+#%if %{with pulse}
+#%files pulse
+#%defattr(-,root,root,-)
+#%{_libdir}/libmurphy-pulse.so.*
+#%manifest murphy.manifest
+
+#%files pulse-devel
+#%defattr(-,root,root,-)
+#%{_includedir}/murphy/common/pulse-glue.h
+#%{_libdir}/libmurphy-pulse.so
+#%{_libdir}/pkgconfig/murphy-pulse.pc
+#%endif
+
+#%if %{with ecore}
+#%files ecore
+#%defattr(-,root,root,-)
+#%{_libdir}/libmurphy-ecore.so.*
+#%manifest murphy.manifest
+
+#%files ecore-devel
+#%defattr(-,root,root,-)
+#%{_includedir}/murphy/common/ecore-glue.h
+#%{_libdir}/libmurphy-ecore.so
+#%{_libdir}/pkgconfig/murphy-ecore.pc
+#%endif
+
+%if %{with glib}
+%files glib
+%defattr(-,root,root,-)
+%{_libdir}/libmurphy-glib.so.*
+%manifest murphy.manifest
+
+%files glib-devel
+%defattr(-,root,root,-)
+%{_includedir}/murphy/common/glib-glue.h
+%{_libdir}/libmurphy-glib.so
+%{_libdir}/pkgconfig/murphy-glib.pc
+%endif
+
+#%if %{with qt}
+#%files qt
+#%defattr(-,root,root,-)
+#%{_libdir}/libmurphy-qt.so.*
+#%manifest murphy.manifest
+
+#%files qt-devel
+#%defattr(-,root,root,-)
+#%{_includedir}/murphy/common/qt-glue.h
+#%{_libdir}/libmurphy-qt.so
+#%{_libdir}/pkgconfig/murphy-qt.pc
+#%endif
+
+#%files gam
+#%defattr(-,root,root,-)
+#%{_libdir}/libmurphy-decision-tree.so.*
+#%{_libdir}/libmurphy-decision-tree.so.0.0.0
+#%{_libdir}/murphy/plugins/plugin-gam-resource-manager.so
+
+#%files gam-devel
+#%defattr(-,root,root,-)
+#%{_bindir}/decision-test
+#%{_bindir}/pattern-generator
+#%{_libdir}/libmurphy-decision-tree.so
+
+%files tests
+%defattr(-,root,root,-)
+%{_bindir}/resource-client
+%{_bindir}/resource-api-test
+%{_bindir}/resource-api-fuzz
+%{_bindir}/resource-context-create
+%{_bindir}/test-domain-controller
+%{_bindir}/murphy-console
+%manifest murphy.manifest
+
+#%if %{with icosyscon}
+#%files system-controller
+#%defattr(-,root,root,-)
+#%{_libdir}/murphy/plugins/plugin-system-controller.so
+#%manifest murphy.manifest
+#%endif
diff --git a/patches/lua/lua-5.1.4-specfile.patch b/patches/lua/lua-5.1.4-specfile.patch
new file mode 100644 (file)
index 0000000..3b8e35d
--- /dev/null
@@ -0,0 +1,79 @@
+*** rpmbuild/SPECS/lua.spec.orig       2014-03-09 23:16:11.119234628 +0200
+--- rpmbuild/SPECS/lua.spec    2014-03-09 23:16:20.243251349 +0200
+***************
+*** 1,3 ****
+--- 1,6 ----
++ %global lua_version 5.1.4
++ %global _prefix /usr/local/lua-%{lua_version}
++ 
+  Name:           lua
+  Version:        5.1.4
+  Release:        12%{?dist}
+***************
+*** 63,68 ****
+--- 65,72 ----
+  make %{?_smp_mflags} LIBS="-lm -ldl" luac_LDADD="liblua.la -lm -ldl"
+  # also remove readline from lua.pc
+  sed -i 's/-lreadline -lncurses //g' etc/lua.pc
++ # add path to %_libdir to lua.pc
++ sed -i 's#^Libs: #Libs: -L${libdir} #g' etc/lua.pc
+  
+  
+  %install
+***************
+*** 72,81 ****
+--- 76,102 ----
+  mkdir -p $RPM_BUILD_ROOT%{_libdir}/lua/5.1
+  mkdir -p $RPM_BUILD_ROOT%{_datadir}/lua/5.1
+  
++ # construct a config file to help the dynamic linker find us
++ mkdir -p $RPM_BUILD_ROOT/etc/ld.so.conf.d
++ echo "%{_libdir}" > $RPM_BUILD_ROOT/etc/ld.so.conf.d/lua-%{lua_version}.conf
++ 
++ # add a version-specific symlink for pkgconfig to find us
++ case %{_libdir} in
++     *lib64*) lib=lib64;;
++           *) lib=lib
++ esac
++ mkdir -p $RPM_BUILD_ROOT/usr/$lib/pkgconfig
++ ln -sf %_libdir/pkgconfig/lua.pc \
++     $RPM_BUILD_ROOT/usr/$lib/pkgconfig/lua-%{lua_version}.pc
+  
+  %clean
+  rm -rf $RPM_BUILD_ROOT
+  
++ %post
++ ldconfig
++ 
++ %postun
++ ldconfig
+  
+  %files
+  %defattr(-,root,root,-)
+***************
+*** 87,93 ****
+  %dir %{_libdir}/lua/5.1
+  %dir %{_datadir}/lua
+  %dir %{_datadir}/lua/5.1
+! 
+  
+  %files devel
+  %defattr(-,root,root,-)
+--- 108,114 ----
+  %dir %{_libdir}/lua/5.1
+  %dir %{_datadir}/lua
+  %dir %{_datadir}/lua/5.1
+! /etc/ld.so.conf.d/lua-%{lua_version}.conf
+  
+  %files devel
+  %defattr(-,root,root,-)
+***************
+*** 95,100 ****
+--- 116,122 ----
+  %{_includedir}/l*.hpp
+  %{_libdir}/liblua.so
+  %{_libdir}/pkgconfig/*.pc
++ /usr/lib*/pkgconfig/lua-*.pc
+  
+  %files static
+  %defattr(-,root,root,-)
diff --git a/patches/lua/lua-5.2.2-specfile.patch b/patches/lua/lua-5.2.2-specfile.patch
new file mode 100644 (file)
index 0000000..0bf3bee
--- /dev/null
@@ -0,0 +1,62 @@
+*** rpmbuild/SPECS/lua.spec.orig       2013-10-23 19:30:44.000000000 +0300
+--- rpmbuild/SPECS/lua.spec    2014-03-09 23:28:23.064213927 +0200
+***************
+*** 1,4 ****
+--- 1,6 ----
+  %global major_version 5.2
++ %global lua_version 5.2.2
++ %global _prefix /usr/local/lua-%{lua_version}
+  
+  Name:           lua
+  Version:        %{major_version}.2
+***************
+*** 65,70 ****
+--- 67,74 ----
+  # hack so that only /usr/bin/lua gets linked with readline as it is the
+  # only one which needs this and otherwise we get License troubles
+  make %{?_smp_mflags} LIBS="-lm -ldl" luac_LDADD="liblua.la -lm -ldl"
++ # add path to %_libdir to lua.pc
++ sed -i 's#^Libs: #Libs: -L${libdir} #g' src/lua.pc
+  
+  
+  %install
+***************
+*** 73,78 ****
+--- 77,102 ----
+  mkdir -p $RPM_BUILD_ROOT%{_libdir}/lua/%{major_version}
+  mkdir -p $RPM_BUILD_ROOT%{_datadir}/lua/%{major_version}
+  
++ # construct a config file to help the dynamic linker find us
++ mkdir -p $RPM_BUILD_ROOT/etc/ld.so.conf.d
++ echo "%{_libdir}" > $RPM_BUILD_ROOT/etc/ld.so.conf.d/lua-%{lua_version}.conf
++ 
++ # add a version-specific symlink for pkgconfig to find us
++ case %{_libdir} in
++     *lib64*) lib=lib64;;
++           *) lib=lib
++ esac
++ mkdir -p $RPM_BUILD_ROOT/usr/$lib/pkgconfig
++ ln -sf %_libdir/pkgconfig/lua.pc \
++     $RPM_BUILD_ROOT/usr/$lib/pkgconfig/lua-%{lua_version}.pc
++ 
++ 
++ %post
++ ldconfig
++ 
++ %postun
++ ldconfig
++ 
+  
+  %files
+  %doc README doc/*.html doc/*.css doc/*.gif doc/*.png
+***************
+*** 90,95 ****
+--- 114,121 ----
+  %{_includedir}/l*.hpp
+  %{_libdir}/liblua.so
+  %{_libdir}/pkgconfig/*.pc
++ /usr/lib*/pkgconfig/lua-*.pc
++ /etc/ld.so.conf.d/lua-%{lua_version}.conf
+  
+  %files static
+  %{_libdir}/*.a
diff --git a/scripts/cleanup-whitespace.sh b/scripts/cleanup-whitespace.sh
new file mode 100755 (executable)
index 0000000..bfab741
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+# Replace TABs with sequences of 8 spaces in all given files.
+replace_tabs() {
+    local _file _tmp
+
+    for _file in $*; do
+        _tmp=$_file.tabs
+        cp $_file $_tmp && \
+            cat $_tmp | \
+                sed 's/\t/        /g' > $_file && \
+        rm -f $_tmp
+    done
+}
+
+
+# Replaces lines containing only spaces with empty lines in all given files.
+strip_empty_lines() {
+    local _file _tmp
+
+    for _file in $*; do
+        _tmp=$_file.spaces
+        cp $_file $_tmp && \
+            cat $_tmp | \
+                sed 's/^ [ ]*$//g' > $_file && \
+        rm -f $_tmp
+    done
+}
+
+
+# Strip trailing white space from all the given files.
+strip_trailing_ws() {
+    local _file _tmp
+
+    for _file in $*; do
+        _tmp=$_file.spaces
+        cp $_file $_tmp && \
+            cat $_tmp | \
+                sed 's/ *$//g' > $_file && \
+        rm -f $_tmp
+    done
+}
+
+
+# Clean up TABS and empty lines in all given or found files.
+if [ -n "$*" ]; then
+    replace_tabs $* && \
+        strip_empty_lines $* && \
+            strip_trailing_ws $*
+else
+    files=$(find . -name \*.h -o -name \*.c)
+    replace_tabs $files && \
+        strip_empty_lines $files && \
+            strip_trailing_ws $files
+fi
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644 (file)
index 0000000..a9a5d9c
--- /dev/null
@@ -0,0 +1,1524 @@
+SUBDIRS         = murphy-db . \
+                 common/tests breedline/tests core/tests \
+                 core/lua-decision/tests resolver/tests \
+                 daemon/tests  plugins/tests
+
+AM_CFLAGS       = $(WARNING_CFLAGS) $(AM_CPPFLAGS) \
+                 -DSYSCONFDIR=\"@SYSCONFDIR@\" -DLIBDIR=\"@LIBDIR@\"
+MURPHY_CFLAGS   =
+pkgconfigdir    = ${libdir}/pkgconfig
+
+bin_PROGRAMS    =
+lib_LTLIBRARIES =
+pkgconfig_DATA  =
+EXTRA_DIST      =
+
+QUIET_GEN      = $(Q:@=@echo '  GEN   '$@;)
+
+LEXCOMPILE      = $(LEX) $(LFLAGS) $(AM_LFLAGS)
+YACCCOMPILE     = $(YACC) $(YFLAGS) $(AM_YFLAGS)
+
+AM_CPPFLAGS     = -I$(top_builddir) -I$(top_builddir)/src \
+                 -I$(top_builddir)/src/murphy-db/include
+BUILT_SOURCES   =
+
+###################################
+# murphy common library
+#
+
+lib_LTLIBRARIES += libmurphy-common.la
+EXTRA_DIST      += common/murphy-common.pc
+pkgconfig_DATA  += common/murphy-common.pc
+
+
+libmurphy_commonh_ladir      =         \
+               $(includedir)/murphy
+
+libmurphy_commonh_la_HEADERS =         \
+               common.h                \
+               config.h
+
+libmurphy_common_ladir      =          \
+               $(includedir)/murphy/common
+
+libmurphy_common_la_HEADERS =          \
+               common/macros.h         \
+               common/list.h           \
+               common/log.h            \
+               common/debug.h          \
+               common/debug-info.h     \
+               common/env.h            \
+               common/mm.h             \
+               common/hashtbl.h        \
+               common/process.h        \
+               common/mainloop.h       \
+               common/utils.h          \
+               common/file-utils.h     \
+               common/socket-utils.h   \
+               common/msg.h            \
+               common/refcnt.h         \
+               common/fragbuf.h        \
+               common/json.h           \
+               common/transport.h      \
+               common/tlv.h            \
+               common/native-types.h   \
+               common/mask.h
+
+libmurphy_common_la_REGULAR_SOURCES =          \
+               common/log.c                    \
+               common/debug.c                  \
+               common/env.c                    \
+               common/mm.c                     \
+               common/hashtbl.c                \
+               common/mainloop.c               \
+               common/utils.c                  \
+               common/file-utils.c             \
+               common/socket-utils.c           \
+               common/process.c                \
+               common/msg.c                    \
+               common/fragbuf.c                \
+               common/json.c                   \
+               common/transport.c              \
+               common/stream-transport.c       \
+               common/internal-transport.c     \
+               common/dgram-transport.c        \
+               common/tlv.c                    \
+               common/native-types.c
+
+libmurphy_common_la_SOURCES =                          \
+               $(libmurphy_common_la_REGULAR_SOURCES)
+
+libmurphy_common_la_CFLAGS  =          \
+               $(AM_CFLAGS)            \
+               $(JSON_CFLAGS)
+
+libmurphy_common_la_LDFLAGS =                                  \
+               -Wl,-version-script=linker-script.common        \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_common_la_LIBADD  =          \
+               $(JSON_LIBS)            \
+               -lrt
+
+libmurphy_common_la_DEPENDENCIES =     \
+               linker-script.common    \
+               $(filter %.la, $(libmurphy_common_la_LIBADD))
+
+libcommonincludedir      = $(includedir)/murphy/common
+libcommoninclude_HEADERS = $(libmurphy_common_la_HEADERS)
+
+if WEBSOCKETS_ENABLED
+libmurphy_common_la_HEADERS +=                 \
+               common/websocklib.h             \
+               common/websocket.h
+
+libmurphy_common_la_REGULAR_SOURCES +=         \
+               common/websocklib.c             \
+               common/websocket.c              \
+               common/wsck-transport.c
+
+libmurphy_common_la_CFLAGS +=                  \
+               $(WEBSOCKETS_CFLAGS)
+
+libmurphy_common_la_LIBADD +=                  \
+               $(WEBSOCKETS_LIBS)
+
+EXTRA_DIST     += $(webconsole_DATA);
+webconsoledir   = $(datadir)/murphy/webconsole
+webconsole_DATA =                              \
+               plugins/console/console.js      \
+               plugins/console/console.html
+endif
+
+
+# linker script generation
+linker-script.common: $(libmurphy_common_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_common_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.common
+
+###################################
+# murphy lua utilities
+#
+
+lib_LTLIBRARIES += libmurphy-lua-utils.la
+EXTRA_DIST      += core/lua-utils/murphy-lua-utils.pc
+pkgconfig_DATA  += core/lua-utils/murphy-lua-utils.pc
+
+libmurphy_lua_utils_ladir =                                    \
+               $(includedir)/murphy/core/lua-utils
+
+libmurphy_lua_utils_la_HEADERS =                               \
+               core/lua-utils/lua-utils.h                      \
+               core/lua-utils/strarray.h                       \
+               core/lua-utils/funcbridge.h                     \
+               core/lua-utils/object.h                         \
+               core/lua-utils/error.h                          \
+               core/lua-utils/include.h
+
+libmurphy_lua_utils_la_REGULAR_SOURCES =                       \
+               core/lua-utils/lua-utils.c                      \
+               core/lua-utils/strarray.c                       \
+               core/lua-utils/funcbridge.c                     \
+               core/lua-utils/object.c                         \
+               core/lua-utils/error.c                          \
+               core/lua-utils/include.c
+
+libmurphy_lua_utils_la_SOURCES =                               \
+               $(libmurphy_lua_utils_la_REGULAR_SOURCES)
+
+libmurphy_lua_utils_la_CFLAGS =                                        \
+               $(AM_CFLAGS)                                    \
+               $(LUA_CFLAGS)
+
+libmurphy_lua_utils_la_LDFLAGS =                               \
+               -Wl,-version-script=linker-script.lua-utils     \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_lua_utils_la_LIBADD =                                        \
+               libmurphy-common.la                             \
+               $(LUA_LIBS)
+
+libmurphy_lua_utils_la_DEPENDENCIES =                          \
+               linker-script.lua-utils                         \
+               $(filter %.la, $(libmurphy_lua_utils_la_LIBADD))
+
+# lua-utils linker script generation
+linker-script.lua-utils: $(libmurphy_lua_utils_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_lua_utils_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.lua-utils
+
+###################################
+# murphy lua decision network
+#
+
+lib_LTLIBRARIES += libmurphy-lua-decision.la
+EXTRA_DIST      += core/lua-decision/murphy-lua-decision.pc
+pkgconfig_DATA  += core/lua-decision/murphy-lua-decision.pc
+
+libmurphy_lua_decision_ladir =                                 \
+               $(includedir)/murphy/core/lua-decision
+
+libmurphy_lua_decision_la_HEADERS =                            \
+               core/lua-decision/mdb.h                         \
+               core/lua-decision/element.h
+
+libmurphy_lua_decision_la_REGULAR_SOURCES =                    \
+               core/lua-decision/mdb.c                         \
+               core/lua-decision/element.c
+
+libmurphy_lua_decision_la_SOURCES =                            \
+               $(libmurphy_lua_decision_la_REGULAR_SOURCES)
+
+libmurphy_lua_decision_la_CFLAGS =                             \
+               $(AM_CFLAGS)                                    \
+               $(LUA_CFLAGS)
+
+libmurphy_lua_decision_la_LDFLAGS =                            \
+               -Wl,-version-script=linker-script.lua-decision  \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_lua_decision_la_LIBADD =     \
+               libmurphy-common.la     \
+               libmurphy-lua-utils.la  \
+               murphy-db/mql/libmql.la \
+               murphy-db/mqi/libmqi.la \
+               murphy-db/mdb/libmdb.la \
+               $(LUA_LIBS)
+
+libmurphy_lua_decision_la_DEPENDENCIES =       \
+               linker-script.lua-decision      \
+               $(filter %.la, $(libmurphy_lua_decision_la_LIBADD))
+
+# lua-decision linker script generation
+linker-script.lua-decision: $(libmurphy_lua_decision_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_lua_decision_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.lua-decision
+
+###################################
+# murphy core library
+#
+
+lib_LTLIBRARIES += libmurphy-core.la
+EXTRA_DIST      += core/murphy-core.pc
+pkgconfig_DATA  += core/murphy-core.pc
+
+LUA_BINDINGS_SOURCES =                                         \
+               core/lua-bindings/lua-murphy.c          \
+               core/lua-bindings/lua-lua.c             \
+               core/lua-bindings/lua-plugin.c          \
+               core/lua-bindings/lua-log.c             \
+               core/lua-bindings/lua-console.c         \
+               core/lua-bindings/lua-bitwise.c         \
+               core/lua-bindings/lua-json.c            \
+               core/lua-bindings/lua-timer.c           \
+               core/lua-bindings/lua-event.c           \
+               core/lua-bindings/lua-deferred.c        \
+               core/lua-bindings/lua-sighandler.c      \
+               core/lua-bindings/lua-transport.c       \
+               core/lua-bindings/lua-env.c
+
+libmurphy_core_lua_bindings_ladir =                    \
+               $(includedir)/murphy/core/lua-bindings
+
+libmurphy_core_lua_bindings_la_HEADERS =               \
+               core/lua-bindings/murphy.h              \
+               core/lua-bindings/lua-json.h
+
+libmurphy_coreh_ladir      =           \
+               $(includedir)/murphy
+
+libmurphy_coreh_la_HEADERS =           \
+               core.h
+
+libmurphy_core_ladir =                 \
+               $(includedir)/murphy/core
+
+libmurphy_core_la_HEADERS =            \
+               core/context.h          \
+               core/plugin.h           \
+               core/console-command.h  \
+               core/console.h          \
+               core/scripting.h        \
+               core/method.h           \
+               core/auth.h             \
+               core/domain.h           \
+               core/domain-types.h
+
+libmurphy_core_la_REGULAR_SOURCES =    \
+               core/context.c          \
+               core/plugin.c           \
+               core/console.c          \
+               core/scripting.c        \
+               core/method.c           \
+               core/auth.c             \
+               core/auth-deny.c        \
+               core/domain.c           \
+               $(LUA_BINDINGS_SOURCES)
+
+if SMACK_ENABLED
+libmurphy_core_la_REGULAR_SOURCES +=   \
+               core/auth-smack.c
+endif
+
+libmurphy_core_la_SOURCES =            \
+               $(libmurphy_core_la_REGULAR_SOURCES)
+
+libmurphy_core_la_CFLAGS  =            \
+               $(AM_CFLAGS)            \
+               $(LUA_CFLAGS)           \
+               $(JSON_CFLAGS)          \
+               $(SMACK_CFLAGS)
+
+libmurphy_core_la_LDFLAGS =            \
+               -Wl,-version-script=linker-script.core \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_core_la_LIBADD  =                    \
+               libmurphy-common.la             \
+               libmurphy-lua-decision.la       \
+               libmurphy-lua-utils.la          \
+               murphy-db/mql/libmql.la         \
+               murphy-db/mqi/libmqi.la         \
+               murphy-db/mdb/libmdb.la         \
+               $(LUA_LIBS)                     \
+               $(SMACK_LIBS)                   \
+               -ldl
+
+libmurphy_core_la_DEPENDENCIES =               \
+               linker-script.core              \
+               $(filter %.la, $(libmurphy_core_la_LIBADD))
+
+# core linker script generation
+linker-script.core: $(libmurphy_core_la_HEADERS) \
+                   $(libmurphy_core_lua_bindings_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_core_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.core
+
+###################################
+# murphy dbus library
+#
+# Right now we have three variants of the murphy D-Bus library. The
+# original with leaky libdbus abstraction, a revised one that plugs
+# the most gaping abstraction holes, and one which uses systemd-bus,
+# the licensing-wise much easier/liberal D-Bus implementation from
+# systemd.
+#
+# All original code uses/used to use the libdbus-based one with leaky
+# abstraction. This would make it very difficult to move away from
+# the license-troubled libdbus even if/when a viable alternative (eg.
+# from the kdbus effort) became available. To get rid of this problem,
+# we need to get rid of the direct dependency on libdbus. Once that is
+# done, we can (hopefully) switch easily and transparently between
+# D-Bus libraries, if needed by touching only one component, the
+# murphy-dbus library.
+#
+# To recap these are the different library variants:
+#
+# 1) murphy-libdbus
+#    This is the original murphy-dbus library. It uses libdbus as the
+#    underlying D-Bus client library implementation. It does not provide
+#    a proper abstraction on top of libdbus. Messages, message building,
+#    and parsing used DBusMessage.
+#
+# 2) murphy-dbus-libdbus
+#    This is a modified version of murphy-libdbus which provides an
+#    additional abstraction for messages, message building and parsing.
+#
+# 3) murphy-dbus-systemdbus
+#    This library provides an identical API to murphy-dbus-libdbus but
+#    it uses the systemd dbus library implementation (systemd-bus) as
+#    the underlying D-Bus client library implementation.
+#
+# So all current code still using libdbus for message building/parsing
+# needs to get rid of it and changed to use murphy-dbus-libdbus.
+#
+
+# original libdbus-based library
+murphy_libdbus_headers =               \
+               common/libdbus.h
+
+if LIBDBUS_ENABLED
+lib_LTLIBRARIES += libmurphy-libdbus.la
+EXTRA_DIST      += common/murphy-libdbus.pc
+pkgconfig_DATA  += common/murphy-libdbus.pc
+
+libmurphy_libdbus_ladir =              \
+               $(includedir)/murphy/common
+
+libmurphy_libdbus_la_HEADERS = $(murphy_libdbus_headers)
+
+libmurphy_libdbus_la_REGULAR_SOURCES =         \
+               common/libdbus.c                \
+               common/libdbus-glue.c
+
+libmurphy_libdbus_la_SOURCES =         \
+               $(libmurphy_libdbus_la_REGULAR_SOURCES)
+
+libmurphy_libdbus_la_CFLAGS =          \
+               $(AM_CFLAGS)            \
+               $(LIBDBUS_CFLAGS)
+
+libmurphy_libdbus_la_LDFLAGS =         \
+               -Wl,-version-script=linker-script.libdbus \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_libdbus_la_LIBADD  =                 \
+               libmurphy-common.la     \
+               $(LIBDBUS_LIBS)         \
+               -lrt
+
+libmurphy_libdbus_la_DEPENDENCIES =    \
+               linker-script.libdbus   \
+               $(filter %.la, $(libmurphy_libdbus_la_LIBADD))
+
+libmurphy_libdbusdir      = $(includedir)/murphy/dbus
+libmurphy_libdbus_HEADERS = $(libmurphy_libdbus_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.libdbus: $(murphy_libdbus_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_libdbus_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.libdbus
+
+# libdbus-based library with full abstraction
+murphy_dbus_libdbus_headers =          \
+               common/dbus-libdbus.h   \
+               common/dbus-error.h
+
+if LIBDBUS_ENABLED
+lib_LTLIBRARIES += libmurphy-dbus-libdbus.la
+EXTRA_DIST      += common/murphy-dbus-libdbus.pc
+pkgconfig_DATA  += common/murphy-dbus-libdbus.pc
+
+libmurphy_dbus_libdbus_ladir =         \
+               $(includedir)/murphy/common
+
+libmurphy_dbus_libdbus_la_HEADERS = $(murphy_dbus_libdbus_headers)
+
+libmurphy_dbus_libdbus_la_REGULAR_SOURCES =    \
+               common/dbus-libdbus.c           \
+               common/dbus-libdbus-glue.c      \
+               common/dbus-libdbus-transport.c
+
+libmurphy_dbus_libdbus_la_SOURCES =            \
+               $(libmurphy_dbus_libdbus_la_REGULAR_SOURCES)
+
+libmurphy_dbus_libdbus_la_CFLAGS =     \
+               $(AM_CFLAGS)            \
+               $(LIBDBUS_CFLAGS)
+
+libmurphy_dbus_libdbus_la_LDFLAGS =    \
+               -Wl,-version-script=linker-script.dbus-libdbus \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_dbus_libdbus_la_LIBADD  =    \
+               libmurphy-common.la     \
+               -lrt $(LIBDBUS_LIBS)
+
+libmurphy_dbus_libdbus_la_DEPENDENCIES =       \
+               linker-script.dbus-libdbus      \
+               $(filter %.la, $(libmurphy_dbus_libdbus_la_LIBADD))
+
+libmurphy_dbus_libdbusdir      = $(includedir)/murphy/dbus
+libmurphy_dbus_libdbus_HEADERS = $(libmurphy_dbus_libdbus_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.dbus-libdbus: $(murphy_dbus_libdbus_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_dbus_libdbus_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.dbus-libdbus
+
+# systemd-bus based library
+murphy_dbus_sdbus_headers =                    \
+               common/dbus-sdbus.h             \
+               common/dbus-error.h
+
+if SDBUS_ENABLED
+lib_LTLIBRARIES += libmurphy-dbus-sdbus.la
+EXTRA_DIST      += common/murphy-dbus-sdbus.pc
+pkgconfig_DATA  += common/murphy-dbus-sdbus.pc
+
+libmurphy_dbus_sdbus_ladir      =              \
+               $(includedir)/murphy/common
+
+libmurphy_dbus_sdbus_la_HEADERS = $(murphy_dbus_sdbus_headers)
+
+libmurphy_dbus_sdbus_la_REGULAR_SOURCES =      \
+               common/dbus-sdbus.c             \
+               common/dbus-sdbus-glue.c        \
+               common/dbus-sdbus-transport.c
+
+libmurphy_dbus_sdbus_la_SOURCES =              \
+               $(libmurphy_dbus_sdbus_la_REGULAR_SOURCES)
+
+libmurphy_dbus_sdbus_la_CFLAGS  =      \
+               $(AM_CFLAGS)            \
+               $(SDBUS_CFLAGS)
+
+libmurphy_dbus_sdbus_la_LDFLAGS =              \
+               -Wl,-version-script=linker-script.dbus-sdbus \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_dbus_sdbus_la_LIBADD  =              \
+               -lrt $(SDBUS_LIBS)
+
+libmurphy_dbus_sdbus_la_DEPENDENCIES =         \
+               linker-script.dbus-sdbus        \
+               $(filter %.la, $(libmurphy_dbus_sdbus_la_LIBADD))
+
+libmurphy_dbus_sdbusdir      = $(includedir)/murphy/dbus
+libmurphy_dbus_sdbus_HEADERS = $(libmurphy_dbus_sdbus_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.dbus-sdbus: $(murphy_dbus_sdbus_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_dbus_sdbus_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.sd-bus
+
+###################################
+# murphy pulse glue library
+#
+
+murphy_pulse_headers =                 \
+               common/pulse-glue.h     \
+               common/pulse-subloop.h
+
+if PULSE_ENABLED
+lib_LTLIBRARIES += libmurphy-pulse.la
+EXTRA_DIST      += common/murphy-pulse.pc
+pkgconfig_DATA  += common/murphy-pulse.pc
+
+libmurphy_pulse_ladir      =           \
+               $(includedir)/murphy/common
+
+libmurphy_pulse_la_HEADERS = $(murphy_pulse_headers)
+
+libmurphy_pulse_la_SOURCES =           \
+               common/pulse-glue.c     \
+               common/pulse-subloop.c
+
+libmurphy_pulse_la_CFLAGS  =           \
+               $(AM_CFLAGS)            \
+               $(PULSE_CFLAGS)
+
+libmurphy_pulse_la_LDFLAGS =           \
+               -Wl,-version-script=linker-script.pulse \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_pulse_la_LIBADD  =           \
+               libmurphy-common.la     \
+               $(PULSE_LIBS)
+
+libmurphy_pulse_la_DEPENDENCIES =      \
+               linker-script.pulse     \
+               $(filter %.la, $(libmurphy_pulse_la_LIBADD))
+
+libpulseincludedir      = $(includedir)/murphy/pulse
+libpulseinclude_HEADERS = $(libmurphy_pulse_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.pulse: $(murphy_pulse_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_pulse_la_CFLAGS)" \
+                -p '^mrp_|^_mrp|^pa_' -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.pulse
+
+###################################
+# murphy ecore glue library
+#
+
+murphy_ecore_headers =                 \
+               common/ecore-glue.h
+
+if ECORE_ENABLED
+lib_LTLIBRARIES += libmurphy-ecore.la
+EXTRA_DIST      += common/murphy-ecore.pc
+pkgconfig_DATA  += common/murphy-ecore.pc
+
+libmurphy_ecore_ladir      =           \
+               $(includedir)/murphy/common
+
+libmurphy_ecore_la_HEADERS = $(murphy_ecore_headers)
+
+libmurphy_ecore_la_SOURCES =           \
+               common/ecore-glue.c
+
+libmurphy_ecore_la_CFLAGS  =           \
+               $(AM_CFLAGS)            \
+               $(ECORE_CFLAGS)
+
+libmurphy_ecore_la_LDFLAGS =           \
+               -Wl,-version-script=linker-script.ecore \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_ecore_la_LIBADD  =           \
+               libmurphy-common.la     \
+               $(ECORE_LIBS)
+
+libmurphy_ecore_la_DEPENDENCIES =      \
+               linker-script.ecore     \
+               $(filter %.la, $(libmurphy_ecore_la_LIBADD))
+
+libecoreincludedir      = $(includedir)/murphy/ecore
+libecoreinclude_HEADERS = $(libmurphy_ecore_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.ecore: $(murphy_ecore_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_ecore_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.ecore
+
+###################################
+# murphy glib glue library
+#
+
+murphy_glib_headers =                  \
+               common/glib-glue.h
+
+if GLIB_ENABLED
+lib_LTLIBRARIES += libmurphy-glib.la
+EXTRA_DIST      += common/murphy-glib.pc
+pkgconfig_DATA  += common/murphy-glib.pc
+
+libmurphy_glib_ladir      =            \
+               $(includedir)/murphy/common
+
+libmurphy_glib_la_HEADERS = $(murphy_glib_headers)
+
+libmurphy_glib_la_SOURCES =            \
+               common/glib-glue.c
+
+libmurphy_glib_la_CFLAGS  =            \
+               $(AM_CFLAGS)            \
+               $(GLIB_CFLAGS)
+
+libmurphy_glib_la_LDFLAGS =            \
+               -Wl,-version-script=linker-script.glib \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_glib_la_LIBADD  =            \
+               libmurphy-common.la     \
+               $(GLIB_LIBS)
+
+libmurphy_glib_la_DEPENDENCIES =       \
+               linker-script.glib      \
+               $(filter %.la, $(libmurphy_glib_la_LIBADD))
+
+libglibincludedir      = $(includedir)/murphy/glib
+libglibinclude_HEADERS = $(libmurphy_glib_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.glib: $(murphy_glib_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_glib_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.glib
+
+###################################
+# murphy qt glue library
+#
+
+murphy_qt_headers      =               \
+               common/qt-glue.h
+
+if QT_ENABLED
+lib_LTLIBRARIES += libmurphy-qt.la
+EXTRA_DIST      += common/murphy-qt.pc
+pkgconfig_DATA  += common/murphy-qt.pc
+
+BUILT_SOURCES   += common/qt-glue-priv.moc.h
+
+libmurphy_qt_ladir      =              \
+               $(includedir)/murphy/common
+
+libmurphy_qt_la_HEADERS = $(murphy_qt_headers)
+
+libmurphy_qt_la_SOURCES =              \
+               common/qt-glue.cpp
+
+libmurphy_qt_la_CPPFLAGS  =            \
+               $(AM_CFLAGS)            \
+               $(QTCORE_CFLAGS)
+
+libmurphy_qt_la_LDFLAGS =              \
+               -Wl,-version-script=linker-script.qt \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_qt_la_LIBADD  = $(QTCORE_LIBS)
+
+libmurphy_qt_la_DEPENDENCIES =         \
+               linker-script.qt        \
+               $(filter %.la, $(libmurphy_qt_la_LIBADD))
+
+libqtincludedir      = $(includedir)/murphy/qt
+libqtinclude_HEADERS = $(libmurphy_qt_la_HEADERS)
+
+# run moc to generate Qt meta objects
+common/qt-glue-priv.moc.h: common/qt-glue-priv.h
+       $(QUIET_GEN)$(QT_MOC) -DQT_ENABLED=1 $< -o$@
+endif
+
+# linker script generation
+linker-script.qt: $(murphy_qt_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_qt_la_CPPFLAGS) -DQT_ENABLED=1" \
+               -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.qt
+
+###################################
+# murphy resolver library
+#
+
+lib_LTLIBRARIES += libmurphy-resolver.la
+EXTRA_DIST      += resolver/murphy-resolver.pc
+pkgconfig_DATA  += resolver/murphy-resolver.pc
+
+BUILT_SOURCES   += resolver/scanner.c resolver/parser.c
+
+libmurphy_resolver_ladir =                     \
+               $(includedir)/murphy/resolver
+
+libmurphy_resolver_la_HEADERS =                        \
+               resolver/resolver.h
+
+SIMPLE_SCRIPT_SOURCES =                                        \
+               resolver/scripting/simple/simple-script.c       \
+               resolver/scripting/simple/simple-scanner.c      \
+               resolver/scripting/simple/simple-parser.c       \
+               resolver/scripting/simple/call.c                \
+               resolver/scripting/simple/builtins.c
+
+libmurphy_resolver_la_REGULAR_SOURCES =                                \
+               resolver/resolver.c                             \
+               resolver/parser.c                               \
+               resolver/scanner.c                              \
+               resolver/target.c                               \
+               resolver/target-sorter.c                        \
+               resolver/fact.c                                 \
+               resolver/events.c                               \
+               resolver/console.c                              \
+               $(SIMPLE_SCRIPT_SOURCES)
+
+libmurphy_resolver_la_SOURCES =                        \
+               $(libmurphy_resolver_la_REGULAR_SOURCES)
+
+libmurphy_resolver_la_CFLAGS =                 \
+               $(AM_CFLAGS)                    \
+               $(JSON_CFLAGS)
+
+libmurphy_resolver_la_LDFLAGS =                        \
+               -Wl,-version-script=linker-script.resolver \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_resolver_la_LIBADD =         \
+               libmurphy-core.la       \
+               libmurphy-common.la     \
+               murphy-db/mql/libmql.la \
+               murphy-db/mqi/libmqi.la \
+               murphy-db/mdb/libmdb.la
+
+libmurphy_resolver_la_DEPENDENCIES =   \
+               linker-script.resolver  \
+               $(filter %.la, $(libmurphy_resolver_la_LIBADD))
+
+# resolver lexical analyser and parser generation
+RESOLVER_PARSER_PREFIX = yy_res_
+
+resolver/scanner.c: resolver/scanner.l resolver/parser.c
+       $(LEXCOMPILE) -P $(RESOLVER_PARSER_PREFIX) $<
+       mv lex.$(RESOLVER_PARSER_PREFIX).c $@
+
+resolver/parser.h resolver/parser.c: resolver/parser.y
+       $(YACCCOMPILE) -p $(RESOLVER_PARSER_PREFIX) $< -o resolver/parser.c
+
+# resolver linker script generation
+linker-script.resolver: $(libmurphy_resolver_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_resolver_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.resolver
+
+# simple interpreter lexical analyser and parser generation
+SIMPLE_PARSER_PREFIX  = yy_smpl_
+BUILT_SOURCES        += resolver/scripting/simple/simple-scanner.c \
+                        resolver/scripting/simple/simple-parser.c
+
+resolver/scripting/simple/simple-scanner.c:            \
+    resolver/scripting/simple/simple-scanner.l         \
+    resolver/scripting/simple/simple-parser.c
+       $(LEXCOMPILE) -P $(SIMPLE_PARSER_PREFIX) $<
+       mv lex.$(SIMPLE_PARSER_PREFIX).c $@
+
+# simple interpreter parser generation
+resolver/scripting/simple/simple-parser.h      \
+resolver/scripting/simple/simple-parser.c:     \
+    resolver/scripting/simple/simple-parser.y
+       $(YACCCOMPILE) -p $(SIMPLE_PARSER_PREFIX) $< -o \
+           resolver/scripting/simple/simple-parser.c
+
+###################################
+# murphy backend resource library
+#
+
+if BUILD_RESOURCES
+RESOURCE_LIBRARY = libmurphy-resource-backend.la
+else
+RESOURCE_LIBRARY =
+endif
+
+if BUILD_RESOURCES
+lib_LTLIBRARIES += $(RESOURCE_LIBRARY)
+
+libmurphy_resource_backend_ladir =                     \
+               $(includedir)/murphy/resource
+
+libmurphy_resource_backend_la_HEADERS =                        \
+               resource/data-types.h           \
+               resource/common-api.h           \
+               resource/client-api.h           \
+               resource/manager-api.h          \
+               resource/config-api.h           \
+               resource/protocol.h
+
+libmurphy_resource_backend_la_REGULAR_SOURCES =                \
+               resource/attribute.c            \
+               resource/resource.c             \
+               resource/resource-set.c         \
+               resource/application-class.c    \
+               resource/resource-owner.c       \
+               resource/resource-client.c      \
+               resource/zone.c                 \
+               resource/config-lua.c           \
+               resource/resource-lua.c         \
+               resource/lua-resource.c
+
+libmurphy_resource_backend_la_SOURCES =                        \
+               $(libmurphy_resource_backend_la_REGULAR_SOURCES)
+
+libmurphy_resource_backend_la_CFLAGS =         \
+               $(AM_CFLAGS)                    \
+               $(LUA_CFLAGS)
+
+libmurphy_resource_backend_la_LDFLAGS =                        \
+               -Wl,-version-script=linker-script.resource_backend \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_resource_backend_la_LIBADD =     \
+               libmurphy-core.la          \
+               libmurphy-common.la        \
+               libmurphy-lua-utils.la     \
+               murphy-db/mql/libmql.la    \
+               murphy-db/mqi/libmqi.la    \
+               murphy-db/mdb/libmdb.la    \
+               $(LUA_LIBS)
+
+libmurphy_resource_backend_la_DEPENDENCIES =   \
+               linker-script.resource_backend  \
+               $(filter %.la, $(libmurphy_resource_backend_la_LIBADD))
+endif
+
+# resource linker script generation
+linker-script.resource_backend: $(libmurphy_resource_backend_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_resource_backend_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.resource_backend
+
+##########################
+# readline replacement
+#
+lib_LTLIBRARIES += libbreedline.la
+EXTRA_DIST      += breedline/breedline.pc
+pkgconfig_DATA  += breedline/breedline.pc
+
+libbreedline_ladir      =              \
+               $(includedir)/breedline
+
+libbreedline_la_HEADERS =              \
+               breedline/breedline.h   \
+               breedline/macros.h
+
+libbreedline_la_SOURCES =              \
+               breedline/breedline.c
+
+libbreedline_la_CFLAGS  =              \
+               $(AM_CFLAGS)
+
+libbreedline_la_LDFLAGS =                                      \
+               -Wl,-version-script=linker-script.breedline     \
+               -version-info @MURPHY_VERSION_INFO@
+
+libbreedline_la_LIBADD =
+
+libbreedline_la_DEPENDENCIES =                 \
+               linker-script.breedline         \
+               $(filter %.la, $(libbreedline_la_LIBADD))
+
+libbreedlineincludedir      = $(includedir)/breedline
+libbreedlineinclude_HEADERS = $(libbreedline_la_HEADERS)
+
+# linker script generation
+linker-script.breedline: $(libbreedline_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libbreedline_la_CFLAGS)" -p '^brl_' -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.breedline
+
+##########################
+# breedline murphy glue library
+#
+lib_LTLIBRARIES += libbreedline-murphy.la
+EXTRA_DIST      += breedline/breedline-murphy.pc
+pkgconfig_DATA  += breedline/breedline-murphy.pc
+
+libbreedline_murphy_ladir      = $(includedir)/breedline
+
+libbreedline_murphy_la_HEADERS = breedline/breedline-murphy.h
+libbreedline_murphy_la_SOURCES = breedline/breedline-murphy.c
+libbreedline_murphy_la_CFLAGS  = $(AM_CFLAGS)
+
+libbreedline_murphy_la_LDFLAGS =       \
+               -Wl,-version-script=linker-script.breedline-murphy      \
+               -version-info @MURPHY_VERSION_INFO@
+
+libbreedline_murphy_la_LIBADD =        \
+               libmurphy-common.la     \
+               libbreedline.la
+
+libbreedline_murphy_la_DEPENDENCIES =          \
+               linker-script.breedline-murphy  \
+               $(filter %.la,$(libbreedline_murphy_la_LIBADD))
+
+libbreedline_murphyincludedir      = $(includedir)/breedline
+libbreedline_murphyinclude_HEADERS = $(libbreedline_murphy_la_HEADERS)
+
+# linker script generation
+linker-script.breedline-murphy: $(libbreedline_murphy_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+           -P "$(CC)" -c "$(libbreedline_murphy_la_CFLAGS)" -p '^brl_' -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.breedline-murphy
+
+##########################
+# breedline glib glue library
+#
+
+breedline_glib_headers =       \
+               breedline/breedline-glib.h
+
+if GLIB_ENABLED
+lib_LTLIBRARIES += libbreedline-glib.la
+EXTRA_DIST      += breedline/breedline-glib.pc
+pkgconfig_DATA  += breedline/breedline-glib.pc
+
+libbreedline_glib_ladir      = $(includedir)/breedline
+
+libbreedline_glib_la_HEADERS = $(breedline_glib_headers)
+libbreedline_glib_la_SOURCES = breedline/breedline-glib.c
+libbreedline_glib_la_CFLAGS  = $(AM_CFLAGS) $(GLIB_CFLAGS)
+
+libbreedline_glib_la_LDFLAGS = \
+               -Wl,-version-script=linker-script.breedline-glib        \
+               -version-info @MURPHY_VERSION_INFO@
+
+libbreedline_glib_la_LIBADD =          \
+               libbreedline.la         \
+               $(GLIB_LIBS)
+
+libbreedline_glib_la_DEPENDENCIES =            \
+               linker-script.breedline-glib    \
+               $(filter %.la, $(libbreedline_glib_la_LIBADD))
+
+libbreedline_glibincludedir      = $(includedir)/breedline
+libbreedline_glibinclude_HEADERS = $(libbreedline_glib_la_HEADERS)
+endif
+
+# linker script generation
+linker-script.breedline-glib: $(breedline_glib_headers)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libbreedline_glib_la_CFLAGS)" -p '^brl_' \
+               -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.breedline-glib
+
+###################################
+# murphy resource external library
+
+# TODO: put this under conditional compilation
+
+lib_LTLIBRARIES += libmurphy-resource.la
+EXTRA_DIST      += plugins/resource-native/libmurphy-resource/murphy-resource.pc
+pkgconfig_DATA  += plugins/resource-native/libmurphy-resource/murphy-resource.pc
+
+libmurphy_resource_ladir =          \
+        $(includedir)/murphy/plugins/resource-native/libmurphy-resource
+
+libmurphy_resource_la_HEADERS =         \
+        plugins/resource-native/libmurphy-resource/resource-api.h     \
+        plugins/resource-native/libmurphy-resource/resource-private.h
+
+libmurphy_resource_la_SOURCES =         \
+        plugins/resource-native/libmurphy-resource/resource.c \
+        plugins/resource-native/libmurphy-resource/attribute.c \
+        plugins/resource-native/libmurphy-resource/rset.c \
+        plugins/resource-native/libmurphy-resource/string_array.c \
+        plugins/resource-native/libmurphy-resource/message.c \
+       plugins/resource-native/libmurphy-resource/resource-log.c
+
+libmurphy_resource_la_CFLAGS =          \
+        $(AM_CFLAGS)
+
+libmurphy_resource_la_LDFLAGS =        \
+               -Wl,-version-script=linker-script.resource      \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_resource_la_LIBADD =      \
+               libmurphy-common.la
+
+libmurphy_resource_la_DEPENDENCIES =   \
+               libmurphy-common.la     \
+               linker-script.resource  \
+               $(filter %.la, $(libmurphy_resource_la_LIBADD))
+
+# linker script generation
+linker-script.resource: $(libmurphy_resource_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_resource_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.resource
+
+# resource-api-test
+bin_PROGRAMS += resource-api-test
+
+resource_api_test_SOURCES = plugins/resource-native/libmurphy-resource/api_test.c
+resource_api_test_CFLAGS  = $(AM_CFLAGS)
+resource_api_test_LDADD   = libmurphy-common.la libmurphy-resource.la
+
+# resource-api-fuzz
+bin_PROGRAMS += resource-api-fuzz
+
+resource_api_fuzz_SOURCES = plugins/resource-native/libmurphy-resource/resource-fuzz.c
+resource_api_fuzz_CFLAGS  = $(AM_CFLAGS)
+resource_api_fuzz_LDADD   = libmurphy-common.la libmurphy-resource.la
+
+# context-create
+bin_PROGRAMS += resource-context-create
+
+resource_context_create_SOURCES = plugins/resource-native/libmurphy-resource/context-create.c
+resource_context_create_CFLAGS  = $(AM_CFLAGS)
+resource_context_create_LDADD   = libmurphy-common.la libmurphy-resource.la
+
+###################################
+# murphy plugins
+#
+
+BUILTIN_PLUGINS    =
+BUILTIN_CFLAGS     = -D__MURPHY_BUILTIN_PLUGIN__ $(AM_CFLAGS)
+BUILTIN_LIBS       =
+
+LINKEDIN_PLUGINS   =
+
+plugin_LTLIBRARIES =
+plugindir          = $(libdir)/murphy/plugins
+
+
+# test plugin
+TEST_PLUGIN_SOURCES = plugins/plugin-test.c
+TEST_PLUGIN_CFLAGS  =
+TEST_PLUGIN_LIBS    =
+
+if !DISABLED_PLUGIN_TEST
+if BUILTIN_PLUGIN_TEST
+BUILTIN_PLUGINS += $(TEST_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(TEST_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(TEST_PLUGIN_LIBS)
+else
+plugin_test_la_SOURCES = $(TEST_PLUGIN_SOURCES)
+plugin_test_la_CFLAGS  = $(TEST_PLUGIN_CFLAGS) $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_test_la_LDFLAGS = -module -avoid-version
+plugin_test_la_LIBADD  = $(TEST_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES    += plugin-test.la
+endif
+endif
+
+# dbus plugin
+if LIBDBUS_ENABLED
+DBUS_PLUGIN_SOURCES = plugins/plugin-dbus.c
+DBUS_PLUGIN_CFLAGS  = $(LIBDBUS_CFLAGS)
+DBUS_PLUGIN_LIBS    = libmurphy-dbus-libdbus.la $(LIBDBUS_LIBS)
+
+if !DISABLED_PLUGIN_DBUS
+if BUILTIN_PLUGIN_DBUS
+BUILTIN_PLUGINS += $(DBUS_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(DBUS_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(DBUS_PLUGIN_LIBS)
+else
+plugin_dbus_la_SOURCES = $(DBUS_PLUGIN_SOURCES)
+plugin_dbus_la_CFLAGS  = $(DBUS_PLUGIN_CFLAGS) $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_dbus_la_LDFLAGS = -module -avoid-version
+plugin_dbus_la_LIBADD  = $(DBUS_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES    += plugin-dbus.la
+endif
+endif
+endif
+
+if GLIB_ENABLED
+# glib plugin
+GLIB_PLUGIN_SOURCES = plugins/plugin-glib.c
+GLIB_PLUGIN_CFLAGS  = $(GLIB_CFLAGS)
+GLIB_PLUGIN_LIBS    = $(GLIB_LIBS)
+
+if !DISABLED_PLUGIN_GLIB
+if BUILTIN_PLUGIN_GLIB
+BUILTIN_PLUGINS += $(GLIB_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(GLIB_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(GLIB_PLUGIN_LIBS)
+else
+plugin_glib_la_SOURCES = $(GLIB_PLUGIN_SOURCES)
+plugin_glib_la_CFLAGS  = $(GLIB_PLUGIN_CFLAGS) $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_glib_la_LDFLAGS = -module -avoid-version
+plugin_glib_la_LIBADD  = $(GLIB_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES    += plugin-glib.la
+endif
+endif
+endif
+
+# resource-dbus plugin
+if BUILD_RESOURCES
+if LIBDBUS_ENABLED
+RESOURCE_DBUS_PLUGIN_SOURCES = plugins/plugin-resource-dbus.c
+RESOURCE_DBUS_PLUGIN_CFLAGS  = $(LIBDBUS_CFLAGS)
+RESOURCE_DBUS_PLUGIN_LIBS    =                                 \
+                               libmurphy-dbus-libdbus.la       \
+                               libmurphy-core.la               \
+                               libmurphy-common.la             \
+                               $(RESOURCE_LIBRARY)
+
+if !DISABLED_PLUGIN_RESOURCE_DBUS
+if BUILTIN_PLUGIN_RESOURCE_DBUS
+BUILTIN_PLUGINS += $(RESOURCE_DBUS_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(RESOURCE_DBUS_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(RESOURCE_DBUS_PLUGIN_LIBS)
+else
+plugin_resource_dbus_la_SOURCES = $(RESOURCE_DBUS_PLUGIN_SOURCES)
+plugin_resource_dbus_la_CFLAGS  = $(RESOURCE_DBUS_PLUGIN_CFLAGS) $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_resource_dbus_la_LDFLAGS = -module -avoid-version
+plugin_resource_dbus_la_LIBADD  = $(RESOURCE_DBUS_PLUGIN_LIBS)
+plugin_LTLIBRARIES    += plugin-resource-dbus.la
+endif
+endif
+endif
+endif
+
+# resource-wrt plugin
+if BUILD_RESOURCES
+if WEBSOCKETS_ENABLED
+RESOURCE_WRT_PLUGIN_SOURCES = plugins/resource-wrt/plugin-resource-wrt.c
+RESOURCE_WRT_PLUGIN_CFLAGS  = $(WEBSOCKETS_CFLAGS)
+RESOURCE_WRT_PLUGIN_LIBS    =
+
+if !DISABLED_PLUGIN_RESOURCE_WRT
+if BUILTIN_PLUGIN_RESOURCE_WRT
+BUILTIN_PLUGINS += $(RESOURCE_WRT_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(RESOURCE_WRT_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(RESOURCE_WRT_PLUGIN_LIBS)
+else
+plugin_resource_wrt_la_SOURCES = $(RESOURCE_WRT_PLUGIN_SOURCES)
+plugin_resource_wrt_la_CFLAGS  = $(RESOURCE_WRT_PLUGIN_CFLAGS) \
+                                $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_resource_wrt_la_LDFLAGS = -module -avoid-version
+plugin_resource_wrt_la_LIBADD  = $(RESOURCE_WRT_PLUGIN_LIBS)
+plugin_LTLIBRARIES    += plugin-resource-wrt.la
+
+EXTRA_DIST       += $(resource_wrt_DATA);
+resource_wrtdir   = $(datadir)/murphy/resource-wrt
+resource_wrt_DATA =                                    \
+               plugins/resource-wrt/resource-api.js    \
+               plugins/resource-wrt/resource-test.html
+
+endif
+endif
+endif
+endif
+
+# console plugin
+CONSOLE_PLUGIN_REGULAR_SOURCES = plugins/console/plugin-console.c
+CONSOLE_PLUGIN_SOURCES         = $(CONSOLE_PLUGIN_REGULAR_SOURCES)
+CONSOLE_PLUGIN_CFLAGS                 =
+CONSOLE_PLUGIN_LIBS                   =
+
+if CONSOLE_ENABLED
+if !DISABLED_PLUGIN_CONSOLE
+if BUILTIN_PLUGIN_CONSOLE
+BUILTIN_PLUGINS += $(CONSOLE_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(CONSOLE_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(CONSOLE_PLUGIN_LIBS)
+else
+plugin_console_la_SOURCES = $(CONSOLE_PLUGIN_SOURCES)
+plugin_console_la_CFLAGS  = $(CONSOLE_PLUGIN_CFLAGS) \
+                           $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_console_la_LDFLAGS = -module -avoid-version
+plugin_console_la_LIBADD  = $(CONSOLE_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES    += plugin-console.la
+endif
+endif
+endif
+
+# native resource plugin
+if BUILD_RESOURCES
+PLUGIN_RESOURCE_NATIVE_REGULAR_SOURCES =                               \
+               plugins/resource-native/plugin-resource-native.c
+PLUGIN_RESOURCE_NATIVE_SOURCES =                                       \
+               $(PLUGIN_RESOURCE_NATIVE_REGULAR_SOURCES)
+PLUGIN_RESOURCE_NATIVE_CFLAGS =                                                \
+               $(LUA_CFLAGS)
+
+PLUGIN_RESOURCE_NATIVE_LIBS =                                          \
+               libmurphy-common.la                                     \
+               $(RESOURCE_LIBRARY)                                     \
+               $(LUA_LIBS)
+
+plugin_resource_native_la_SOURCES = $(PLUGIN_RESOURCE_NATIVE_SOURCES)
+plugin_resource_native_la_CFLAGS  = $(PLUGIN_RESOURCE_NATIVE_CFLAGS)   \
+                                   $(MURPHY_CFLAGS) $(AM_CFLAGS)       \
+                                   $(JSON_CFLAGS)
+plugin_resource_native_la_LDFLAGS = -module -avoid-version
+plugin_resource_native_la_LIBADD  = $(PLUGIN_RESOURCE_NATIVE_LIBS)
+
+plugin_LTLIBRARIES += plugin-resource-native.la
+
+# resource-client
+bin_PROGRAMS += resource-client
+
+resource_client_SOURCES = plugins/resource-native/resource-client.c
+resource_client_CFLAGS  = $(AM_CFLAGS)
+resource_client_LDADD   =  libmurphy-common.la
+endif
+
+# domain control plugin
+DOMAIN_CONTROL_PLUGIN_SOURCES = plugins/domain-control/plugin-domain-control.c \
+                               plugins/domain-control/domain-control.c        \
+                               plugins/domain-control/proxy.c                 \
+                               plugins/domain-control/table.c                 \
+                               plugins/domain-control/notify.c                \
+                               plugins/domain-control/message.c
+DOMAIN_CONTROL_PLUGIN_LOADER  = linkedin-domain-control-loader.c
+
+
+DOMAIN_CONTROL_PLUGIN_CFLAGS  =
+DOMAIN_CONTROL_PLUGIN_LIBS    =
+
+if !DISABLED_PLUGIN_DOMAIN_CONTROL
+if BUILTIN_PLUGIN_DOMAIN_CONTROL
+LINKEDIN_PLUGINS             += libmurphy-plugin-domain-control.la
+lib_LTLIBRARIES              += libmurphy-plugin-domain-control.la
+DOMAIN_CONTROL_PLUGIN_CFLAGS += $(BUILTIN_CFLAGS)
+
+
+libmurphy_plugin_domain_control_ladir =                                 \
+               $(includedir)/murphy/domain-control
+
+libmurphy_plugin_domain_control_la_SOURCES =                    \
+               $(DOMAIN_CONTROL_PLUGIN_SOURCES)                 \
+               $(DOMAIN_CONTROL_PLUGIN_LOADER)
+
+libmurphy_plugin_domain_control_la_CFLAGS =                     \
+               $(DOMAIN_CONTROL_PLUGIN_CFLAGS)                  \
+               $(AM_CFLAGS)                                     \
+               $(JSON_CFLAGS)
+
+libmurphy_plugin_domain_control_la_LDFLAGS =                    \
+               -Wl,-version-script=linker-script.domain-control \
+               -version-info @MURPHY_VERSION_INFO@
+
+libmurphy_plugin_domain_control_la_LIBADD =            \
+               libmurphy-core.la                       \
+               libmurphy-common.la                     \
+               murphy-db/mql/libmql.la                 \
+               murphy-db/mqi/libmqi.la                 \
+               murphy-db/mdb/libmdb.la
+
+libmurphy_plugin_domain_control_la_DEPENDENCIES =      \
+               linker-script.domain-control            \
+       $(filter %.la, $(libmurphy_plugin_domain_control_la_LIBADD))
+else
+plugin_domain_control_la_SOURCES = $(DOMAIN_CONTROL_PLUGIN_SOURCES)
+plugin_domain_control_la_CFLAGS  = $(DOMAIN_CONTROL_PLUGIN_CFLAGS) \
+                                  $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_domain_control_la_LDFLAGS = -module -avoid-version
+plugin_domain_control_la_LIBADD  = $(DOMAIN_CONTROL_PLUGIN_LIBS)
+plugin_LTLIBRARIES              += plugin-domain-control.la
+endif
+
+
+# domain controller client library
+lib_LTLIBRARIES += libmurphy-domain-controller.la
+EXTRA_DIST      += plugins/domain-control/murphy-domain-controller.pc
+pkgconfig_DATA  += plugins/domain-control/murphy-domain-controller.pc
+
+libmurphy_domain_controller_ladir =                    \
+               $(includedir)/murphy/domain-control
+
+libmurphy_domain_controller_la_HEADERS =               \
+               plugins/domain-control/client.h
+
+libmurphy_domain_controller_la_SOURCES =               \
+               plugins/domain-control/client.c         \
+               plugins/domain-control/message.c
+libmurphy_domain_controller_la_CFLAGS   =              \
+               $(JSON_CFLAGS)
+libmurphy_domain_controller_la_LIBADD   =      \
+               libmurphy-common.la             \
+               murphy-db/mql/libmql.la         \
+               murphy-db/mqi/libmqi.la         \
+               murphy-db/mdb/libmdb.la         \
+               $(JSON_LIBS)
+
+if WEBSOCKETS_ENABLED
+EXTRA_DIST          += $(domain_control_DATA)
+domain_controldir    = $(datadir)/murphy/domain-control
+domain_control_DATA  =                                                 \
+               plugins/domain-control/domain-control-api.js    \
+               plugins/domain-control/domain-control-test.html
+endif
+
+# test domain controller (disabled until we have a readline replacement)
+bin_PROGRAMS += test-domain-controller
+
+test_domain_controller_SOURCES = plugins/domain-control/test-client.c
+test_domain_controller_CFLAGS  = $(AM_CFLAGS)
+test_domain_controller_LDADD   = libmurphy-domain-controller.la \
+                                libbreedline-murphy.la         \
+                                libbreedline.la                \
+                                libmurphy-common.la
+endif
+
+# linkedin domain control plugin linker script generation
+linker-script.domain-control: $(DOMAIN_CONTROL_PLUGIN_LOADER:%.c=%.h)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+               -P "$(CC)" -c "$(libmurphy_domain_controller_la_CFLAGS)" -o $@ $^
+
+clean-linker-script::
+       -rm -f linker-script.domain-control
+
+# Lua plugin
+LUA_PLUGIN_SOURCES = plugins/plugin-lua.c
+LUA_PLUGIN_CFLAGS  = $(AM_CFLAGS) $(LUA_CFLAGS)
+LUA_PLUGIN_LIBS    = $(LUA_LIBS)
+
+
+if BUILTIN_PLUGIN_LUA
+BUILTIN_PLUGINS += $(LUA_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(LUA_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(LUA_PLUGIN_LIBS)
+else
+plugin_lua_la_SOURCES = plugins/plugin-lua.c
+plugin_lua_la_CFLAGS  = $(LUA_PLUGIN_CFLAGS) \
+                       $(MURPHY_CFLAGS) $(AM_CFLAGS)
+plugin_lua_la_LDFLAGS = -module -avoid-version
+plugin_lua_la_LIBADD  = $(LUA_PLUGIN_LIBS) $(LUA_LIBS)
+
+plugin_LTLIBRARIES    += plugin-lua.la
+endif
+
+# systemd (logging) plugin
+if SYSTEMD_ENABLED
+SYSTEMD_PLUGIN_SOURCES = plugins/plugin-systemd.c
+SYSTEMD_PLUGIN_CFLAGS  = $(SYSTEMD_CFLAGS)
+SYSTEMD_PLUGIN_LIBS    = $(SYSTEMD_LIBS)
+
+if !DISABLED_PLUGIN_SYSTEMD
+if BUILTIN_PLUGIN_SYSTEMD
+BUILTIN_PLUGINS += $(SYSTEMD_PLUGIN_SOURCES)
+BUILTIN_CFLAGS  += $(SYSTEMD_PLUGIN_CFLAGS)
+BUILTIN_LIBS    += $(SYSTEMD_PLUGIN_LIBS)
+else
+plugin_systemd_la_SOURCES = $(SYSTEMD_PLUGIN_SOURCES)
+plugin_systemd_la_CFLAGS  = $(SYSTEMD_PLUGIN_CFLAGS) \
+                            $(MURPHY_CFLAGS) \
+                            $(AM_CFLAGS)
+plugin_systemd_la_LDFLAGS = -module -avoid-version
+plugin_systemd_la_LIBADD  = $(SYSTEMD_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES += plugin-systemd.la
+endif
+endif
+endif
+
+
+###################################
+# murphy daemon
+#
+
+bin_PROGRAMS += murphyd
+EXTRA_DIST   += $(config_DATA)
+configdir     = $(sysconfdir)/murphy
+config_DATA   = $(shell ls daemon/sample-config/*.{cfg,rules})
+
+murphyd_SOURCES =                      \
+               daemon/daemon.c         \
+               daemon/config.c         \
+               $(BUILTIN_PLUGINS)      \
+               load-linkedin-plugins.c
+
+murphyd_CFLAGS  =                      \
+               $(AM_CFLAGS)            \
+               $(BUILTIN_CFLAGS)       \
+               $(JSON_CFLAGS)
+
+murphyd_LDADD  =                               \
+               $(BUILTIN_LIBS)                 \
+               $(LINKEDIN_PLUGINS)             \
+               $(RESOURCE_LIBRARY)             \
+               libmurphy-resolver.la           \
+               libmurphy-lua-decision.la       \
+               libmurphy-core.la               \
+               libmurphy-lua-utils.la          \
+               libmurphy-common.la             \
+               $(JSON_LIBS)
+
+if LIBDBUS_ENABLED
+murphyd_LDADD +=                               \
+               libmurphy-dbus-libdbus.la       \
+               -lrt $(LIBDBUS_LIBS)
+endif
+
+
+murphyd_LDFLAGS = -rdynamic
+
+install-data-hook::
+       rm -f $(DESTDIR)/$(sysconfdir)/murphy/murphy.cfg
+       echo "load-plugin lua config=\"$(sysconfdir)/murphy/main.cfg\"" \
+           > $(DESTDIR)/$(sysconfdir)/murphy/murphy.conf
+
+###################################
+# linkedin (DSO) loader generation
+#
+
+linkedin-%-loader.c:
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linkedin-loader \
+           -p $(shell echo $@ | \
+               sed 's/linkedin-//g;s/-loader.c//g') -o $@
+
+linkedin-%-loader.h:
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linkedin-loader \
+           -p $(shell echo $@ | \
+               sed 's/linkedin-//g;s/-loader.h//g') -o $@
+
+load-linkedin-plugins.c:
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linkedin-loader \
+           -o $@ -d $(shell echo $(LINKEDIN_PLUGINS) | \
+                          sed 's/.*plugin-//g;s/\.[^\.]*$$//g')
+
+clean-local::
+       -rm -f linkedin-*-loader.[hc] load-linkedin-plugins.c
+
+###################################
+# murphy console client
+#
+
+if CONSOLE_ENABLED
+bin_PROGRAMS += murphy-console
+
+murphy_console_SOURCES =               \
+               console-client/client.c
+
+murphy_console_CFLAGS  =               \
+               $(AM_CFLAGS)
+
+murphy_console_LDADD  =                        \
+               libbreedline-murphy.la  \
+               libbreedline.la         \
+               libmurphy-common.la
+
+if LIBDBUS_ENABLED
+murphy_console_LDADD +=        libmurphy-dbus-libdbus.la
+endif
+
+murphy_console_LDFLAGS = -rdynamic
+endif
+
+# cleanup
+clean-local:: # clean-linker-script
+       -rm -f *~
diff --git a/src/breedline/LICENSE-BSD b/src/breedline/LICENSE-BSD
new file mode 100644 (file)
index 0000000..f70109e
--- /dev/null
@@ -0,0 +1,26 @@
+Copyright (c) 2012, Intel Corporation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of Intel Corporation nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/breedline/Makefile b/src/breedline/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/breedline/README b/src/breedline/README
new file mode 100644 (file)
index 0000000..8ce81ff
--- /dev/null
@@ -0,0 +1,43 @@
+Breedline is a BSD-licensed simplistic alternative to readline.
+Please see the LICENSE-BSD file for the exact license.
+
+Breedline provides a limited alternative to readline. It has
+only support for the most basic readline-like editing interface
+as well as a callback-based interface for integrating to various
+mainloops. Glib and murphy mainloop integration convenience
+libraries are readily provided.
+
+Breedline should work with most terminal emulators that support
+just the following set of VT100 escape sequences:
+
+  - move cursor to beginning of line (ESC[0G)
+  - move cursor right by %d (ESC[%dC)
+  - erase right of cursor til the end of line (ESC[0K)
+
+I have tested it only with xterm, vt100, ansi, and linux console
+terminal settings in practice but in principle it should work with
+almost all non-dumb terminals out there.
+
+Currently breedline supports the following editing commands:
+
+- basic cursor positioning:
+  o left: left arrow/ctrl-b
+  o right: right arrow/ctrl-f
+  o beginning of line: home/ctrl-a/
+  o end of line: end/ctrl-e
+  o word left: ctrl left arrow
+  o word right: ctrl right arrow
+
+- basic editing:
+  o erase: backspace
+  o delete: ctrl-d
+  o kill line: ctrl-u (saves to yank buffer)
+  o kill rest of line: ctrl-k (saves to yank buffer)
+  o yank: ctrl-y
+
+- basic history:
+  o previous: up arrow/ctrl-p
+  o next: down arrow/ctrl-n
+
+- miscallanea:
+  o redraw line: ctrl-l
diff --git a/src/breedline/breedline-glib.c b/src/breedline/breedline-glib.c
new file mode 100644 (file)
index 0000000..1362934
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <poll.h>
+#include <sys/poll.h>
+#include <glib.h>
+
+#include <breedline/breedline.h>
+
+typedef struct {
+    GIOChannel *ioc;
+    guint       iow;
+    void      (*cb)(int fd, int events, void *user_data);
+    void       *user_data;
+} watch_t;
+
+
+static gboolean io_cb(GIOChannel *ioc, GIOCondition events, void *user_data)
+{
+    watch_t *w  = (watch_t *)user_data;
+    int      e  = 0;
+    int      fd = g_io_channel_unix_get_fd(ioc);
+
+    if (events & G_IO_IN)
+        e |= POLLIN;
+    if (events & G_IO_HUP)
+        e |= POLLHUP;
+
+    w->cb(fd, e, w->user_data);
+
+    return TRUE;
+}
+
+
+static void *add_watch(void *mlp, int fd,
+                       void (*cb)(int fd, int events, void *user_data),
+                       void *user_data)
+{
+    GIOCondition  events = G_IO_IN | G_IO_HUP;
+    watch_t      *w;
+
+    (void)mlp;
+
+    w = malloc(sizeof(*w));
+
+    if (w != NULL) {
+        memset(w, 0, sizeof(*w));
+        w->cb        = cb;
+        w->user_data = user_data;
+        w->ioc       = g_io_channel_unix_new(fd);
+
+        if (w->ioc != NULL) {
+            w->iow = g_io_add_watch(w->ioc, events, io_cb, w);
+
+            if (w->iow != 0)
+                return w;
+
+            g_io_channel_unref(w->ioc);
+        }
+
+        free(w);
+    }
+
+    return NULL;
+}
+
+
+static void del_watch(void *wp)
+{
+    watch_t *w = (watch_t *)wp;
+
+    if (w != NULL) {
+        g_source_remove(w->iow);
+        g_io_channel_unref(w->ioc);
+
+        free(w);
+    }
+}
+
+
+static brl_mainloop_ops_t ml_ops = {
+    .add_watch = add_watch,
+    .del_watch = del_watch
+};
+
+
+brl_t *brl_create_with_glib(int fd, const char *prompt, GMainLoop *ml,
+                              brl_line_cb_t cb, void *user_data)
+{
+    brl_t *brl;
+
+    brl = brl_create(fd, prompt);
+
+    if (brl != NULL) {
+        if (brl_use_mainloop(brl, ml, &ml_ops, cb, user_data) == 0)
+            return brl;
+        else
+            brl_destroy(brl);
+    }
+
+    return NULL;
+}
diff --git a/src/breedline/breedline-glib.h b/src/breedline/breedline-glib.h
new file mode 100644 (file)
index 0000000..375d2e1
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BREEDLINE_MURPHY_H__
+#define __BREEDLINE_MURPHY_H__
+
+#include <glib.h>
+#include <breedline/macros.h>
+#include <breedline/breedline.h>
+
+BRL_CDECL_BEGIN
+
+brl_t *brl_create_with_glib(int fd, const char *prompt, GMainLoop *ml,
+                            brl_line_cb_t cb, void *user_data);
+
+BRL_CDECL_END
+
+
+
+#endif /* __BREEDLINE_MURPHY_H__ */
diff --git a/src/breedline/breedline-glib.pc.in b/src/breedline/breedline-glib.pc.in
new file mode 100644 (file)
index 0000000..a10b1d4
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: breedline-glib
+Description: GLIB mainloop glue library for breedline.
+Version: @PACKAGE_VERSION@
+Requires: breedline glib-2.0
+Libs: -L${libdir} -lbreedline-glib
+Cflags: -I${includedir}
diff --git a/src/breedline/breedline-murphy.c b/src/breedline/breedline-murphy.c
new file mode 100644 (file)
index 0000000..3e9c308
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common.h>
+#include <breedline/breedline.h>
+
+typedef struct {
+    mrp_io_watch_t  *w;
+    void           (*cb)(int fd, int events, void *user_data);
+    void            *user_data;
+} watch_t;
+
+
+static void io_cb(mrp_io_watch_t *watch, int fd, mrp_io_event_t events,
+                  void *user_data)
+{
+    watch_t *w = (watch_t *)user_data;
+    int      e = 0;
+
+    MRP_UNUSED(watch);
+
+    if (events & MRP_IO_EVENT_IN)
+        e |= POLLIN;
+    if (events & MRP_IO_EVENT_HUP)
+        e |= POLLHUP;
+
+    w->cb(fd, e, w->user_data);
+}
+
+
+static void *add_watch(void *mlp, int fd,
+                       void (*cb)(int fd, int events, void *user_data),
+                       void *user_data)
+{
+    mrp_mainloop_t *ml     = (mrp_mainloop_t *)mlp;
+    mrp_io_event_t  events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+    watch_t        *w;
+
+    w = mrp_allocz(sizeof(*w));
+
+    if (w != NULL) {
+        w->cb        = cb;
+        w->user_data = user_data;
+        w->w         = mrp_add_io_watch(ml, fd, events, io_cb, w);
+
+        if (w->w != NULL)
+            return w;
+        else
+            mrp_free(w);
+    }
+
+    return NULL;
+}
+
+
+static void del_watch(void *wp)
+{
+    watch_t *w = (watch_t *)wp;
+
+    if (w != NULL) {
+        mrp_del_io_watch(w->w);
+        mrp_free(w);
+    }
+}
+
+
+static brl_allocator_t allocator = {
+    .allocfn   = mrp_mm_alloc,
+    .reallocfn = mrp_mm_realloc,
+    .strdupfn  = mrp_mm_strdup,
+    .freefn    = mrp_mm_free
+};
+
+
+static brl_mainloop_ops_t ml_ops = {
+    .add_watch = add_watch,
+    .del_watch = del_watch
+};
+
+
+brl_t *brl_create_with_murphy(int fd, const char *prompt, mrp_mainloop_t *ml,
+                              brl_line_cb_t cb, void *user_data)
+{
+    brl_t *brl;
+
+    brl_set_allocator(&allocator);
+
+    brl = brl_create(fd, prompt);
+
+    if (brl != NULL) {
+        if (brl_use_mainloop(brl, ml, &ml_ops, cb, user_data) == 0)
+            return brl;
+        else
+            brl_destroy(brl);
+    }
+
+    return NULL;
+}
diff --git a/src/breedline/breedline-murphy.h b/src/breedline/breedline-murphy.h
new file mode 100644 (file)
index 0000000..7e764dd
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BREEDLINE_MURPHY_H__
+#define __BREEDLINE_MURPHY_H__
+
+#include <murphy/common/mainloop.h>
+#include <breedline/macros.h>
+#include <breedline/breedline.h>
+
+BRL_CDECL_BEGIN
+
+brl_t *brl_create_with_murphy(int fd, const char *prompt, mrp_mainloop_t *ml,
+                              brl_line_cb_t cb, void *user_data);
+
+BRL_CDECL_END
+
+#endif /* __BREEDLINE_MURPHY_H__ */
diff --git a/src/breedline/breedline-murphy.pc.in b/src/breedline/breedline-murphy.pc.in
new file mode 100644 (file)
index 0000000..e017173
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: breedline-glib
+Description: GLIB mainloop glue library for breedline.
+Version: @PACKAGE_VERSION@
+Requires: breedline murphy-common
+Libs: -L${libdir} -lbreedline-murphy
+Cflags: -I${includedir}
diff --git a/src/breedline/breedline.c b/src/breedline/breedline.c
new file mode 100644 (file)
index 0000000..3169e0d
--- /dev/null
@@ -0,0 +1,1490 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <ctype.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+
+#include "breedline/breedline.h"
+#include "breedline/mm.h"
+
+#ifndef TRUE
+#  define FALSE 0
+#  define TRUE  (!FALSE)
+#endif
+
+#define BRL_UNUSED(var) (void)var
+
+#ifdef __GNUC__
+#    define BRL_UNLIKELY(cond) __builtin_expect((cond), 0)
+#else
+#    define BRL_UNLIKELY(cond) __builtin_expect((cond), 0)
+#endif
+
+#define BRL_CURSOR_START "\x1b[0G"       /* move cursor to start of line */
+#define BRL_CURSOR_FORW  "\x1b[%dC"      /* move cursor forward by %d */
+#define BRL_ERASE_RIGHT  "\x1b[0K"       /* erase right of cursor */
+
+
+/*
+ * breedline modes
+ */
+
+typedef enum {
+    BRL_MODE_NORMAL,                     /* normal editing mode */
+    BRL_MODE_SEARCH_FORW,                /* searching forward */
+    BRL_MODE_SEARCH_BACK,                /* searching backward */
+} brl_mode_t;
+
+
+/*
+ * breedline key types
+ */
+
+typedef enum {
+    BRL_TYPE_INVALID = 0x000,            /* invalid key */
+    BRL_TYPE_SELF    = 0x100,            /* self-inserting (normal) key */
+    BRL_TYPE_COMMAND = 0x200,            /* editing command key */
+    BRL_TYPE_CSEQ    = 0x400,            /* control sequence indicator */
+    BRL_TYPE_MASK    = 0x700,            /* key type mask */
+} brl_type_t;
+
+#define BRL_INPUT_TYPE(in)     ((in)  & BRL_TYPE_MASK)
+#define BRL_TAG_INPUT(type, c) ((type & BRL_TYPE_MASK) | ((c) & ~BRL_TYPE_MASK))
+#define BRL_INPUT_DATA(in)     ((in)  & ~BRL_TYPE_MASK)
+
+/*
+ * breedline commands
+ */
+
+typedef enum {
+    BRL_CMD_INVALID = 0x00,              /* invalid input */
+    BRL_CMD_SELF    = 0xff,              /* self-inserting input */
+    BRL_CMD_FORWARD = 0x01,              /* cursor forward */
+    BRL_CMD_BACKWARD,                    /* cursor backward */
+    BRL_CMD_PREV_LINE,                   /* previous line from history */
+    BRL_CMD_NEXT_LINE,                   /* next line from history */
+    BRL_CMD_ERASE_BEFORE,                /* erase before cursor */
+    BRL_CMD_ERASE_AT,                    /* erase at cursor */
+    BRL_CMD_LINE_START,                  /* cursor to start of line */
+    BRL_CMD_LINE_END,                    /* cursor to end of line */
+    BRL_CMD_ERASE_REST,                  /* erase till the end of line */
+    BRL_CMD_ERASE_ALL,                   /* erase the whole line */
+    BRL_CMD_YANK,                        /* yank at insertion point */
+    BRL_CMD_PREV_WORD,                   /* cursor to previous word boundary */
+    BRL_CMD_NEXT_WORD,                   /* cursor to next word boundary */
+    BRL_CMD_CANCEL,                      /* cancel special command or mode */
+    BRL_CMD_ENTER,                       /* enter input */
+    BRL_CMD_REDRAW,                      /* redraw input prompt */
+    BRL_CMD_SEARCH_BACK,                 /* search history backward */
+    BRL_CMD_SEARCH_FORW,                 /* search history forward */
+} brl_cmd_t;
+
+
+/*
+ * breedline extended control sequences
+ */
+
+typedef struct {
+    const char *seq;                     /* control sequence */
+    int         len;                     /* sequence length */
+    int         key;                     /* mapped to this key */
+} extmap_t;
+
+
+/*
+ * key mapping
+ */
+
+#undef  CTRL
+#define CTRL(code) ((code) & 0x1f)
+#define ESC  0x1b
+#define DEL  0x7f
+#define BELL 0x7
+
+#define MAP(in, out)             [(in)]            = (out)
+#define MAP_RANGE(min, max, out) [(min) ... (max)] = (out)
+#define CMD(cmd)                 BRL_TAG_INPUT(BRL_TYPE_COMMAND, BRL_CMD_##cmd)
+
+static int key_map[256] = {
+    MAP_RANGE(' ' , '~', BRL_TYPE_SELF    ),
+    MAP(CTRL('b')      , CMD(BACKWARD)    ),
+    MAP(CTRL('f')      , CMD(FORWARD)     ),
+    MAP(CTRL('p')      , CMD(PREV_LINE)   ),
+    MAP(CTRL('n')      , CMD(NEXT_LINE)   ),
+    MAP(CTRL('d')      , CMD(ERASE_AT)    ),
+    MAP(    DEL        , CMD(ERASE_BEFORE)),
+    MAP(CTRL('a')      , CMD(LINE_START)  ),
+    MAP(CTRL('e')      , CMD(LINE_END)    ),
+    MAP(CTRL('k')      , CMD(ERASE_REST)  ),
+    MAP(CTRL('u')      , CMD(ERASE_ALL)   ),
+    MAP(CTRL('y')      , CMD(YANK)        ),
+    MAP(CTRL('m')      , CMD(ENTER)       ),
+    MAP(CTRL('l')      , CMD(REDRAW)      ),
+    MAP(CTRL('r')      , CMD(SEARCH_BACK) ),
+    MAP(CTRL('s')      , CMD(SEARCH_FORW) ),
+    MAP(    ESC        , BRL_TYPE_CSEQ    ),
+};
+
+static extmap_t ext_map[] = {
+    { "\x1b[A"   , 3, CMD(PREV_LINE)   },
+    { "\x1b[B"   , 3, CMD(NEXT_LINE)   },
+    { "\x1b[C"   , 3, CMD(FORWARD)     },
+    { "\x1b[D"   , 3, CMD(BACKWARD)    },
+    { "\x1b[F"   , 3, CMD(LINE_END)    },
+    { "\x1b[H"   , 3, CMD(LINE_START)  },
+    { "\x1b[1;5A", 6, BRL_TYPE_INVALID },
+    { "\x1b[1;5B", 6, BRL_TYPE_INVALID },
+    { "\x1b[1;5C", 6, CMD(NEXT_WORD)   },
+    { "\x1b[1;5D", 6, CMD(PREV_WORD)   },
+    { NULL       , 0, BRL_TYPE_INVALID }
+};
+
+
+/*
+ * ring buffer for saving history
+ */
+
+typedef struct {
+    char **entries;                      /* ring buffer entries */
+    int    size;                         /* buffer size */
+    int    next;                         /* buffer insertion point */
+    int    srch;                         /* buffer search point */
+    char  *pattern;                      /* search pattern buffer */
+    int    psize;                        /* pattern buffer size */
+    int    plen;                         /* actual pattern length */
+} ringbuf_t;
+
+
+
+/*
+ * breedline context
+ */
+
+struct brl_s {
+    int                 fd;              /* file descriptor to read */
+    struct termios      term_mode;       /* original terminal settings */
+    int                 term_flags;      /* terminal descriptor flags */
+    int                 term_blck;       /* originally in blocking mode */
+    int                 term_ncol;       /* number of columns */
+    void               *ml;              /* mainloop, if any */
+    brl_mainloop_ops_t *ml_ops;          /* mainloop operations, if any */
+    void               *ml_w;            /* I/O watch */
+    brl_line_cb_t       line_cb;         /* input callback */
+    void               *user_data;       /* opaque callback data */
+    char               *prompt;          /* prompt string */
+    int                 hidden;          /* whether prompt is hidden */
+    int                 mode;            /* current mode */
+    unsigned char      *buf;             /* input buffer */
+    int                 size;            /* size of the buffer */
+    int                 data;            /* amount of data in buffer */
+    int                 offs;            /* buffer insertion offset */
+    unsigned char      *yank;            /* yank buffer */
+    int                 yank_size;       /* yank buffer size */
+    int                 yank_data;       /* yank buffer effective length */
+    int                *map;             /* key map */
+    extmap_t           *ext;             /* extended key map */
+    int                 esc;             /* CS being collected */
+    unsigned char       seq[8];          /* control sequence buffer */
+    int                 seq_len;         /* sequence length */
+    ringbuf_t           h;               /* history ring buffer */
+    char               *saved;           /* input buffer saved during search */
+    int                 dump;            /* whether to dump key input */
+    char               *dbg_buf;         /* debug buffer */
+    int                 dbg_size;        /* debug buffer size */
+    int                 dbg_len;         /* debug buffer effective length */
+};
+
+
+static int setup_terminal(brl_t *brl);
+static int cleanup_terminal(brl_t *brl);
+static int terminal_size(int fd, int *nrow, int *ncol);
+static int enable_rawmode(brl_t *brl);
+static int restore_rawmode(brl_t *brl);
+static int disable_blocking(brl_t *brl);
+static int restore_blocking(brl_t *brl);
+
+static void process_input(brl_t *brl);
+static void reset_input(brl_t *brl);
+static void dump_input(brl_t *brl);
+static void redraw_prompt(brl_t *brl);
+
+static int ringbuf_init(ringbuf_t *rb, int size);
+static void ringbuf_purge(ringbuf_t *rb);
+
+static void *_brl_default_alloc(size_t size, const char *file, int line,
+                                const char *func);
+static void *_brl_default_realloc(void *ptr, size_t size,
+                                  const char *file, int line, const char *func);
+static char *_brl_default_strdup(const char *str, const char *file, int line,
+                                 const char *func);
+static void _brl_default_free(void *ptr,
+                              const char *file, int line, const char *func);
+
+
+
+brl_t *brl_create(int fd, const char *prompt)
+{
+    static int  dump = -1, dbg_size = -1;
+    brl_t      *brl;
+
+    if (dump < 0) {
+        const char *val = getenv("__BREEDLINE_DUMP_KEYS");
+        dump = (val != NULL && (*val == 'y' || *val == 'Y'));
+    }
+
+    if (dbg_size < 0) {
+        const char *val = getenv("__BREEDLINE_DEBUG");
+        dbg_size = (val == NULL ? 0 : atoi(val));
+    }
+
+    brl = brl_allocz(sizeof(*brl));
+
+    if (brl != NULL) {
+        brl->fd     = fd;
+        brl->map    = key_map;
+        brl->ext    = ext_map;
+        brl->prompt = brl_strdup(prompt ? prompt : "");
+
+        brl->dump     = dump;
+        brl->dbg_size = dbg_size;
+        if (dbg_size > 0)
+            brl->dbg_buf = brl_allocz(dbg_size);
+
+        brl_limit_history(brl, BRL_DEFAULT_HISTORY);
+
+        if (!setup_terminal(brl) && !terminal_size(fd, NULL, &brl->term_ncol))
+            return brl;
+        else
+            brl_destroy(brl);
+    }
+
+    return NULL;
+}
+
+
+void brl_destroy(brl_t *brl)
+{
+    if (brl != NULL) {
+        brl_hide_prompt(brl);
+        brl_free(brl->prompt);
+        brl_free(brl->buf);
+        brl_free(brl->saved);
+        brl_free(brl->yank);
+        brl_free(brl->dbg_buf);
+        ringbuf_purge(&brl->h);
+        cleanup_terminal(brl);
+
+        brl_free(brl);
+    }
+}
+
+
+int brl_set_prompt(brl_t *brl, const char *prompt)
+{
+    brl_free(brl->prompt);
+    brl->prompt = brl_strdup(prompt);
+
+    return (brl->prompt != NULL || prompt == NULL);
+}
+
+
+void brl_hide_prompt(brl_t *brl)
+{
+    static int warned = 0;
+    char buf[32];
+    int  n, o;
+
+    brl->hidden = TRUE;
+
+    n = snprintf(buf, sizeof(buf), "%s%s", BRL_CURSOR_START, BRL_ERASE_RIGHT);
+    o = write(brl->fd, buf, n);
+    restore_rawmode(brl);
+
+    if (BRL_UNLIKELY(o < 0 && !warned)) {           /* make gcc happy */
+        fprintf(stderr, "write to fd %d failed\n", brl->fd);
+        warned = 1;
+    }
+}
+
+
+void brl_show_prompt(brl_t *brl)
+{
+    brl->hidden = FALSE;
+    enable_rawmode(brl);
+    redraw_prompt(brl);
+}
+
+
+static void debug(brl_t *brl, const char *fmt, ...)
+{
+    va_list ap;
+    int     n;
+
+    va_start(ap, fmt);
+    n = vsnprintf(brl->dbg_buf, brl->dbg_size, fmt, ap);
+    va_end(ap);
+
+    if (n > 0)
+        brl->dbg_len = n < brl->dbg_size ? n : brl->dbg_size;
+    else
+        brl->dbg_len = 0;
+}
+
+
+static int ringbuf_init(ringbuf_t *rb, int size)
+{
+    int i;
+
+    for (i = 0; i < rb->size; i++)
+        brl_free(rb->entries[i]);
+
+    brl_free(rb->entries);
+    rb->entries = NULL;
+    rb->size    = 0;
+    rb->next    = 0;
+    rb->srch    = 0;
+
+    brl_free(rb->pattern);
+    rb->pattern = NULL;
+    rb->psize   = 0;
+    rb->plen    = 0;
+
+    rb->entries = brl_allocz(size * sizeof(rb->entries[0]));
+
+    if (rb->entries == NULL && size != 0)
+        return -1;
+
+    rb->size = size;
+
+    return 0;
+}
+
+
+static void ringbuf_purge(ringbuf_t *rb)
+{
+    ringbuf_init(rb, 0);
+}
+
+
+static char **ringbuf_entry(ringbuf_t *rb, int idx)
+{
+    int i;
+
+    if (rb->entries == NULL) {
+        errno = ENOSPC;
+        return NULL;
+    }
+
+    if (idx >= rb->size) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    if (idx < 0 && -idx > rb->size) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    if (idx == 0)
+        return rb->entries + rb->next;
+
+    if (idx < 0) {
+        i = rb->next + idx;
+
+        if (i < 0)
+            i += rb->size;
+
+        return rb->entries + i;
+    }
+    else {
+        i = rb->next - 1 + idx;
+
+        if (i >= rb->size)
+            i -= rb->size;
+
+        return rb->entries + i;
+    }
+}
+
+
+static int ringbuf_add(ringbuf_t *rb, const char *str)
+{
+    char **entry = ringbuf_entry(rb, 0);
+
+    if (entry != NULL) {
+        brl_free(*entry);
+        *entry   = brl_strdup(str);
+        rb->next = (rb->next + 1) % rb->size;
+
+        return 0;
+    }
+    else
+        return -1;
+}
+
+
+static void ringbuf_reset_search(ringbuf_t *rb)
+{
+    rb->srch = 0;
+    rb->plen = 0;
+    memset(rb->pattern, 0, rb->psize);
+}
+
+
+static char *ringbuf_search(ringbuf_t *rb, int dir, unsigned char c,
+                            brl_mode_t mode, char *current)
+{
+    int    i;
+    char **e;
+
+    if (!c && mode == BRL_MODE_NORMAL) {
+        i = rb->srch + (dir < 0 ? -1 : +1);
+
+        if (i > 0)
+            return NULL;
+
+        if (i == 0) {
+            rb->srch = 0;
+            return current;
+        }
+
+        e = ringbuf_entry(rb, i);
+
+        if (e != NULL && *e != NULL) {
+            rb->srch = i;
+            return *e;
+        }
+        else
+            return NULL;
+    }
+    else if (mode == BRL_MODE_SEARCH_BACK) {
+        int total = rb->plen + 1;
+        int found = 0;
+
+        if (c) {
+            if (rb->psize == 0) {
+                rb->psize = 32;
+                if (!(rb->pattern = brl_allocz(rb->psize))) {
+                    errno = ENOMEM;
+                    return NULL;
+                }
+            }
+
+            if (rb->psize < total) {
+                if (rb->psize * 2 > total)
+                    total = rb->psize * 2;
+                if (!brl_reallocz(rb->pattern, rb->psize, total)) {
+                    errno = ENOMEM;
+                    return NULL;
+                }
+                rb->psize = total;
+            }
+
+            rb->pattern[rb->plen++] = c;
+
+            i = rb->srch;
+        }
+        else {
+            /* keep searching backwards */
+            i = rb->srch - 1;
+        }
+
+        if (!rb->pattern) {
+            errno = EINVAL;
+            return NULL;
+        }
+
+        /* start searching backwards from current search point */
+
+        do {
+            e = ringbuf_entry(rb, i);
+
+            if (e != NULL && *e != NULL) {
+                /* pattern matching */
+                if (strstr(*e, rb->pattern)) {
+                    found = 1;
+                    break;
+                }
+            }
+
+            i--;
+        } while (e != NULL && !found);
+
+        if (found) {
+            /* set the search position while we're in search mode */
+           rb->srch = i;
+           return *e;
+        }
+
+        errno = ENOENT;
+        return NULL;
+    }
+
+    errno = EOPNOTSUPP;
+    return NULL;
+}
+
+
+int brl_limit_history(brl_t *brl, size_t size)
+{
+    return ringbuf_init(&brl->h, size);
+}
+
+
+int brl_add_history(brl_t *brl, const char *str)
+{
+    return ringbuf_add(&brl->h, str);
+}
+
+
+static int _brl_process_input(brl_t *brl)
+{
+    if (brl->dump)
+        dump_input(brl);
+    else
+        process_input(brl);
+
+    return 0;
+}
+
+
+int brl_read_line(brl_t *brl, char *buf, size_t size)
+{
+    if (brl->ml == NULL && brl->ml_ops == NULL) {
+        reset_input(brl);
+        enable_rawmode(brl);
+        brl_show_prompt(brl);
+        redraw_prompt(brl);
+        _brl_process_input(brl);
+
+        if (brl->data > 0)
+            snprintf(buf, size, "%s", brl->buf);
+        else
+            buf[0] = '\0';
+
+        brl_hide_prompt(brl);
+        restore_rawmode(brl);
+
+        return brl->data;
+    }
+    else {
+        errno = EINPROGRESS;
+        return -1;
+    }
+}
+
+
+static void _brl_io_cb(int fd, int events, void *user_data)
+{
+    brl_t *brl = (brl_t *)user_data;
+
+    BRL_UNUSED(fd);
+
+    if (events & POLLIN)
+        _brl_process_input(brl);
+
+    if (events & POLLHUP) {
+        brl->ml_ops->del_watch(brl->ml_w);
+        brl->ml_w = NULL;
+    }
+}
+
+
+int brl_use_mainloop(brl_t *brl, void *ml, brl_mainloop_ops_t *ops,
+                     brl_line_cb_t cb, void *user_data)
+{
+    if (brl != NULL) {
+        if (brl->ml != NULL || brl->ml_ops != NULL) {
+            errno = EBUSY;
+            return -1;
+    }
+
+        brl->line_cb   = cb;
+        brl->user_data = user_data;
+
+        brl->ml     = ml;
+        brl->ml_ops = ops;
+        brl->ml_w   = brl->ml_ops->add_watch(ml, brl->fd, _brl_io_cb, brl);
+
+        if (brl->ml_w != NULL) {
+            disable_blocking(brl);
+            return 0;
+        }
+        else
+            errno = EIO;
+    }
+    else
+        errno = EFAULT;
+
+    return -1;
+}
+
+
+static int enable_rawmode(brl_t *brl)
+{
+    struct termios mode;
+
+    memcpy(&mode, &brl->term_mode, sizeof(mode));
+
+    mode.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+    mode.c_oflag &= ~OPOST;
+    mode.c_cflag |= CS8;
+    mode.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+
+    mode.c_cc[VMIN]  = 1;
+    mode.c_cc[VTIME] = 0;
+
+    return tcsetattr(brl->fd, TCSAFLUSH, &mode);
+}
+
+
+static int restore_rawmode(brl_t *brl)
+{
+    return tcsetattr(brl->fd, TCSAFLUSH, &brl->term_mode);
+}
+
+
+static int disable_blocking(brl_t *brl)
+{
+    int flags;
+
+    if (brl->term_blck) {
+        brl->term_flags = fcntl(brl->fd, F_GETFL);
+
+        if (brl->term_flags != -1) {
+            flags = brl->term_flags | O_NONBLOCK;
+
+            if (fcntl(brl->fd, F_SETFL, flags) == 0) {
+                brl->term_blck = FALSE;
+                return 0;
+            }
+        }
+
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int restore_blocking(brl_t *brl)
+{
+    if (!brl->term_blck) {
+        if (fcntl(brl->fd, F_SETFL, brl->term_flags) == 0) {
+            brl->term_blck = FALSE;
+            return 0;
+        }
+        else
+            return -1;
+    }
+
+    return 0;
+}
+
+
+static int setup_terminal(brl_t *brl)
+{
+    if (!isatty(brl->fd)) {
+        errno = ENOTTY;
+        return -1;
+    }
+
+    if (tcgetattr(brl->fd, &brl->term_mode) == -1)
+        return -1;
+
+    if (enable_rawmode(brl) != 0)
+        return -1;
+
+    brl->term_flags = fcntl(brl->fd, F_GETFL);
+    brl->term_blck  = TRUE;
+
+#if 0
+    if (disable_blocking(brl) == 0)
+        return 0;
+    else
+        return -1;
+#else
+    return 0;
+#endif
+}
+
+
+static int cleanup_terminal(brl_t *brl)
+{
+    return (restore_rawmode(brl) | restore_blocking(brl));
+}
+
+
+static int terminal_size(int fd, int *nrow, int *ncol)
+{
+    struct winsize ws;
+    int    col, row;
+
+    if (ioctl(fd, TIOCGWINSZ, &ws) == 0) {
+        row = (ws.ws_row > 0 ? ws.ws_row : 80);
+        col = (ws.ws_col > 0 ? ws.ws_col : 25);
+
+        if (nrow != NULL)
+            *nrow = row;
+        if (ncol != NULL)
+            *ncol = col;
+
+        return 0;
+    }
+    else
+        return -1;
+}
+
+
+static void redraw_prompt(brl_t *brl)
+{
+    static int warned = 0;
+
+    char *prompt, *buf, *p;
+    int   plen, dlen, space, start, trunc;
+    int   l, n, o;
+    char  search_buf[256];
+
+    if (brl->mode == BRL_MODE_SEARCH_BACK) {
+        snprintf(search_buf, 256, "search backwards: '%s'",
+                brl->h.pattern ? brl->h.pattern : "");
+        prompt = search_buf;
+    }
+    else {
+        prompt = brl->prompt ? brl->prompt : "";
+    }
+
+    plen   = strlen(prompt) + 2;            /* '> ' or '><' */
+
+    if (brl->dbg_len > 0)
+        plen += brl->dbg_len + 2;
+
+    space  = brl->term_ncol - 1 - plen - 1; /* - 1 for potential trailing > */
+
+    /* adjust start if the cursor would be past the right edge */
+    if (brl->offs >= space)
+        start = brl->offs - space;
+    else
+        start = 0;
+
+    /* truncate if there's more data than fits the screen */
+    dlen = brl->data - start;
+
+    if (dlen > space) {
+        dlen = space;
+        trunc = TRUE;
+    }
+    else
+        trunc = FALSE;
+
+    l   = plen + dlen + 64;
+    buf = alloca(l);
+    p   = buf;
+
+#if 0
+    printf("\n\rplen = %d, dlen = %d, start = %d, buf = '%*.*s'\n\r",
+           plen, dlen, start, brl->data, brl->data, brl->buf);
+    printf("brl->offs = %d, effective offset = %d\n\r", brl->offs,
+           plen + brl->offs - start);
+#endif
+
+    /* position cursor to beginning of line */
+    n  = snprintf(p, l, "%s", BRL_CURSOR_START);
+    p += n;
+    l -= n;
+
+    /* print prompt + visible portion of buffer */
+    n  = snprintf(p, l, "%s%s%s%s%s%*.*s%s", prompt,
+                  brl->dbg_len ? "[" : "",
+                  brl->dbg_len ? brl->dbg_buf : "",
+                  brl->dbg_len ? "]" : "",
+                  start > 0 ? "><" : "> ",
+                  dlen, dlen, brl->buf + start, trunc ? ">" : "");
+    p += n;
+    l -= n;
+
+    /* erase the rest of the line (ie. make sure there's no trailing junk) */
+    n  = snprintf(p, l, "%s", BRL_ERASE_RIGHT);
+    p += n;
+    l -= n;
+
+    /* okay, perform the actions collected so far */
+    o = write(brl->fd, buf, (p - buf));
+
+    l = plen + dlen + 64;
+    p = buf;
+
+    if (BRL_UNLIKELY(o < 0 && !warned)) {           /* make gcc happy */
+        fprintf(stderr, "write to fd %d failed\n", brl->fd);
+        warned = 1;
+    }
+
+    /* re-position cursor to the current insertion offset */
+    n  = snprintf(p, l, BRL_CURSOR_START""BRL_CURSOR_FORW,
+                  plen + brl->offs - start);
+    p += n;
+    l -= n;
+    o = write(brl->fd, buf, (p - buf));
+
+    if (BRL_UNLIKELY(o < 0 && !warned)) {           /* make gcc happy */
+        fprintf(stderr, "write to fd %d failed\n", brl->fd);
+        warned = 1;
+    }
+}
+
+
+/*
+ * input buffer handling
+ */
+
+static void reset_input(brl_t *brl)
+{
+    memset(brl->buf, 0, brl->size);
+    brl->data = 0;
+    brl->offs = 0;
+}
+
+
+static int insert_input(brl_t *brl, const char *input, int len)
+{
+    int total;
+
+    total = brl->data + len + 1;
+
+    if (brl->size < total) {
+        if (brl->size * 2 > total)
+            total = brl->size * 2;
+        if (!brl_reallocz(brl->buf, brl->size, total))
+            return -1;
+        brl->size = total;
+    }
+
+    if (brl->offs < brl->data) {
+        memmove(brl->buf + brl->offs + len, brl->buf + brl->offs,
+                brl->data - brl->offs);
+        memcpy(brl->buf + brl->offs, input, len);
+    }
+    else
+        memcpy(brl->buf + brl->offs, input, len);
+
+    brl->data += len;
+    brl->offs += len;
+    brl->buf[brl->data] = '\0';
+
+    return 0;
+}
+
+
+static int erase_input(brl_t *brl, int n)
+{
+    if (n < 0) {
+        if (-n > brl->offs)
+            n = -brl->offs;
+        if (brl->offs < brl->data)
+            memmove(brl->buf + brl->offs + n, brl->buf + brl->offs,
+                    brl->data - brl->offs);
+        brl->data += n;
+        if (brl->offs > brl->data)
+            brl->offs = brl->data;
+    }
+    else {
+        if (n > brl->data - brl->offs)
+            n = brl->data - brl->offs;
+        memmove(brl->buf + brl->offs, brl->buf + brl->offs + n,
+                brl->data - (brl->offs + n));
+        brl->data -= n;
+    }
+
+    return 0;
+}
+
+
+static void save_input(brl_t *brl)
+{
+    brl_free(brl->saved);
+    brl->saved = brl_strdup((char *)brl->buf);
+}
+
+
+static void save_yank(brl_t *brl, int start, int end)
+{
+    int size, len;
+
+    if (start < 0 || start >= brl->data || end > brl->data)
+        return;
+
+    len  = end - start + 1;
+    size = len + 1;
+
+    if (brl->yank_size < size) {
+        if (brl->yank_size * 2 > size)
+            size = brl->yank_size * 2;
+        if (!brl_reallocz(brl->yank, brl->yank_size, size))
+            return;
+    }
+
+    brl->yank_size = size;
+
+    memcpy(brl->yank, brl->buf + start, len);
+    brl->yank[len] = '\0';
+    brl->yank_data = len - 1;
+}
+
+
+static void restore_input(brl_t *brl)
+{
+    reset_input(brl);
+
+    if (brl->saved != NULL) {
+        insert_input(brl, brl->saved, strlen(brl->saved));
+        brl_free(brl->saved);
+        brl->saved = NULL;
+    }
+}
+
+
+static int input_delimiter(brl_t *brl, int dir)
+{
+    static const char  delim[] = " ,;:.?!'\"-_/";
+    char              *s, *p;
+
+    if (brl->data == 0)
+        return 0;
+
+    if ((dir < 0 && brl->offs == 0) || (dir >= 0 && brl->offs >= brl->data))
+        return 0;
+
+    s = (char *)brl->buf + brl->offs;
+
+    if (dir < 0) {
+        p = s - 1;
+        if (p > (char *)brl->buf && strchr(delim, *p) != NULL)
+            p--;
+        while (p >= (char *)brl->buf) {
+            if (strchr(delim, *p) != NULL) {
+                p   += 1;
+                break;
+            }
+            else
+                p--;
+        }
+        return p - s;
+    }
+    else {
+        p = s;
+        if (strchr(delim, *p) != NULL && s < (char *)brl->buf + brl->data)
+            p++;
+        while (p < (char *)brl->buf + brl->data) {
+            if (strchr(delim, *p) != NULL)
+                break;
+            else
+                p++;
+        }
+        return p - s;
+    }
+
+    return 0;
+}
+
+
+static void move_cursor(brl_t *brl, int n)
+{
+    brl->offs += n;
+
+    if (brl->offs < 0)
+        brl->offs = 0;
+    if (brl->offs > brl->data)
+        brl->offs = brl->data;
+}
+
+
+static void bell(brl_t *brl)
+{
+    int fd;
+
+    if (brl->fd == fileno(stdin))
+        fd = fileno(stderr);
+    else
+        fd = brl->fd;
+
+    dprintf(fd, "%c", BELL);
+}
+
+
+/*
+ * input mapping
+ */
+
+static int map_input(brl_t *brl, unsigned char c)
+{
+    int mapped;
+
+    mapped = brl->map[c];
+
+    if (mapped == BRL_TYPE_SELF)
+        return BRL_TAG_INPUT(BRL_TYPE_SELF, c);
+    else
+        return mapped;
+}
+
+
+static int map_esc_sequence(brl_t *brl)
+{
+    BRL_UNUSED(brl);
+
+    return BRL_TYPE_INVALID;
+}
+
+
+static int map_ctrl_sequence(brl_t *brl)
+{
+    extmap_t *e;
+    int       d;
+
+    if (brl->ext != NULL) {
+        for (e = brl->ext; e->seq != NULL; e++) {
+            if (e->len == brl->seq_len) {
+                d = strncmp((char *)brl->seq, e->seq, e->len);
+
+                if (d == 0)
+                    return e->key;
+
+                if (d < 0)
+                    break;
+            }
+
+            if (e->len > brl->seq_len)
+                break;
+        }
+    }
+
+    return BRL_TYPE_INVALID;
+}
+
+
+/*
+ * main input processing
+ */
+
+static void process_input(brl_t *brl)
+{
+    unsigned char c;
+    int           mapped, type, in, n, diff;
+    char          out, *line, *hentry;
+
+    while((n = read(brl->fd, &c, sizeof(c))) > 0) {
+        if (brl->esc) {
+            if (brl->seq_len < (int)sizeof(brl->seq))
+                brl->seq[brl->seq_len++] = c;
+
+            if (brl->seq_len == 2) {
+                if (c != '[') {
+                    mapped = map_esc_sequence(brl);
+                    brl->esc = FALSE;
+                }
+                else
+                    continue;
+            }
+            else {
+                if (0x40 <= c && c <= 0x7e) {
+                    mapped = map_ctrl_sequence(brl);
+                    brl->esc = FALSE;
+                }
+                else {
+                    if (brl->seq_len == (int)sizeof(brl->seq)) {
+                        mapped = BRL_TYPE_INVALID;
+                        brl->esc = FALSE;
+                    }
+                    else
+                        continue;
+                }
+            }
+        }
+        else
+            mapped = map_input(brl, c);
+
+        type = BRL_INPUT_TYPE(mapped);
+        in   = BRL_INPUT_DATA(mapped);
+
+        switch (type) {
+        case BRL_TYPE_SELF:
+            switch (brl->mode) {
+                case BRL_MODE_NORMAL:
+                    out = (char)(in & 0xff);
+                    insert_input(brl, &out, 1);
+                    redraw_prompt(brl);
+                    break;
+                case BRL_MODE_SEARCH_BACK:
+                    out = (char)(in & 0xff);
+                    hentry = ringbuf_search(&brl->h, 0, out, BRL_MODE_SEARCH_BACK, NULL);
+                    if (hentry != NULL) {
+                        reset_input(brl);
+                        insert_input(brl, hentry, strlen(hentry));
+                    }
+                    else
+                        bell(brl);
+                    redraw_prompt(brl);
+                    break;
+                case BRL_MODE_SEARCH_FORW:
+                    /* TODO */
+                    break;
+            }
+            break;
+
+        case BRL_TYPE_COMMAND:
+            switch (in) {
+            case BRL_CMD_PREV_LINE:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                if (brl->h.srch == 0)
+                    save_input(brl);
+                hentry = ringbuf_search(&brl->h, -1, 0, BRL_MODE_NORMAL, (char *)brl->saved);
+                debug(brl, "s:%d,'%s'", brl->h.srch,
+                      brl->saved ? brl->saved : "-");
+                if (hentry != NULL) {
+                    reset_input(brl);
+                    insert_input(brl, hentry, strlen(hentry));
+                    redraw_prompt(brl);
+                }
+                else
+                    bell(brl);
+                break;
+
+            case BRL_CMD_NEXT_LINE:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                hentry = ringbuf_search(&brl->h, +1, 0, BRL_MODE_NORMAL, (char *)brl->saved);
+                debug(brl, "s:%d,'%s'", brl->h.srch,
+                      brl->saved ? brl->saved : "-");
+                if (hentry != NULL) {
+                    if (hentry == brl->saved)
+                        restore_input(brl);
+                    else {
+                        reset_input(brl);
+                        insert_input(brl, hentry, strlen(hentry));
+                    }
+                    redraw_prompt(brl);
+                }
+                else
+                    bell(brl);
+                break;
+
+            case BRL_CMD_SEARCH_BACK:
+                if (brl->mode == BRL_MODE_SEARCH_BACK) {
+                    /* already in search mode, continue */
+                    hentry = ringbuf_search(&brl->h, 0, 0, BRL_MODE_SEARCH_BACK, NULL);
+                    if (hentry != NULL) {
+                        reset_input(brl);
+                        insert_input(brl, hentry, strlen(hentry));
+                    }
+                    else
+                        bell(brl);
+                }
+                else {
+                    if (brl->h.srch == 0)
+                        save_input(brl);
+                    brl->mode = BRL_MODE_SEARCH_BACK;
+                }
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_BACKWARD:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                move_cursor(brl, -1);
+                redraw_prompt(brl);
+                break;
+            case BRL_CMD_FORWARD:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                move_cursor(brl, +1);
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_LINE_START:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                move_cursor(brl, -brl->offs);
+                redraw_prompt(brl);
+                break;
+            case BRL_CMD_LINE_END:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                move_cursor(brl, brl->data - brl->offs);
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_ERASE_BEFORE:
+                switch(brl->mode) {
+                case BRL_MODE_NORMAL:
+                    erase_input(brl, -1);
+                    if (brl->offs < brl->data)
+                        move_cursor(brl, -1);
+                    redraw_prompt(brl);
+                    break;
+                case BRL_MODE_SEARCH_BACK:
+                case BRL_MODE_SEARCH_FORW:
+                    if (brl->h.plen > 0) {
+                        brl->h.pattern[--brl->h.plen] = '\0';
+                    }
+                    else {
+                        ringbuf_reset_search(&brl->h);
+                        brl->mode = BRL_MODE_NORMAL;
+                        restore_input(brl);
+                    }
+                    redraw_prompt(brl);
+                    break;
+                }
+                break;
+            case BRL_CMD_ERASE_AT:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                erase_input(brl, 1);
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_ERASE_REST:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                save_yank(brl, brl->offs, brl->data);
+                erase_input(brl, brl->data - brl->offs);
+                redraw_prompt(brl);
+                break;
+            case BRL_CMD_ERASE_ALL:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                save_yank(brl, 0, brl->data);
+                reset_input(brl);
+                redraw_prompt(brl);
+                break;
+            case BRL_CMD_YANK:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                insert_input(brl, (char *)brl->yank, brl->yank_data);
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_PREV_WORD:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                diff = input_delimiter(brl, -1);
+                move_cursor(brl, diff);
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_NEXT_WORD:
+                if (brl->mode != BRL_MODE_NORMAL) {
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                }
+                diff = input_delimiter(brl, +1);
+                move_cursor(brl, diff);
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_REDRAW:
+                redraw_prompt(brl);
+                break;
+
+            case BRL_CMD_ENTER:
+                dprintf(brl->fd, "\n\r");
+                if (brl->line_cb != NULL) {
+                    line = alloca(brl->data + 1);
+                    strncpy(line, (char *)brl->buf, brl->data);
+                    line[brl->data] = '\0';
+                    reset_input(brl);
+                    restore_rawmode(brl);
+                    brl->line_cb(brl, line, brl->user_data);
+                    enable_rawmode(brl);
+                    ringbuf_reset_search(&brl->h);
+                    brl->mode = BRL_MODE_NORMAL;
+                    debug(brl, "");
+                    redraw_prompt(brl);
+                }
+                else
+                    return;
+                break;
+
+            default:
+#if 0
+                printf("editing command 0x%x\n\r", in);
+#endif
+                bell(brl);
+            }
+            break;
+
+        case BRL_TYPE_CSEQ:
+            brl->esc     = TRUE;
+            brl->seq[0]  = c;
+            brl->seq_len = 1;
+            break;
+
+        case BRL_TYPE_INVALID:
+        default:
+            bell(brl);
+            break;
+        }
+    }
+}
+
+
+static void dump_input(brl_t *brl)
+{
+    unsigned char c, seq[64], s[4] = "  \0";
+    int           i = 0;
+
+    printf("got input:");
+
+    while (read(brl->fd, &c, 1) == 1) {
+        printf(" 0x%2.2x", c);
+        seq[i++] = c;
+
+        if (c == 0x3)
+            exit(0);
+    }
+
+    printf("\n\r");
+    seq[i] = '\0';
+
+    printf("          ");
+    for (i = 0; seq[i] != 0; i++) {
+        printf(" %4d", seq[i]);
+    }
+    printf("\n\r");
+
+    seq[i] = '\0';
+    printf("          ");
+    for (i = 0; seq[i] != '\0'; i++) {
+        s[3] = c = seq[i];
+        printf(" %s", (isprint(c) && c != '\n' && c != '\r' && c != '\t') ?
+               (char *)s : (c == ESC ? "ESC" : "."));
+    }
+    printf("\n\r");
+}
+
+
+/*
+ * default passthru allocator
+ */
+
+static void *_brl_default_alloc(size_t size, const char *file, int line,
+                                const char *func)
+{
+    BRL_UNUSED(file);
+    BRL_UNUSED(line);
+    BRL_UNUSED(func);
+
+    return malloc(size);
+}
+
+static void *_brl_default_realloc(void *ptr, size_t size,
+                                  const char *file, int line, const char *func)
+{
+    BRL_UNUSED(file);
+    BRL_UNUSED(line);
+    BRL_UNUSED(func);
+
+    return realloc(ptr, size);
+}
+
+static char *_brl_default_strdup(const char *str, const char *file, int line,
+                                 const char *func)
+{
+    BRL_UNUSED(file);
+    BRL_UNUSED(line);
+    BRL_UNUSED(func);
+
+    return strdup(str);
+}
+
+static void _brl_default_free(void *ptr,
+                              const char *file, int line, const char *func)
+{
+    BRL_UNUSED(file);
+    BRL_UNUSED(line);
+    BRL_UNUSED(func);
+
+    free(ptr);
+}
+
+
+/* By default we use the libc memory allocator. */
+brl_allocator_t __brl_mm = {
+    .allocfn   = _brl_default_alloc,
+    .reallocfn = _brl_default_realloc,
+    .strdupfn  = _brl_default_strdup,
+    .freefn    = _brl_default_free
+};
+
+/* Once an allocation is done, this will block changing the allocator. */
+int __brl_mm_busy = FALSE;
+
+
+int brl_set_allocator(brl_allocator_t *allocator)
+{
+    if (!__brl_mm_busy) {
+        __brl_mm = *allocator;
+
+        return 0;
+    }
+    else {
+        errno = EBUSY;
+
+        return -1;
+    }
+}
diff --git a/src/breedline/breedline.h b/src/breedline/breedline.h
new file mode 100644 (file)
index 0000000..44378e8
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BREEDLINE_H__
+#define __BREEDLINE_H__
+
+#include <breedline/macros.h>
+
+BRL_CDECL_BEGIN
+
+/** Default history buffer size (in number of items). */
+#define BRL_DEFAULT_HISTORY 64
+
+/** Type for opaque breedline context. */
+struct brl_s;
+typedef struct brl_s brl_t;
+
+/** Create a new breedline context for the given file descriptor. */
+brl_t *brl_create(int fd, const char *prompt);
+
+/** Destroy the given context. */
+void brl_destroy(brl_t *brl);
+
+/** Set breedline prompt. */
+int brl_set_prompt(brl_t *brl, const char *prompt);
+
+/** Hide breedline prompt. */
+void brl_hide_prompt(brl_t *brl);
+
+/** Show breedline prompt. */
+void brl_show_prompt(brl_t *brl);
+
+/** Limit the size of history to the given number of entries. */
+int brl_limit_history(brl_t *brl, size_t size);
+
+/** Read a single line of input and put it to the given buffer. */
+int brl_read_line(brl_t *brl, char *buf, size_t size);
+
+/** Add an entry to history. Replaces oldest entry if history buffer is full. */
+int brl_add_history(brl_t *brl, const char *entry);
+
+/** In put delivery callback type, used when running in mainloop mode. */
+typedef void (*brl_line_cb_t)(brl_t *brl, const char *line, void *user_data);
+
+/** Breedline mainloop subset abstraction. */
+typedef struct {
+    void *(*add_watch)(void *ml, int fd,
+                       void (*cb)(int fd, int events, void *user_data),
+                       void *user_data);
+    void  (*del_watch)(void *w);
+} brl_mainloop_ops_t;
+
+/** Set up the given context to be pumped by the given mainloop. */
+int brl_use_mainloop(brl_t *brl, void *ml, brl_mainloop_ops_t *ops,
+                     brl_line_cb_t cb, void *user_data);
+
+/** Memory allocation operations. */
+typedef struct {
+    void *(*allocfn)(size_t size, const char *file, int line, const char *func);
+    void *(*reallocfn)(void *ptr, size_t size, const char *file, int line,
+                       const char *func);
+    char *(*strdupfn)(const char *str, const char *file, int line,
+                      const char *func);
+    void  (*freefn)(void *ptr, const char *file, int line, const char *func);
+} brl_allocator_t;
+
+/** Override the default memory allocator. */
+int brl_set_allocator(brl_allocator_t *allocator);
+
+BRL_CDECL_END
+
+#endif /* __BREEDLINE_H__ */
diff --git a/src/breedline/breedline.pc.in b/src/breedline/breedline.pc.in
new file mode 100644 (file)
index 0000000..ab9a02a
--- /dev/null
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: breedline
+Description: A BSD-licensed simplistic alternative to readline.
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lbreedline
+Cflags: -I${includedir}
diff --git a/src/breedline/macros.h b/src/breedline/macros.h
new file mode 100644 (file)
index 0000000..894c262
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BREEDLINE_MACROS_H__
+#define __BREEDLINE_MACROS_H__
+
+#define BRL_UNUSED(var) (void)var
+
+#ifdef __GNUC__
+#    define BRL_UNLIKELY(cond) __builtin_expect((cond), 0)
+#    define BRL_LIKELY(cond)   __builtin_expect((cond), 1)
+#else
+#    define BRL_UNLIKELY(cond) (cond)
+#    define BRL_LIKELY(cond)   (cond)
+#endif
+
+#ifdef __cplusplus
+#    define BRL_CDECL_BEGIN extern "C" {
+#    define BRL_CDECL_END   }
+#else
+#    define BRL_CDECL_BEGIN
+#    define BRL_CDECL_END
+#endif
+
+#endif /* __BREEDLINE_MACROS_H__ */
diff --git a/src/breedline/mm.h b/src/breedline/mm.h
new file mode 100644 (file)
index 0000000..6337659
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BREEDLINE_MM_H__
+#define __BREEDLINE_MM_H__
+
+#include <stdlib.h>
+#include <string.h>
+
+/* Macro that can be used to pass the location of its usage further. */
+#define BRL_LOC __FILE__, __LINE__, __func__
+
+/* Memory allocation macros used by breedline. */
+#define brl_allocz(size) ({                                        \
+            void   *_ptr;                                          \
+            size_t  _size = size;                                  \
+                                                                   \
+            if ((_ptr = __brl_mm.allocfn(_size, BRL_LOC)) != NULL) \
+                memset(_ptr, 0, _size);                            \
+                                                                   \
+            __brl_mm_busy = TRUE;                                  \
+                                                                   \
+            _ptr; })
+
+#define brl_reallocz(ptr, o, n) ({                               \
+            typeof(ptr) _ptr;                                    \
+            size_t      _size = sizeof(*_ptr) * (n);             \
+            typeof(n)   _n    = (n);                             \
+            typeof(o)   _o;                                      \
+                                                                 \
+            if ((ptr) != NULL)                                   \
+                _o = o;                                          \
+            else                                                 \
+                _o = 0;                                          \
+                                                                 \
+            _ptr = __brl_mm.reallocfn(ptr, _size, BRL_LOC);      \
+            if (_ptr != NULL || _n == 0) {                       \
+                if ((unsigned)(_n) > (unsigned)(_o))             \
+                    memset(_ptr + (_o), 0,                       \
+                           ((_n) - (_o)) * sizeof(*_ptr));       \
+                ptr = _ptr;                                      \
+            }                                                    \
+                                                                 \
+            __brl_mm_busy = TRUE;                                \
+                                                                 \
+            _ptr; })
+
+#define brl_strdup(s) ({                                         \
+            __brl_mm_busy = TRUE;                                \
+                                                                 \
+            __brl_mm.strdupfn((s), BRL_LOC);                     \
+        })
+
+#define brl_free(ptr) __brl_mm.freefn((ptr), BRL_LOC)
+
+extern brl_allocator_t __brl_mm;
+extern int __brl_mm_busy;
+
+#endif /* __BREEDLINE_MM_H__ */
diff --git a/src/breedline/tests/Makefile.am b/src/breedline/tests/Makefile.am
new file mode 100644 (file)
index 0000000..90b2b32
--- /dev/null
@@ -0,0 +1,28 @@
+AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir)
+
+# murphy breedline test
+noinst_PROGRAMS = breedline-murphy-test
+
+breedline_murphy_test_SOURCES = breedline-murphy-test.c
+breedline_murphy_test_CFLAGS  = $(AM_CFLAGS)
+breedline_murphy_test_LDADD   = ../../libbreedline-murphy.la   \
+                               ../../libbreedline.la           \
+                               ../../libmurphy-common.la
+
+# basic (blocking) breedline test
+noinst_PROGRAMS += breedline-test
+
+breedline_test_SOURCES = breedline-test.c
+breedline_test_CFLAGS  = $(AM_CFLAGS)
+breedline_test_LDADD   = ../../libbreedline.la
+
+if GLIB_ENABLED
+# breedline glib test
+noinst_PROGRAMS += breedline-glib-test
+
+breedline_glib_test_SOURCES = breedline-glib-test.c
+breedline_glib_test_CFLAGS  = $(AM_CFLAGS) $(GLIB_CFLAGS)
+breedline_glib_test_LDADD   = ../../libbreedline-glib.la       \
+                             ../../libbreedline.la             \
+                             $(GLIB_LIBS)
+endif
diff --git a/src/breedline/tests/breedline-glib-test.c b/src/breedline/tests/breedline-glib-test.c
new file mode 100644 (file)
index 0000000..71ad7bd
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/poll.h>
+
+#include <glib.h>
+
+#include "breedline/breedline-glib.h"
+
+static void line_cb(brl_t *brl, const char *line, void *user_data)
+{
+    GMainLoop *ml = (GMainLoop *)user_data;
+
+    (void)brl;
+
+    printf("got line: '%s'\n", line);
+
+    if (!strcmp(line, "exit") || !strcmp(line, "quit"))
+        g_main_loop_quit(ml);
+    else {
+        if (brl_add_history(brl, line) != 0)
+            fprintf(stderr, "Failed to save history entry.\n");
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    GMainLoop  *ml;
+    brl_t      *brl;
+    int         fd;
+    const char *prompt;
+
+    ml = g_main_loop_new(NULL, FALSE);
+
+    if (ml == NULL) {
+        fprintf(stderr, "Failed to create mainloop.\n");
+        exit(1);
+    }
+
+    fd     = fileno(stdin);
+    prompt = argc > 1 ? argv[1] : "breedline-glib";
+
+    brl = brl_create_with_glib(fd, prompt, ml, line_cb, ml);
+
+    if (brl == NULL) {
+        fprintf(stderr, "Failed to create breedline context (%d: %s).\n",
+                errno, strerror(errno));
+        exit(1);
+    }
+
+    brl_show_prompt(brl);
+    g_main_loop_run(ml);
+    brl_destroy(brl);
+    g_main_loop_unref(ml);
+
+    return 0;
+}
diff --git a/src/breedline/tests/breedline-murphy-test.c b/src/breedline/tests/breedline-murphy-test.c
new file mode 100644 (file)
index 0000000..bf82973
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common.h>
+
+#include "breedline/breedline-murphy.h"
+
+static void line_cb(brl_t *brl, const char *line, void *user_data)
+{
+    mrp_mainloop_t *ml = (mrp_mainloop_t *)user_data;
+
+    MRP_UNUSED(brl);
+
+    printf("got line: '%s'\n", line);
+
+    if (!strcmp(line, "exit") || !strcmp(line, "quit"))
+        mrp_mainloop_quit(ml, 0);
+    else {
+        if (brl_add_history(brl, line) != 0)
+            fprintf(stderr, "Failed to save history entry.\n");
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    mrp_mainloop_t *ml;
+    brl_t          *brl;
+    int             fd;
+    const char     *prompt;
+
+    ml = mrp_mainloop_create();
+
+    if (ml == NULL) {
+        fprintf(stderr, "Failed to create mainloop.\n");
+        exit(1);
+    }
+
+    fd     = fileno(stdin);
+    prompt = argc > 1 ? argv[1] : "breedline-murphy";
+
+    brl = brl_create_with_murphy(fd, prompt, ml, line_cb, ml);
+
+    if (brl == NULL) {
+        fprintf(stderr, "Failed to create breedline context (%d: %s).\n",
+                errno, strerror(errno));
+        exit(1);
+    }
+
+    brl_show_prompt(brl);
+    mrp_mainloop_run(ml);
+    brl_destroy(brl);
+    mrp_mainloop_destroy(ml);
+
+    return 0;
+}
diff --git a/src/breedline/tests/breedline-test.c b/src/breedline/tests/breedline-test.c
new file mode 100644 (file)
index 0000000..ad2081f
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/poll.h>
+
+#include <breedline/breedline.h>
+
+
+int main(int argc, char *argv[])
+{
+    brl_t *brl;
+    char   line[1024];
+    int    n;
+
+    brl = brl_create(fileno(stdin), argc > 1 ? argv[1] : "breedline");
+
+    if (brl == NULL) {
+        fprintf(stderr, "Failed to create breedline context (%d: %s).\n",
+                errno, strerror(errno));
+        exit(1);
+    }
+
+    while ((n = brl_read_line(brl, line, sizeof(line))) >= 0) {
+        printf("got line: '%s'\n", line);
+
+        if (!strcmp(line, "exit") || !strcmp(line, "quit"))
+            break;
+        else {
+            if (brl_add_history(brl, line) != 0)
+                fprintf(stderr, "Failed to save history entry.\n");
+        }
+    }
+
+    brl_destroy(brl);
+
+    return 0;
+}
diff --git a/src/common.h b/src/common.h
new file mode 100644 (file)
index 0000000..dc7af31
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_COMMON_H__
+#define __MURPHY_COMMON_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/file-utils.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+
+#endif
diff --git a/src/common/Makefile b/src/common/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/common/dbus-error.h b/src/common/dbus-error.h
new file mode 100644 (file)
index 0000000..1704cec
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DBUS_ERROR_H__
+#define __MURPHY_DBUS_ERROR_H__
+
+#define __ERR(error) "org.freedesktop.DBus.Error."#error
+
+#define MRP_DBUS_ERROR_FAILED                     __ERR(Failed)
+#define MRP_DBUS_ERROR_NO_MEMORY                  __ERR(NoMemory)
+#define MRP_DBUS_ERROR_SERVICE_UNKNOWN            __ERR(ServiceUnknown)
+#define MRP_DBUS_ERROR_NAME_HAS_NO_OWNER          __ERR(NameHasNoOwner)
+#define MRP_DBUS_ERROR_NO_REPLY                   __ERR(NoReply)
+#define MRP_DBUS_ERROR_IO_ERROR                   __ERR(IOError)
+#define MRP_DBUS_ERROR_BAD_ADDRESS                __ERR(BadAddress)
+#define MRP_DBUS_ERROR_NOT_SUPPORTED              __ERR(NotSupported)
+#define MRP_DBUS_ERROR_LIMITS_EXCEEDED            __ERR(LimitsExceeded)
+#define MRP_DBUS_ERROR_ACCESS_DENIED              __ERR(AccessDenied)
+#define MRP_DBUS_ERROR_AUTH_FAILED                __ERR(AuthFailed)
+#define MRP_DBUS_ERROR_NO_SERVER                  __ERR(NoServer)
+#define MRP_DBUS_ERROR_TIMEOUT                    __ERR(Timeout)
+#define MRP_DBUS_ERROR_NO_NETWORK                 __ERR(NoNetwork)
+#define MRP_DBUS_ERROR_ADDRESS_IN_USE             __ERR(AddressInUse)
+#define MRP_DBUS_ERROR_DISCONNECTED               __ERR(Disconnected)
+#define MRP_DBUS_ERROR_INVALID_ARGS               __ERR(InvalidArgs)
+#define MRP_DBUS_ERROR_FILE_NOT_FOUND             __ERR(FileNotFound)
+#define MRP_DBUS_ERROR_FILE_EXISTS                __ERR(FileExists)
+#define MRP_DBUS_ERROR_UNKNOWN_METHOD             __ERR(UnknownMethod)
+#define MRP_DBUS_ERROR_UNKNOWN_OBJECT             __ERR(UnknownObject)
+#define MRP_DBUS_ERROR_UNKNOWN_INTERFACE          __ERR(UnknownInterface)
+#define MRP_DBUS_ERROR_UNKNOWN_PROPERTY           __ERR(UnknownProperty)
+#define MRP_DBUS_ERROR_PROPERTY_READ_ONLY         __ERR(PropertyReadOnly)
+#define MRP_DBUS_ERROR_TIMED_OUT                  __ERR(TimedOut)
+#define MRP_DBUS_ERROR_MATCH_RULE_NOT_FOUND       __ERR(MatchRuleNotFound)
+#define MRP_DBUS_ERROR_MATCH_RULE_INVALID         __ERR(MatchRuleInvalid)
+#define MRP_DBUS_ERROR_SPAWN_EXEC_FAILED          __ERR(Spawn.ExecFailed)
+#define MRP_DBUS_ERROR_SPAWN_FORK_FAILED          __ERR(Spawn.ForkFailed)
+#define MRP_DBUS_ERROR_SPAWN_CHILD_EXITED         __ERR(Spawn.ChildExited)
+#define MRP_DBUS_ERROR_SPAWN_CHILD_SIGNALED       __ERR(Spawn.ChildSignaled)
+#define MRP_DBUS_ERROR_SPAWN_FAILED               __ERR(Spawn.Failed)
+#define MRP_DBUS_ERROR_SPAWN_SETUP_FAILED         __ERR(Spawn.FailedToSetup)
+#define MRP_DBUS_ERROR_SPAWN_CONFIG_INVALID       __ERR(Spawn.ConfigInvalid)
+#define MRP_DBUS_ERROR_SPAWN_SERVICE_INVALID      __ERR(Spawn.ServiceNotValid)
+#define MRP_DBUS_ERROR_SPAWN_SERVICE_NOT_FOUND    __ERR(Spawn.ServiceNotFound)
+#define MRP_DBUS_ERROR_SPAWN_PERMISSIONS_INVALID  __ERR(Spawn.PermissionsInvalid)
+#define MRP_DBUS_ERROR_SPAWN_FILE_INVALID         __ERR(Spawn.FileInvalid)
+#define MRP_DBUS_ERROR_SPAWN_NO_MEMORY            __ERR(Spawn.NoMemory)
+#define MRP_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN    __ERR(UnixProcessIdUnknown)
+#define MRP_DBUS_ERROR_INVALID_SIGNATURE          __ERR(InvalidSignature)
+#define MRP_DBUS_ERROR_INVALID_FILE_CONTENT       __ERR(InvalidFileContent)
+#define MRP_DBUS_ERROR_ADT_AUDIT_DATA_UNKNOWN     __ERR(AdtAuditDataUnknown)
+#define MRP_DBUS_ERROR_OBJECT_PATH_IN_USE         __ERR(ObjectPathInUse)
+#define MRP_DBUS_ERROR_INCONSISTENT_MESSAGE       __ERR(InconsistentMessage)
+#define MRP_DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN __ERR(SELinuxSecurityContextUnknown)
+
+#endif /* __MURPHY_MRP_DBUS_ERROR_H__ */
diff --git a/src/common/dbus-libdbus-glue.c b/src/common/dbus-libdbus-glue.c
new file mode 100644 (file)
index 0000000..d1bb093
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <dbus/dbus.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+
+typedef struct dbus_glue_s dbus_glue_t;
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_io_watch_t  *mw;
+    DBusWatch       *dw;
+    mrp_list_hook_t  hook;
+} watch_t;
+
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_timer_t     *mt;
+    DBusTimeout     *dt;
+    mrp_list_hook_t  hook;
+} timeout_t;
+
+
+struct dbus_glue_s {
+    DBusConnection  *conn;
+    mrp_mainloop_t  *ml;
+    mrp_list_hook_t  watches;
+    mrp_list_hook_t  timers;
+    mrp_deferred_t  *pump;
+};
+
+
+static dbus_int32_t data_slot = -1;
+
+static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events,
+                           void *user_data)
+{
+    watch_t        *watch = (watch_t *)user_data;
+    DBusConnection *conn  = watch->glue->conn;
+    unsigned int    mask  = 0;
+
+    MRP_UNUSED(mw);
+    MRP_UNUSED(fd);
+
+    if (events & MRP_IO_EVENT_IN)
+        mask |= DBUS_WATCH_READABLE;
+    if (events & MRP_IO_EVENT_OUT)
+        mask |= DBUS_WATCH_WRITABLE;
+    if (events & MRP_IO_EVENT_HUP)
+        mask |= DBUS_WATCH_HANGUP;
+    if (events & MRP_IO_EVENT_ERR)
+        mask |= DBUS_WATCH_ERROR;
+
+    dbus_connection_ref(conn);
+    dbus_watch_handle(watch->dw, mask);
+    dbus_connection_unref(conn);
+}
+
+
+static void watch_freed_cb(void *data)
+{
+    watch_t *watch = (watch_t *)data;
+
+    if (watch != NULL) {
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+        mrp_free(watch);
+    }
+}
+
+
+static dbus_bool_t add_watch(DBusWatch *dw, void *data)
+{
+    dbus_glue_t    *glue = (dbus_glue_t *)data;
+    watch_t        *watch;
+    mrp_io_watch_t *mw;
+    mrp_io_event_t  mask;
+    int             fd;
+    unsigned int    flags;
+
+    mrp_debug("adding D-BUS watch %p (%s)", dw,
+              dbus_watch_get_enabled(dw) ? "enabled" : "disabled");
+
+    if (!dbus_watch_get_enabled(dw))
+        return TRUE;
+
+    fd    = dbus_watch_get_unix_fd(dw);
+    flags = dbus_watch_get_flags(dw);
+    mask  = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR;
+
+    if (flags & DBUS_WATCH_READABLE)
+        mask |= MRP_IO_EVENT_IN;
+    if (flags & DBUS_WATCH_WRITABLE)
+        mask |= MRP_IO_EVENT_OUT;
+
+    mrp_debug("event mask for fd %d: %s%s", fd,
+              mask & MRP_IO_EVENT_IN  ? "read"  : "",
+              mask & MRP_IO_EVENT_OUT ? "write" : "");
+
+    if ((watch = mrp_allocz(sizeof(*watch))) != NULL) {
+        mrp_list_init(&watch->hook);
+        mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch);
+
+        if (mw != NULL) {
+            watch->glue = glue;
+            watch->mw   = mw;
+            watch->dw   = dw;
+            dbus_watch_set_data(dw, watch, watch_freed_cb);
+            mrp_list_append(&glue->watches, &watch->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(watch);
+    }
+
+    return FALSE;
+}
+
+
+static void del_watch(DBusWatch *dw, void *data)
+{
+    watch_t *watch = (watch_t *)dbus_watch_get_data(dw);
+
+    MRP_UNUSED(data);
+
+    mrp_debug("deleting D-BUS watch %p...", dw);
+
+    if (watch != NULL) {
+        mrp_del_io_watch(watch->mw);
+        watch->mw = NULL;
+    }
+}
+
+
+static void toggle_watch(DBusWatch *dw, void *data)
+{
+    mrp_debug("Toggling D-BUS watch %p...", dw);
+
+    if (dbus_watch_get_enabled(dw))
+        add_watch(dw, data);
+    else
+        del_watch(dw, data);
+}
+
+
+static void dispatch_timeout(mrp_timer_t *mt, void *user_data)
+{
+    timeout_t *timer = (timeout_t *)user_data;
+
+    MRP_UNUSED(mt);
+
+    mrp_debug("dispatching D-BUS timeout %p...", timer->dt);
+
+    dbus_timeout_handle(timer->dt);
+}
+
+
+static void timeout_freed_cb(void *data)
+{
+    timeout_t *timer = (timeout_t *)data;
+
+    if (timer != NULL) {
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+}
+
+
+static dbus_bool_t add_timeout(DBusTimeout *dt, void *data)
+{
+    dbus_glue_t  *glue = (dbus_glue_t *)data;
+    timeout_t    *timer;
+    mrp_timer_t  *mt;
+    unsigned int  msecs;
+
+    mrp_debug("adding D-BUS timeout %p...", dt);
+
+    if ((timer = mrp_allocz(sizeof(*timer))) != NULL) {
+        mrp_list_init(&timer->hook);
+        msecs = dbus_timeout_get_interval(dt);
+        mt    = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer);
+
+        if (mt != NULL) {
+            timer->glue = glue;
+            timer->mt   = mt;
+            timer->dt   = dt;
+            dbus_timeout_set_data(dt, timer, timeout_freed_cb);
+            mrp_list_append(&glue->timers, &timer->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(timer);
+    }
+
+    return FALSE;
+}
+
+
+static void del_timeout(DBusTimeout *dt, void *data)
+{
+    timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt);
+
+    MRP_UNUSED(data);
+
+    mrp_debug("deleting D-BUS timeout %p...", dt);
+
+    if (timer != NULL) {
+        mrp_del_timer(timer->mt);
+        timer->mt = NULL;
+    }
+}
+
+
+static void toggle_timeout(DBusTimeout *dt, void *data)
+{
+    mrp_debug("toggling D-BUS timeout %p...", dt);
+
+    if (dbus_timeout_get_enabled(dt))
+        add_timeout(dt, data);
+    else
+        del_timeout(dt, data);
+}
+
+
+static void wakeup_mainloop(void *data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)data;
+
+    mrp_debug("waking up mainloop...");
+
+    mrp_enable_deferred(glue->pump);
+}
+
+
+static void glue_free_cb(void *data)
+{
+    dbus_glue_t     *glue = (dbus_glue_t *)data;
+    mrp_list_hook_t *p, *n;
+    watch_t         *watch;
+    timeout_t       *timer;
+
+    mrp_list_foreach(&glue->watches, p, n) {
+        watch = mrp_list_entry(p, typeof(*watch), hook);
+
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+
+        mrp_free(watch);
+    }
+
+    mrp_list_foreach(&glue->timers, p, n) {
+        timer = mrp_list_entry(p, typeof(*timer), hook);
+
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+
+    mrp_free(glue);
+}
+
+
+static void pump_cb(mrp_deferred_t *d, void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    mrp_debug("dispatching dbus connection %p...", glue->conn);
+
+    if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE)
+        mrp_disable_deferred(d);
+}
+
+
+static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status,
+                               void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    MRP_UNUSED(conn);
+
+    switch (status) {
+    case DBUS_DISPATCH_COMPLETE:
+        mrp_debug("dispatching status for %p: complete", conn);
+        mrp_disable_deferred(glue->pump);
+        break;
+
+    case DBUS_DISPATCH_DATA_REMAINS:
+    case DBUS_DISPATCH_NEED_MEMORY:
+    default:
+        mrp_debug("dispatching status for %p: not complete yet", conn);
+        mrp_enable_deferred(glue->pump);
+        break;
+    }
+}
+
+
+int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn)
+{
+    dbus_glue_t *glue;
+
+    if (!dbus_connection_allocate_data_slot(&data_slot))
+        return FALSE;
+
+    if (dbus_connection_get_data(conn, data_slot) != NULL)
+        return FALSE;
+
+    if ((glue = mrp_allocz(sizeof(*glue))) != NULL) {
+        mrp_list_init(&glue->watches);
+        mrp_list_init(&glue->timers);
+        glue->pump = mrp_add_deferred(ml, pump_cb, glue);
+
+        if (glue->pump == NULL) {
+            mrp_free(glue);
+            return FALSE;
+        }
+
+        glue->ml   = ml;
+        glue->conn = conn;
+    }
+    else
+        return FALSE;
+
+    if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb))
+        return FALSE;
+
+    dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb,
+                                                 glue, NULL);
+
+    dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop,
+                                             glue, NULL);
+
+    return
+        dbus_connection_set_watch_functions(conn, add_watch, del_watch,
+                                            toggle_watch, glue, NULL) &&
+            dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout,
+                                              toggle_timeout, glue, NULL);
+}
diff --git a/src/common/dbus-libdbus-transport.c b/src/common/dbus-libdbus-transport.c
new file mode 100644 (file)
index 0000000..e19dac8
--- /dev/null
@@ -0,0 +1,1745 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/dbus-libdbus.h>
+#include <murphy/common/dbus-transport.h>
+
+#define DBUS  "dbus"
+#define DBUSL 4
+
+#define TRANSPORT_PATH       "/murphy/transport"
+#define TRANSPORT_INTERFACE  "Murphy.Transport"
+#define TRANSPORT_MESSAGE    "DeliverMessage"
+#define TRANSPORT_DATA       "DeliverData"
+#define TRANSPORT_RAW        "DeliverRaw"
+#define TRANSPORT_METHOD     "DeliverMessage"
+
+#define ANY_ADDRESS          "any"
+
+typedef struct {
+    MRP_TRANSPORT_PUBLIC_FIELDS;         /* common transport fields */
+    mrp_dbus_t     *dbus;                /* D-BUS connection */
+    int             bound : 1;           /* whether bound to an address */
+    int             peer_resolved : 1;   /* connected and peer name known */
+    mrp_dbusaddr_t  local;               /* address we're bound to */
+    mrp_dbusaddr_t  remote;              /* address we're connected to */
+} dbus_t;
+
+
+static uint32_t nauto;                   /* for autobinding */
+
+
+static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data);
+static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data);
+static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data);
+
+static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data);
+
+static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  mrp_msg_t *msg);
+static mrp_msg_t *msg_decode(mrp_dbus_msg_t *msg, const char **sender_id);
+
+static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination,
+                                   const char *path, const char *interface,
+                                   const char *member, const char *sender_id,
+                                   void *data, uint16_t tag);
+static void *data_decode(mrp_dbus_msg_t *msg, uint16_t *tag,
+                         const char **sender_id);
+
+static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  void *data, size_t size);
+static void *raw_decode(mrp_dbus_msg_t *msg, size_t *sizep,
+                        const char **sender_id);
+
+
+static socklen_t parse_address(const char *str, mrp_dbusaddr_t *addr,
+                               socklen_t size)
+{
+    const char *p, *e;
+    char       *q;
+    size_t      l, n;
+
+    if (size < sizeof(*addr)) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    if (strncmp(str, DBUS":", DBUSL + 1))
+        return 0;
+    else
+        str += DBUSL + 1;
+
+    /*
+     * The format of the address is
+     *     dbus:[bus-address]@address/path
+     * eg.
+     *     dbus:[session]@:1.33/client1, or
+     *     dbus:[unix:abstract=/tmp/dbus-Xx2Kpi...a572]@:1.33/client1
+     */
+
+    p = str;
+    q = addr->db_fqa;
+    l = sizeof(addr->db_fqa) - 1;
+
+    /* get bus address */
+    if (*p != '[') {
+        errno = EINVAL;
+        return 0;
+    }
+    else
+        p++;
+
+    e = strchr(p, ']');
+
+    if (e == NULL) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    n = e - p;
+    if (n >= l) {
+        errno = ENAMETOOLONG;
+        return 0;
+    }
+
+    /* save bus address */
+    strncpy(q, p, n);
+    q[n] = '\0';
+    addr->db_bus = q;
+
+    q += n + 1;
+    l -= n + 1;
+    p  = e + 1;
+
+    /* get (local or remote) address on bus */
+    if (*p != '@')
+        addr->db_addr = ANY_ADDRESS;
+    else {
+        p++;
+        e = strchr(p, '/');
+
+        if (e == NULL) {
+            errno = EINVAL;
+            return 0;
+        }
+
+        n = e - p;
+        if (n >= l) {
+            errno = ENAMETOOLONG;
+            return 0;
+        }
+
+        /* save address on bus */
+        strncpy(q, p, n);
+        q[n] = '\0';
+        addr->db_addr = q;
+
+        q += n + 1;
+        l -= n + 1;
+        p  = e;
+    }
+
+    /* get object path */
+    if (*p != '/') {
+        errno = EINVAL;
+        return 0;
+    }
+
+    n = snprintf(q, l, "%s%s", TRANSPORT_PATH, p);
+    if (n >= l) {
+        errno = ENAMETOOLONG;
+        return 0;
+    }
+
+    addr->db_path   = q;
+    addr->db_family = MRP_AF_DBUS;
+
+    return sizeof(*addr);
+}
+
+
+static mrp_dbusaddr_t *copy_address(mrp_dbusaddr_t *dst, mrp_dbusaddr_t *src)
+{
+    char   *p, *q;
+    size_t  l, n;
+
+    dst->db_family = src->db_family;
+
+    /* copy bus address */
+    p = src->db_bus;
+    q = dst->db_fqa;
+    l = sizeof(dst->db_fqa) - 1;
+
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_bus = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    /* copy address */
+    p = src->db_addr;
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_addr = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    /* copy path */
+    p = src->db_path;
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_path = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    return dst;
+}
+
+
+static inline int check_address(mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addr;
+
+    return (a && a->db_family == MRP_AF_DBUS && addrlen == sizeof(*a));
+}
+
+
+static size_t peer_address(mrp_sockaddr_t *addrp, const char *sender,
+                           const char *path)
+{
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    const char     *p;
+    char           *q;
+    int             l, n;
+
+    q = addr->db_fqa;
+    l = sizeof(addr->db_fqa) - 1;
+    p = ANY_ADDRESS;
+    n = 3;
+
+    addr->db_bus = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    addr->db_addr = q;
+    p = sender;
+    n = strlen(sender);
+
+    if (l < n + 1)
+        return 0;
+
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    addr->db_path = q;
+    p = path;
+    n = strlen(p);
+
+    if (l < n + 1)
+        return 0;
+
+    memcpy(q, p, n + 1);
+
+    return sizeof(addrp);
+}
+
+
+static socklen_t dbus_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    socklen_t len;
+
+    len = parse_address(str, (mrp_dbusaddr_t *)addr, size);
+
+    if (len > 0) {
+        if (typep != NULL)
+            *typep = DBUS;
+    }
+
+    return len;
+}
+
+
+static int dbus_open(mrp_transport_t *mt)
+{
+    MRP_UNUSED(mt);
+
+    return TRUE;
+}
+
+
+static int dbus_createfrom(mrp_transport_t *mt, void *conn)
+{
+    dbus_t     *t    = (dbus_t *)mt;
+    mrp_dbus_t *dbus = (mrp_dbus_t *)conn;
+
+    t->dbus = mrp_dbus_ref(dbus);
+
+    if (t->dbus != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static int dbus_bind(mrp_transport_t *mt, mrp_sockaddr_t *addrp,
+                     socklen_t addrlen)
+{
+    dbus_t          *t    = (dbus_t *)mt;
+    mrp_dbus_t      *dbus = NULL;
+    mrp_dbusaddr_t  *addr = (mrp_dbusaddr_t *)addrp;
+    int            (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *);
+    const char      *method;
+
+    if (t->bound) {
+        errno = EINVAL;
+        goto fail;
+    }
+
+    if (!check_address(addrp, addrlen)) {
+        errno = EINVAL;
+        goto fail;
+    }
+
+    if (t->dbus == NULL) {
+        dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL);
+
+        if (dbus == NULL) {
+            errno = ECONNRESET;
+            goto fail;
+        }
+        else {
+            t->dbus = dbus;
+
+            if (addr->db_addr != NULL && strcmp(addr->db_addr, ANY_ADDRESS)) {
+                if (!mrp_dbus_acquire_name(t->dbus, addr->db_addr, NULL)) {
+                    errno = EADDRINUSE; /* XXX TODO, should check error... */
+                    goto fail;
+                }
+            }
+        }
+    }
+    else {
+        /* XXX TODO: should check given address against address of the bus */
+    }
+
+    copy_address(&t->local, addr);
+
+    switch (t->mode) {
+    case MRP_TRANSPORT_MODE_DATA:
+        method = TRANSPORT_DATA;
+        cb     = dbus_data_cb;
+        break;
+    case MRP_TRANSPORT_MODE_RAW:
+        method = TRANSPORT_RAW;
+        cb     = dbus_raw_cb;
+        break;
+    case MRP_TRANSPORT_MODE_MSG:
+        method = TRANSPORT_MESSAGE;
+        cb     = dbus_msg_cb;
+        break;
+    default:
+        errno = EPROTOTYPE;
+        goto fail;
+    }
+
+    if (!mrp_dbus_export_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE,
+                                method, cb, t)) {
+        errno = EIO;
+        goto fail;
+    }
+    else {
+        t->bound = TRUE;
+        return TRUE;
+    }
+
+ fail:
+    if (dbus != NULL) {
+        mrp_dbus_unref(dbus);
+        t->dbus = NULL;
+    }
+
+    return FALSE;
+}
+
+
+static int dbus_autobind(mrp_transport_t *mt, mrp_sockaddr_t *addrp)
+{
+    mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addrp;
+    char            astr[MRP_SOCKADDR_SIZE];
+    mrp_sockaddr_t  addr;
+    socklen_t       alen;
+
+    snprintf(astr, sizeof(astr), "dbus:[%s]/auto/%u", a->db_bus, nauto++);
+
+    alen = dbus_resolve(astr, &addr, sizeof(addr), NULL);
+
+    if (alen > 0)
+        return dbus_bind(mt, &addr, alen);
+    else
+        return FALSE;
+}
+
+
+static void dbus_close(mrp_transport_t *mt)
+{
+    dbus_t         *t = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr;
+    const char     *method;
+    int           (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *);
+
+    if (t->bound) {
+        switch (t->mode) {
+        case MRP_TRANSPORT_MODE_DATA:
+            method = TRANSPORT_DATA;
+            cb     = dbus_data_cb;
+            break;
+        case MRP_TRANSPORT_MODE_RAW:
+            method = TRANSPORT_RAW;
+            cb     = dbus_raw_cb;
+            break;
+        default:
+        case MRP_TRANSPORT_MODE_MSG:
+            method = TRANSPORT_MESSAGE;
+            cb     = dbus_msg_cb;
+        }
+
+        addr = (mrp_dbusaddr_t *)&t->local;
+        mrp_dbus_remove_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE,
+                               method, cb, t);
+    }
+
+    if (t->connected && t->remote.db_addr != NULL)
+        mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t);
+
+    mrp_dbus_unref(t->dbus);
+    t->dbus = NULL;
+}
+
+
+static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    mrp_msg_t       *msg;
+
+    MRP_UNUSED(dbus);
+
+    msg = msg_decode(dmsg, &sender_path);
+
+    if (msg != NULL) {
+        sender = mrp_dbus_msg_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvmsg(mt, msg, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvmsgfrom(mt, msg, &addr, alen, mt->user_data);
+                });
+        }
+
+        mrp_msg_unref(msg);
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode message.");
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    uint16_t         tag;
+    void            *decoded;
+
+    MRP_UNUSED(dbus);
+
+    decoded = data_decode(dmsg, &tag, &sender_path);
+
+    if (decoded != NULL) {
+        sender = mrp_dbus_msg_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvdata(mt, decoded, tag, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvdatafrom(mt, decoded, tag, &addr, alen,
+                                         mt->user_data);
+                });
+        }
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode custom data message.");
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    void            *data;
+    size_t           size;
+
+    MRP_UNUSED(dbus);
+
+    data = raw_decode(dmsg, &size, &sender_path);
+
+    if (data != NULL) {
+        sender = mrp_dbus_msg_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvraw(mt, data, size, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvrawfrom(mt, data, size, &addr, alen,
+                                        mt->user_data);
+                });
+        }
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode raw message.");
+    }
+
+    return TRUE;
+}
+
+
+static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data)
+{
+    dbus_t         *t = (dbus_t *)user_data;
+    mrp_sockaddr_t  addr;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(name);
+
+    if (up) {
+        peer_address(&addr, owner, t->remote.db_path);
+        copy_address(&t->remote, (mrp_dbusaddr_t *)&addr);
+        t->peer_resolved = TRUE;
+    }
+    else {
+        /*
+         * XXX TODO:
+         *     It would be really tempting here to call
+         *         mt->evt.closed(mt, ECONNRESET, mt->user_data)
+         *     to notify the user about the fact our peer went down.
+         *     However, that would not be in line with the other
+         *     transports which call the closed event handler only
+         *     upon foricble transport closes upon errors.
+         *
+         *     The transport interface abstraction (especially the
+         *     available set of events) anyway needs some eyeballing,
+         *     so the right thing to do might be to define a new event
+         *     for disconnection and call the handler for that here...
+         */
+    }
+
+}
+
+
+static int dbus_connect(mrp_transport_t *mt, mrp_sockaddr_t *addrp,
+                        socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+
+    if (!check_address(addrp, addrlen)) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    if (t->dbus == NULL) {
+        t->dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL);
+
+        if (t->dbus == NULL) {
+            errno = ECONNRESET;
+            return FALSE;
+        }
+    }
+    else {
+        /* XXX TODO: check given address against address of the bus */
+    }
+
+    if (!t->bound)
+        if (!dbus_autobind(mt, addrp))
+            return FALSE;
+
+    if (mrp_dbus_follow_name(t->dbus, addr->db_addr, peer_state_cb, t)) {
+        copy_address(&t->remote, addr);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int dbus_disconnect(mrp_transport_t *mt)
+{
+    dbus_t *t = (dbus_t *)mt;
+
+    if (t->connected && t->remote.db_addr != NULL) {
+        mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t);
+        mrp_clear(&t->remote);
+        t->peer_resolved = FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_sendmsgto(mrp_transport_t *mt, mrp_msg_t *msg,
+                          mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    mrp_dbus_msg_t *m;
+    int             success;
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+                return FALSE;
+
+        m = msg_encode(t->dbus, addr->db_addr, addr->db_path,
+                       TRANSPORT_INTERFACE, TRANSPORT_MESSAGE,
+                       t->local.db_path, msg);
+
+        if (m != NULL) {
+            if (mrp_dbus_send_msg(t->dbus, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            mrp_dbus_msg_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_sendmsg(mrp_transport_t *mt, mrp_msg_t *msg)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_sendmsgto(mt, msg, addr, alen);
+}
+
+
+static int dbus_sendrawto(mrp_transport_t *mt, void *data, size_t size,
+                          mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    mrp_dbus_msg_t *m;
+    int             success;
+
+
+    MRP_UNUSED(mt);
+    MRP_UNUSED(data);
+    MRP_UNUSED(size);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+            return FALSE;
+
+        m = raw_encode(t->dbus, addr->db_addr, addr->db_path,
+                       TRANSPORT_INTERFACE, TRANSPORT_RAW,
+                       t->local.db_path, data, size);
+
+        if (m != NULL) {
+            if (mrp_dbus_send_msg(t->dbus, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            mrp_dbus_msg_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_sendraw(mrp_transport_t *mt, void *data, size_t size)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_sendrawto(mt, data, size, addr, alen);
+}
+
+
+static int dbus_senddatato(mrp_transport_t *mt, void *data, uint16_t tag,
+                           mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    mrp_dbus_msg_t *m;
+    int             success;
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+            return FALSE;
+
+        m = data_encode(t->dbus, addr->db_addr, addr->db_path,
+                        TRANSPORT_INTERFACE, TRANSPORT_DATA,
+                        t->local.db_path, data, tag);
+
+        if (m != NULL) {
+            if (mrp_dbus_send_msg(t->dbus, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            mrp_dbus_msg_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_senddata(mrp_transport_t *mt, void *data, uint16_t tag)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_senddatato(mt, data, tag, addr, alen);
+}
+
+
+static const char *get_array_signature(uint16_t type)
+{
+#define MAP(from, to)                                 \
+    case MRP_MSG_FIELD_##from:                        \
+        return MRP_DBUS_TYPE_##to##_AS_STRING;
+
+    switch (type) {
+        MAP(STRING, STRING);
+        MAP(BOOL  , BOOLEAN);
+        MAP(UINT8 , UINT16);
+        MAP(SINT8 , INT16);
+        MAP(UINT16, UINT16);
+        MAP(SINT16, INT16);
+        MAP(UINT32, UINT32);
+        MAP(SINT32, INT32);
+        MAP(UINT64, UINT64);
+        MAP(SINT64, INT64);
+        MAP(DOUBLE, DOUBLE);
+        MAP(BLOB  , BYTE);
+    default:
+        return NULL;
+    }
+}
+
+
+static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  mrp_msg_t *msg)
+{
+    /*
+     * Notes: There is a type mismatch between our and DBUS types for
+     *        8-bit integers (D-BUS does not have a signed 8-bit type)
+     *        and boolean types (D-BUS has uint32_t booleans, C99 fails
+     *        to specify the type and gcc uses a signed char). The
+     *        QUIRKY versions of the macros take care of these mismatches.
+     */
+
+#define BASIC_SIMPLE(_i, _mtype, _dtype, _val)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = &(_val);                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(_i, type, vptr))               \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_i, _mtype, _dtype, _mval, _dval)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type  = MRP_DBUS_TYPE_##_dtype;                               \
+            _dval = _mval;                                                \
+            vptr  = &_dval;                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(_i, type, vptr))               \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_i, _mtype, _dtype, _val)                            \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type = MRP_DBUS_TYPE_##_dtype;                        \
+                    vptr = &_val;                                         \
+                                                                          \
+                    if (!mrp_dbus_msg_append_basic(_i, type, vptr))       \
+                        goto fail;                                        \
+                    break
+
+#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type  = MRP_DBUS_TYPE_##_dtype;                       \
+                    _dvar = _mvar;                                        \
+                    vptr  = &_dvar;                                       \
+                                                                          \
+                    if (!mrp_dbus_msg_append_basic(_i, type, vptr))       \
+                        goto fail;                                        \
+                    break
+
+    mrp_dbus_msg_t  *m;
+    mrp_list_hook_t *p, *n;
+    mrp_msg_field_t *f;
+    uint16_t         base;
+    uint32_t         asize, i;
+    const char      *sig;
+    int              type, len;
+    void            *vptr;
+    dbus_bool_t      bln;
+    uint16_t         u16, blb;
+    int16_t          s16;
+
+    m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member);
+
+    if (m == NULL)
+        return NULL;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH,
+                                   (void *)sender_id))
+        goto fail;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &msg->nfield))
+        goto fail;
+
+    mrp_list_foreach(&msg->fields, p, n) {
+        f = mrp_list_entry(p, typeof(*f), hook);
+
+        if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) ||
+            !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type))
+            goto fail;
+
+        switch (f->type) {
+            BASIC_SIMPLE(m, STRING, STRING , f->str);
+            BASIC_QUIRKY(m, BOOL  , BOOLEAN, f->bln, bln);
+            BASIC_QUIRKY(m, UINT8 , UINT16 , f->u8 , u16);
+            BASIC_QUIRKY(m, SINT8 ,  INT16 , f->s8 , s16);
+            BASIC_SIMPLE(m, UINT16, UINT16 , f->u16);
+            BASIC_SIMPLE(m, SINT16,  INT16 , f->s16);
+            BASIC_SIMPLE(m, UINT32, UINT32 , f->u32);
+            BASIC_SIMPLE(m, SINT32,  INT32 , f->s32);
+            BASIC_SIMPLE(m, UINT64, UINT64 , f->u64);
+            BASIC_SIMPLE(m, SINT64,  INT64 , f->s64);
+            BASIC_SIMPLE(m, DOUBLE, DOUBLE , f->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            vptr  = f->blb;
+            len   = (int)f->size[0];
+            sig   = get_array_signature(f->type);
+            asize = len;
+            if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize))
+                goto fail;
+
+            if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            for (i = 0; i < asize; i++) {
+                blb = ((uint8_t *)f->blb)[i];
+                if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, &blb))
+                    goto fail;
+            }
+
+            if (!mrp_dbus_msg_close_container(m))
+                goto fail;
+            break;
+
+        default:
+            if (f->type & MRP_MSG_FIELD_ARRAY) {
+                base  = f->type & ~(MRP_MSG_FIELD_ARRAY);
+                asize = f->size[0];
+                sig   = get_array_signature(base);
+
+                if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize))
+                    goto fail;
+
+                if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                    goto fail;
+
+                for (i = 0; i < asize; i++) {
+                    switch (base) {
+                        ARRAY_SIMPLE(m, STRING, STRING , f->astr[i]);
+                        ARRAY_QUIRKY(m, BOOL  , BOOLEAN, f->abln[i], bln);
+                        ARRAY_QUIRKY(m, UINT8 , UINT16 , f->au8[i] , u16);
+                        ARRAY_QUIRKY(m, SINT8 ,  INT16 , f->as8[i] , s16);
+                        ARRAY_SIMPLE(m, UINT16, UINT16 , f->au16[i]);
+                        ARRAY_SIMPLE(m, SINT16,  INT16 , f->as16[i]);
+                        ARRAY_SIMPLE(m, UINT32, UINT32 , f->au32[i]);
+                        ARRAY_SIMPLE(m, SINT32,  INT32 , f->as32[i]);
+                        ARRAY_SIMPLE(m, UINT64, UINT64 , f->au64[i]);
+                        ARRAY_SIMPLE(m, DOUBLE, DOUBLE , f->adbl[i]);
+
+                    case MRP_MSG_FIELD_BLOB:
+                        goto fail;
+
+                    default:
+                        goto fail;
+                    }
+                }
+
+                if (!mrp_dbus_msg_close_container(m))
+                    goto fail;
+            }
+            else
+                goto fail;
+        }
+    }
+
+    return m;
+
+ fail:
+    if (m != NULL)
+        mrp_dbus_msg_unref(m);
+
+    errno = ECOMM;
+
+    return FALSE;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+}
+
+
+static mrp_msg_t *msg_decode(mrp_dbus_msg_t *m, const char **sender_id)
+{
+#define BASIC_SIMPLE(_i, _mtype, _dtype, _var)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_var)))                        \
+                goto fail;                                                \
+                                                                          \
+            if (!mrp_msg_append(msg, tag, type, (_var)))                  \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_dvar)))                       \
+                goto fail;                                                \
+                                                                          \
+            _mvar = _dvar;                                                \
+            if (!mrp_msg_append(msg, tag, type, (_mvar)))                 \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_i, _mtype, _dtype, _var)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_var)))                        \
+                goto fail;                                                \
+            break
+
+#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_dvar)))                       \
+                goto fail;                                                \
+                                                                          \
+            _mvar = _dvar;                                                \
+            break
+
+#define APPEND_ARRAY(_type, _var)                                         \
+                case MRP_MSG_FIELD_##_type:                               \
+                    if (!mrp_msg_append(msg, tag,                         \
+                                        MRP_MSG_FIELD_ARRAY |             \
+                                        MRP_MSG_FIELD_##_type,            \
+                                        n, _var))                         \
+                        goto fail;                                        \
+                    break
+
+    mrp_msg_t       *msg;
+    mrp_msg_value_t  v;
+    uint16_t         u16;
+    int16_t          s16;
+    uint32_t         u32;
+    uint16_t         nfield, tag, type, base, i;
+    uint32_t         n, j;
+    int              asize;
+    const char      *sender, *sig;
+
+    msg = NULL;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender))
+        goto fail;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    msg = mrp_msg_create_empty();
+
+    if (msg == NULL)
+        goto fail;
+
+    for (i = 0; i < nfield; i++) {
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+            goto fail;
+
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type))
+            goto fail;
+
+        switch (type) {
+            BASIC_SIMPLE(m, STRING, STRING , v.str);
+            BASIC_QUIRKY(m, BOOL  , BOOLEAN, v.bln, u32);
+            BASIC_QUIRKY(m, UINT8 , UINT16 , v.u8 , u16);
+            BASIC_QUIRKY(m, SINT8 ,  INT16 , v.s8 , s16);
+            BASIC_SIMPLE(m, UINT16, UINT16 , v.u16);
+            BASIC_SIMPLE(m, SINT16,  INT16 , v.s16);
+            BASIC_SIMPLE(m, UINT32, UINT32 , v.u32);
+            BASIC_SIMPLE(m, SINT32,  INT32 , v.s32);
+            BASIC_SIMPLE(m, UINT64, UINT64 , v.u64);
+            BASIC_SIMPLE(m, SINT64,  INT64 , v.s64);
+            BASIC_SIMPLE(m, DOUBLE, DOUBLE , v.dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            {
+                uint8_t blb[n];
+
+                if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL))
+                    goto fail;
+
+                for (j = 0; j < n; j++) {
+                    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE,
+                                                 blb + j))
+                        goto fail;
+                }
+
+                if (!mrp_dbus_msg_exit_container(m))
+                    goto fail;
+
+                asize = n;
+                if (!mrp_msg_append(msg, tag, type, asize, blb))
+                    goto fail;
+            }
+            break;
+
+        default:
+            if (!(type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = type & ~(MRP_MSG_FIELD_ARRAY);
+
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            sig = get_array_signature(base);
+            if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            {
+                char     *astr[n];
+                uint32_t  dbln[n];
+                bool      abln[n];
+                uint8_t   au8 [n];
+                int8_t    as8 [n];
+                uint16_t  au16[n];
+                int16_t   as16[n];
+                uint32_t  au32[n];
+                int32_t   as32[n];
+                uint64_t  au64[n];
+                int64_t   as64[n];
+                double    adbl[n];
+
+                for (j = 0; j < n; j++) {
+                    switch (base) {
+                        ARRAY_SIMPLE(m, STRING, STRING , astr[j]);
+                        ARRAY_QUIRKY(m, BOOL  , BOOLEAN, abln[j], dbln[j]);
+                        ARRAY_QUIRKY(m, UINT8 , UINT16 , au8[j] , au16[j]);
+                        ARRAY_QUIRKY(m, SINT8 ,  INT16 , as8[j] , as16[j]);
+                        ARRAY_SIMPLE(m, UINT16, UINT16 , au16[j]);
+                        ARRAY_SIMPLE(m, SINT16,  INT16 , as16[j]);
+                        ARRAY_SIMPLE(m, UINT32, UINT32 , au32[j]);
+                        ARRAY_SIMPLE(m, SINT32,  INT32 , as32[j]);
+                        ARRAY_SIMPLE(m, UINT64, UINT64 , au64[j]);
+                        ARRAY_SIMPLE(m, SINT64,  INT64 , as64[j]);
+                        ARRAY_SIMPLE(m, DOUBLE, DOUBLE , adbl[j]);
+                    default:
+                        goto fail;
+                    }
+                }
+
+                switch (base) {
+                    APPEND_ARRAY(STRING, astr);
+                    APPEND_ARRAY(BOOL  , abln);
+                    APPEND_ARRAY(UINT8 , au8 );
+                    APPEND_ARRAY(SINT8 , as8 );
+                    APPEND_ARRAY(UINT16, au16);
+                    APPEND_ARRAY(SINT16, as16);
+                    APPEND_ARRAY(UINT32, au32);
+                    APPEND_ARRAY(SINT32, as32);
+                    APPEND_ARRAY(UINT64, au64);
+                    APPEND_ARRAY(SINT64, as64);
+                    APPEND_ARRAY(DOUBLE, adbl);
+                default:
+                    goto fail;
+                }
+            }
+
+            if (!mrp_dbus_msg_exit_container(m))
+                goto fail;
+        }
+    }
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return msg;
+
+ fail:
+    mrp_msg_unref(msg);
+    errno = EBADMSG;
+
+    return NULL;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+#undef APPEND_ARRAY
+}
+
+
+static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination,
+                                   const char *path, const char *interface,
+                                   const char *member, const char *sender_id,
+                                   void *data, uint16_t tag)
+{
+#define BASIC_SIMPLE(_mtype, _dtype, _val)                                \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = &(_val);                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_mtype, _dtype, _mval, _dval)                        \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type  = MRP_DBUS_TYPE_##_dtype;                               \
+            _dval = _mval;                                                \
+            vptr  = &_dval;                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_mtype, _dtype, _val)                                \
+         case MRP_MSG_FIELD_##_mtype:                                     \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = &_val;                                                 \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define ARRAY_QUIRKY(_mtype, _dtype, _mvar, _dvar)                        \
+         case MRP_MSG_FIELD_##_mtype:                                     \
+            type  = MRP_DBUS_TYPE_##_dtype;                               \
+            _dvar = _mvar;                                                \
+            vptr = &_dvar;                                                \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+    mrp_dbus_msg_t    *m;
+    mrp_data_descr_t  *descr;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           type, base;
+    mrp_msg_value_t   *v;
+    void              *vptr;
+    uint32_t           n, j;
+    int                i, blblen;
+    const char        *sig;
+    uint16_t           u16;
+    int16_t            s16;
+    uint32_t           bln, asize;
+
+    m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member);
+
+    if (m == NULL)
+        return NULL;
+
+    descr = mrp_msg_find_type(tag);
+
+    if (descr == NULL)
+        goto fail;
+
+    fields = descr->fields;
+    nfield = descr->nfield;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH,
+                                   (void *)sender_id))
+        goto fail;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+        goto fail;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    for (i = 0, f = fields; i < nfield; i++, f++) {
+        if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) ||
+            !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type))
+            goto fail;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (f->type) {
+            BASIC_SIMPLE(STRING, STRING , v->str);
+            BASIC_QUIRKY(BOOL  , BOOLEAN, v->bln, bln);
+            BASIC_QUIRKY(UINT8 , UINT16 , v->u8 , u16);
+            BASIC_QUIRKY(SINT8 ,  INT16 , v->s8 , s16);
+            BASIC_SIMPLE(UINT16, UINT16 , v->u16);
+            BASIC_SIMPLE(SINT16,  INT16 , v->s16);
+            BASIC_SIMPLE(UINT32, UINT32 , v->u32);
+            BASIC_SIMPLE(SINT32,  INT32 , v->s32);
+            BASIC_SIMPLE(UINT64, UINT64 , v->u64);
+            BASIC_SIMPLE(SINT64,  INT64 , v->s64);
+            BASIC_SIMPLE(DOUBLE, DOUBLE , v->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            sig    = get_array_signature(f->type);
+            blblen = mrp_data_get_blob_size(data, descr, i);
+            asize  = blblen;
+
+            if (blblen == -1)
+                goto fail;
+
+            if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize))
+                goto fail;
+
+            if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            for (i = 0; i < blblen; i++)
+                if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE,
+                                               f->blb + i))
+                    goto fail;
+
+            if (!mrp_dbus_msg_close_container(m))
+                goto fail;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = f->type & ~(MRP_MSG_FIELD_ARRAY);
+            n    = mrp_data_get_array_size(data, descr, i);
+            sig  = get_array_signature(base);
+
+            if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            for (j = 0; j < n; j++) {
+                switch (base) {
+                    ARRAY_SIMPLE(STRING, STRING , v->astr[j]);
+                    ARRAY_QUIRKY(BOOL  , BOOLEAN, v->abln[j], bln);
+                    ARRAY_QUIRKY(UINT8 , UINT16 , v->au8[j] , u16);
+                    ARRAY_QUIRKY(SINT8 ,  INT16 , v->as8[j] , s16);
+                    ARRAY_SIMPLE(UINT16, UINT16 , v->au16[j]);
+                    ARRAY_SIMPLE(SINT16,  INT16 , v->as16[j]);
+                    ARRAY_SIMPLE(UINT32, UINT32 , v->au32[j]);
+                    ARRAY_SIMPLE(SINT32,  INT32 , v->as32[j]);
+                    ARRAY_SIMPLE(UINT64, UINT64 , v->au64[j]);
+                    ARRAY_SIMPLE(DOUBLE, DOUBLE , v->adbl[j]);
+
+                case MRP_MSG_FIELD_BLOB:
+                    goto fail;
+
+                default:
+                    goto fail;
+                }
+            }
+
+            if (!mrp_dbus_msg_close_container(m))
+                goto fail;
+        }
+    }
+
+    return m;
+
+ fail:
+    if (m != NULL)
+        mrp_dbus_msg_unref(m);
+
+    errno = ECOMM;
+
+    return NULL;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+}
+
+
+static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield,
+                                      uint16_t tag)
+{
+    mrp_data_member_t *f;
+    int                i;
+
+    for (i = 0, f = fields; i < nfield; i++, f++)
+        if (f->tag == tag)
+            return f;
+
+    return NULL;
+}
+
+
+static void *data_decode(mrp_dbus_msg_t *m, uint16_t *tagp,
+                         const char **sender_id)
+{
+#define HANDLE_SIMPLE(_i, _mtype, _dtype, _var)                           \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype,       \
+                                         &(_var)))                        \
+                goto fail;                                                \
+            break
+
+#define HANDLE_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                   \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype,       \
+                                         &(_dvar)))                       \
+                goto fail;                                                \
+                                                                          \
+            _mvar = _dvar;                                                \
+            break
+
+    void              *data;
+    mrp_data_descr_t  *descr;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           tag, type, base;
+    mrp_msg_value_t   *v;
+    uint32_t           n, j, size;
+    int                i;
+    const char        *sender, *sig;
+    uint32_t           u32;
+    uint16_t           u16;
+    int16_t            s16;
+
+    tag  = 0;
+    data = NULL;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender))
+        goto fail;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+        goto fail;
+
+    descr = mrp_msg_find_type(tag);
+
+    if (descr == NULL)
+        goto fail;
+
+    *tagp = tag;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    if (nfield != descr->nfield)
+        goto fail;
+
+    fields = descr->fields;
+    data   = mrp_allocz(descr->size);
+
+    if (MRP_UNLIKELY(data == NULL))
+        goto fail;
+
+    for (i = 0; i < nfield; i++) {
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+            goto fail;
+
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type))
+            goto fail;
+
+        f = member_type(fields, nfield, tag);
+
+        if (MRP_UNLIKELY(f == NULL))
+            goto fail;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (type) {
+            HANDLE_SIMPLE(&im, STRING, STRING , v->str);
+            HANDLE_QUIRKY(&im, BOOL  , BOOLEAN, v->bln, u32);
+            HANDLE_QUIRKY(&im, UINT8 , UINT16 , v->u8 , u16);
+            HANDLE_QUIRKY(&im, SINT8 ,  INT16 , v->s8 , s16);
+            HANDLE_SIMPLE(&im, UINT16, UINT16 , v->u16);
+            HANDLE_SIMPLE(&im, SINT16,  INT16 , v->s16);
+            HANDLE_SIMPLE(&im, UINT32, UINT32 , v->u32);
+            HANDLE_SIMPLE(&im, SINT32,  INT32 , v->s32);
+            HANDLE_SIMPLE(&im, UINT64, UINT64 , v->u64);
+            HANDLE_SIMPLE(&im, SINT64,  INT64 , v->s64);
+            HANDLE_SIMPLE(&im, DOUBLE, DOUBLE , v->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &size))
+                goto fail;
+
+            sig = MRP_DBUS_TYPE_BYTE_AS_STRING;
+
+            if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            {
+                uint8_t blb[size];
+
+                for (j = 0; j < size; j++)
+                    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE,
+                                                 blb + j))
+                        goto fail;
+
+                v->blb = mrp_alloc(size);
+
+                if (v->blb == NULL && size != 0)
+                    goto fail;
+
+                memcpy(v->blb, blb, size);
+            }
+
+            if (!mrp_dbus_msg_exit_container(m))
+                goto fail;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = type & ~(MRP_MSG_FIELD_ARRAY);
+
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL))
+                goto fail;
+
+            size = n;
+
+            switch (base) {
+            case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break;
+            case MRP_MSG_FIELD_BOOL:   size *= sizeof(*v->abln); break;
+            case MRP_MSG_FIELD_UINT8:  size *= sizeof(*v->au8);  break;
+            case MRP_MSG_FIELD_SINT8:  size *= sizeof(*v->as8);  break;
+            case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break;
+            case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break;
+            case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break;
+            case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break;
+            case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break;
+            case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break;
+            case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break;
+            default:
+                goto fail;
+            }
+
+            v->aany = mrp_allocz(size);
+            if (v->aany == NULL)
+                goto fail;
+
+            for (j = 0; j < n; j++) {
+                uint32_t  dbln[n];
+                uint16_t  au16[n];
+                int16_t   as16[n];
+
+                switch (base) {
+                    HANDLE_SIMPLE(&ia, STRING, STRING , v->astr[j]);
+                    HANDLE_QUIRKY(&ia, BOOL  , BOOLEAN, v->abln[j], dbln[j]);
+                    HANDLE_QUIRKY(&ia, UINT8 , UINT16 , v->au8[j] , au16[j]);
+                    HANDLE_QUIRKY(&ia, SINT8 ,  INT16 , v->as8[j] , as16[j]);
+                    HANDLE_SIMPLE(&ia, UINT16, UINT16 , v->au16[j]);
+                    HANDLE_SIMPLE(&ia, SINT16,  INT16 , v->as16[j]);
+                    HANDLE_SIMPLE(&ia, UINT32, UINT32 , v->au32[j]);
+                    HANDLE_SIMPLE(&ia, SINT32,  INT32 , v->as32[j]);
+                    HANDLE_SIMPLE(&ia, UINT64, UINT64 , v->au64[j]);
+                    HANDLE_SIMPLE(&ia, SINT64,  INT64 , v->as64[j]);
+                    HANDLE_SIMPLE(&ia, DOUBLE, DOUBLE , v->adbl[j]);
+                }
+
+                if (base == MRP_MSG_FIELD_STRING) {
+                    v->astr[j] = mrp_strdup(v->astr[j]);
+                    if (v->astr[j] == NULL)
+                        goto fail;
+                }
+            }
+
+            if (!mrp_dbus_msg_exit_container(m))
+                goto fail;
+        }
+
+        if (f->type == MRP_MSG_FIELD_STRING) {
+            v->str = mrp_strdup(v->str);
+            if (v->str == NULL)
+                goto fail;
+        }
+    }
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return data;
+
+ fail:
+    mrp_data_free(data, tag);
+    errno = EBADMSG;
+
+    return NULL;
+}
+
+
+static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  void *data, size_t size)
+{
+    mrp_dbus_msg_t *m;
+    const char     *sig;
+    uint32_t        i, n;
+
+    m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member);
+
+    if (m == NULL)
+        return NULL;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH,
+                                   (void *)sender_id))
+        goto fail;
+
+    sig = MRP_DBUS_TYPE_BYTE_AS_STRING;
+    n   = size;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+        goto fail;
+
+    if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+        goto fail;
+
+    for (i = 0; i < n; i++)
+        if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, data + i))
+            goto fail;
+
+    if (!mrp_dbus_msg_close_container(m))
+        goto fail;
+
+    return m;
+
+ fail:
+    mrp_dbus_msg_unref(m);
+
+    errno = ECOMM;
+
+    return NULL;
+}
+
+
+static void *raw_decode(mrp_dbus_msg_t *m, size_t *sizep,
+                        const char **sender_id)
+{
+    const char *sender, *sig;
+    void       *data;
+    uint32_t    n, i;
+
+    data = NULL;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender))
+        goto fail;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+        goto fail;
+
+    sig = MRP_DBUS_TYPE_BYTE_AS_STRING;
+
+    if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+        goto fail;
+
+    {
+        uint8_t databuf[n];
+
+        for (i = 0; i < n; i++)
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, databuf + i))
+                goto fail;
+
+        data = mrp_alloc(n);
+
+        if (data == NULL && n != 0)
+            goto fail;
+
+        memcpy(data, databuf, n);
+    }
+
+    if (!mrp_dbus_msg_exit_container(m))
+        goto fail;
+
+    if (sizep != NULL)
+        *sizep = (size_t)n;
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return data;
+
+ fail:
+    errno = EBADMSG;
+
+    return NULL;
+}
+
+
+MRP_REGISTER_TRANSPORT(dbus, DBUS, dbus_t, dbus_resolve,
+                       dbus_open, dbus_createfrom, dbus_close, NULL,
+                       dbus_bind, NULL, NULL,
+                       dbus_connect, dbus_disconnect,
+                       dbus_sendmsg, dbus_sendmsgto,
+                       dbus_sendraw, dbus_sendrawto,
+                       dbus_senddata, dbus_senddatato,
+                       NULL, NULL,
+                       NULL, NULL,
+                       NULL, NULL);
diff --git a/src/common/dbus-libdbus.c b/src/common/dbus-libdbus.c
new file mode 100644 (file)
index 0000000..6dcf9fa
--- /dev/null
@@ -0,0 +1,2078 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/refcnt.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/dbus-libdbus.h>
+
+
+#define DBUS_ADMIN_SERVICE   "org.freedesktop.DBus"
+#define DBUS_ADMIN_INTERFACE "org.freedesktop.DBus"
+#define DBUS_ADMIN_PATH      "/org/freedesktop/DBus"
+#define DBUS_NAME_CHANGED    "NameOwnerChanged"
+
+
+struct mrp_dbus_s {
+    char            *address;            /* bus address */
+    DBusConnection  *conn;               /* actual D-BUS connection */
+    mrp_mainloop_t  *ml;                 /* murphy mainloop */
+    mrp_htbl_t      *methods;            /* method handler table */
+    mrp_htbl_t      *signals;            /* signal handler table */
+    mrp_list_hook_t  name_trackers;      /* peer (name) watchers */
+    mrp_list_hook_t  calls;              /* pending calls */
+    uint32_t         call_id;            /* next call id */
+    const char      *unique_name;        /* our unique D-BUS address */
+    int              priv;               /* whether a private connection */
+    int              signal_filter;      /* if signal dispatching is set up */
+    int              register_fallback;  /* if the fallback object is set up */
+    mrp_refcnt_t     refcnt;             /* reference count */
+};
+
+
+struct mrp_dbus_msg_s {
+    DBusMessage     *msg;                /* actual D-BUS message */
+    mrp_refcnt_t     refcnt;             /* reference count */
+    mrp_list_hook_t  iterators;          /* iterator stack */
+    mrp_list_hook_t  arrays;             /* implicitly freed related arrays */
+};
+
+
+typedef struct {
+    DBusMessageIter  it;                 /* actual iterator */
+    char            *peeked;             /* peeked contents, or NULL */
+    mrp_list_hook_t  hook;               /* hook to iterator stack */
+} msg_iter_t;
+
+
+typedef struct {
+    mrp_list_hook_t   hook;
+    char            **items;
+    size_t            nitem;
+} msg_array_t;
+
+/*
+ * Notes:
+ *
+ * At the moment we administer DBUS method and signal handlers
+ * in a very primitive way (subject to be changed later). For
+ * every bus instance we maintain two hash tables, one for methods
+ * and another for signals. Each method and signal handler is
+ * hashed in only by it's method/signal name to a linked list of
+ * method or signal handlers.
+ *
+ * When dispatching a method, we look up the chain with a matching
+ * method name, or the chain for "" in case a matching chain is
+ * not found, and invoke the handler which best matches the
+ * received message (by looking at the path, interface and name).
+ * Only one such handler is invoked at most.
+ *
+ * For signals we look up both the chain with a matching name and
+ * the chain for "" and invoke all signal handlers that match the
+ * received message (regardless of their return value).
+ */
+
+
+typedef struct {
+    char            *member;            /* signal/method name */
+    mrp_list_hook_t  handlers;          /* handlers with matching member */
+} handler_list_t;
+
+typedef struct {
+    mrp_list_hook_t     hook;
+    char               *sender;
+    char               *path;
+    char               *interface;
+    char               *member;
+    mrp_dbus_handler_t  handler;
+    void               *user_data;
+} handler_t;
+
+#define method_t handler_t
+#define signal_t handler_t
+
+
+typedef struct {
+    mrp_list_hook_t     hook;           /* hook to name tracker list */
+    char               *name;           /* name to track */
+    mrp_dbus_name_cb_t  cb;             /* status change callback */
+    void               *user_data;      /* opaque callback user data */
+    int32_t             qid;            /* initial query ID */
+} name_tracker_t;
+
+
+typedef struct {
+    mrp_dbus_t          *dbus;           /* DBUS connection */
+    int32_t              id;             /* call id */
+    mrp_dbus_reply_cb_t  cb;             /* completion notification callback */
+    void                *user_data;      /* opaque callback data */
+    DBusPendingCall     *pend;           /* pending DBUS call */
+    mrp_list_hook_t      hook;           /* hook to list of pending calls */
+} call_t;
+
+
+typedef struct {
+    mrp_mainloop_t *ml;                  /* mainloop for bus connection */
+    const char     *address;             /* address of bus */
+} bus_spec_t;
+
+static mrp_htbl_t *buses;
+
+
+
+static DBusHandlerResult dispatch_signal(DBusConnection *c,
+                                         DBusMessage *msg, void *data);
+static DBusHandlerResult dispatch_method(DBusConnection *c,
+                                         DBusMessage *msg, void *data);
+static void purge_name_trackers(mrp_dbus_t *dbus);
+static void purge_calls(mrp_dbus_t *dbus);
+static void handler_list_free_cb(void *key, void *entry);
+static void handler_free(handler_t *h);
+static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m,
+                                void *data);
+static void call_free(call_t *call);
+static void free_msg_array(msg_array_t *a);
+
+
+
+static int purge_filters(void *key, void *entry, void *user_data)
+{
+    mrp_dbus_t      *dbus = (mrp_dbus_t *)user_data;
+    handler_list_t  *l    = (handler_list_t *)entry;
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    MRP_UNUSED(key);
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+        mrp_dbus_remove_filter(dbus,
+                               h->sender, h->path, h->interface,
+                               h->member, NULL);
+    }
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+void dbus_disconnect(mrp_dbus_t *dbus)
+{
+    if (dbus) {
+        mrp_htbl_remove(buses, dbus->conn, FALSE);
+
+        if (dbus->signals) {
+            mrp_htbl_foreach(dbus->signals, purge_filters, dbus);
+            mrp_htbl_destroy(dbus->signals, TRUE);
+        }
+        if (dbus->methods)
+            mrp_htbl_destroy(dbus->methods, TRUE);
+
+        if (dbus->conn != NULL) {
+            if (dbus->signal_filter)
+                dbus_connection_remove_filter(dbus->conn, dispatch_signal,
+                        dbus);
+            if (dbus->register_fallback)
+                dbus_connection_unregister_object_path(dbus->conn, "/");
+            if (dbus->priv)
+                dbus_connection_close(dbus->conn);
+            dbus_connection_unref(dbus->conn);
+        }
+
+        purge_name_trackers(dbus);
+        purge_calls(dbus);
+
+        mrp_free(dbus->address);
+        dbus->conn = NULL;
+        dbus->ml   = NULL;
+
+        mrp_free(dbus);
+    }
+}
+
+
+static int bus_cmp(const void *key1, const void *key2)
+{
+    return key2 - key1;
+}
+
+
+static uint32_t bus_hash(const void *key)
+{
+    uint32_t h;
+
+    h   = (ptrdiff_t)key;
+    h >>= 2 * sizeof(key);
+
+    return h;
+}
+
+
+static int find_bus_by_spec(void *key, void *object, void *user_data)
+{
+    mrp_dbus_t *dbus = (mrp_dbus_t *)object;
+    bus_spec_t *spec = (bus_spec_t *)user_data;
+
+    MRP_UNUSED(key);
+
+    if (dbus->ml == spec->ml && !strcmp(dbus->address, spec->address))
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static mrp_dbus_t *dbus_get(mrp_mainloop_t *ml, const char *address)
+{
+    mrp_htbl_config_t hcfg;
+    bus_spec_t        spec;
+
+    if (buses == NULL) {
+        mrp_clear(&hcfg);
+
+        hcfg.comp = bus_cmp;
+        hcfg.hash = bus_hash;
+        hcfg.free = NULL;
+
+        buses = mrp_htbl_create(&hcfg);
+
+        return NULL;
+    }
+    else {
+        spec.ml      = ml;
+        spec.address = address;
+
+        return mrp_htbl_find(buses, find_bus_by_spec, &spec);
+    }
+}
+
+
+mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address,
+                             mrp_dbus_err_t *errp)
+{
+    static struct DBusObjectPathVTable vtable = {
+        .message_function = dispatch_method
+    };
+
+    mrp_htbl_config_t  hcfg;
+    mrp_dbus_t        *dbus;
+
+    if ((dbus = dbus_get(ml, address)) != NULL)
+        return mrp_dbus_ref(dbus);
+
+    if ((dbus = mrp_allocz(sizeof(*dbus))) == NULL)
+        return NULL;
+
+    mrp_list_init(&dbus->calls);
+    mrp_list_init(&dbus->name_trackers);
+    mrp_refcnt_init(&dbus->refcnt);
+
+    dbus->ml = ml;
+
+
+    mrp_dbus_error_init(errp);
+
+    /*
+     * connect to the bus
+     */
+
+    if (!strcmp(address, "system"))
+        dbus->conn = dbus_bus_get(DBUS_BUS_SYSTEM, errp);
+    else if (!strcmp(address, "session"))
+        dbus->conn = dbus_bus_get(DBUS_BUS_SESSION, errp);
+    else {
+        dbus->conn = dbus_connection_open_private(address, errp);
+        dbus->priv = TRUE;
+
+        if (dbus->conn == NULL || !dbus_bus_register(dbus->conn, errp))
+            goto fail;
+    }
+
+    if (dbus->conn == NULL)
+        goto fail;
+
+    dbus->address     = mrp_strdup(address);
+    dbus->unique_name = dbus_bus_get_unique_name(dbus->conn);
+
+    /*
+     * set up with mainloop
+     */
+
+    if (!mrp_dbus_setup_connection(ml, dbus->conn))
+        goto fail;
+
+    /*
+     * set up our message dispatchers and take our name on the bus
+     */
+
+    if (!dbus_connection_add_filter(dbus->conn, dispatch_signal, dbus, NULL)) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to set up signal dispatching.");
+        goto fail;
+    }
+    dbus->signal_filter = TRUE;
+
+    if (!dbus_connection_register_fallback(dbus->conn, "/", &vtable, dbus)) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to set up method dispatching.");
+        goto fail;
+    }
+    dbus->register_fallback = TRUE;
+
+    mrp_clear(&hcfg);
+    hcfg.comp = mrp_string_comp;
+    hcfg.hash = mrp_string_hash;
+    hcfg.free = handler_list_free_cb;
+
+    if ((dbus->methods = mrp_htbl_create(&hcfg)) == NULL) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to create DBUS method table.");
+        goto fail;
+    }
+
+    if ((dbus->signals = mrp_htbl_create(&hcfg)) == NULL) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to create DBUS signal table.");
+        goto fail;
+    }
+
+
+    /*
+     * install handler for NameOwnerChanged for tracking clients/peers
+     */
+
+    if (!mrp_dbus_add_signal_handler(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                                     DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                                     name_owner_change_cb, NULL)) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to install NameOwnerChanged handler.");
+        goto fail;
+    }
+
+    /* install a 'safe' filter to avoid receiving all name change signals */
+    mrp_dbus_install_filter(dbus,
+                            DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                            DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                            DBUS_ADMIN_SERVICE, NULL);
+
+    mrp_list_init(&dbus->name_trackers);
+    dbus->call_id = 1;
+
+    if (mrp_htbl_insert(buses, dbus->conn, dbus))
+        return dbus;
+
+ fail:
+    dbus_disconnect(dbus);
+    return NULL;
+}
+
+
+mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus)
+{
+    return mrp_ref_obj(dbus, refcnt);
+}
+
+
+int mrp_dbus_unref(mrp_dbus_t *dbus)
+{
+    if (mrp_unref_obj(dbus, refcnt)) {
+        dbus_disconnect(dbus);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error)
+{
+    int flags, status;
+
+    mrp_dbus_error_init(error);
+
+    flags  = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE;
+    status = dbus_bus_request_name(dbus->conn, name, flags, error);
+
+    if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
+        return TRUE;
+    else {
+        if (status == DBUS_REQUEST_NAME_REPLY_EXISTS) {
+            if (error)
+                dbus_error_free(error);
+            dbus_set_error(error, DBUS_ERROR_FAILED, "name already taken");
+        }
+        return FALSE;
+    }
+}
+
+
+int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error)
+{
+    mrp_dbus_error_init(error);
+
+    if (dbus_bus_release_name(dbus->conn, name, error) != -1)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus)
+{
+    return dbus->unique_name;
+}
+
+
+static void name_owner_query_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data)
+{
+    name_tracker_t *t   = (name_tracker_t *)data;
+    DBusMessage    *msg = m->msg;
+    const char     *owner;
+    int             state;
+
+    if (t->cb != NULL) {                /* tracker still active */
+        t->qid = 0;
+        state  = dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN;
+
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &owner,
+                                   DBUS_TYPE_INVALID))
+            owner = "<unknown>";
+
+        t->cb(dbus, t->name, state, owner, t->user_data);
+    }
+    else                                /* already requested to delete */
+        mrp_free(t);
+}
+
+
+static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data)
+{
+    const char      *name, *prev, *next;
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+    DBusMessage     *msg;
+
+    MRP_UNUSED(data);
+
+    msg = m->msg;
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
+        return FALSE;
+
+    if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &name,
+                               DBUS_TYPE_STRING, &prev,
+                               DBUS_TYPE_STRING, &next,
+                               DBUS_TYPE_INVALID))
+        return FALSE;
+
+#if 0
+    /*
+     * Notes: XXX TODO
+     *    In principle t->cb could call mrp_dbus_forget for some other D-BUS
+     *    address than name. If that happened to be n (== p->hook.next) this
+     *    would result in a crash or memory corruption in the next iteration
+     *    of this loop (when handling n). We can easily get around this
+     *    problem by
+     *
+     *     1. administering in mrp_dbus_t that we're handing a NameOwnerChange
+     *     2. checking for this in mrp_dbus_forget_name and if it is the case
+     *        only marking the affected entry for deletion
+     *     3. removing entries marked for deletion in this loop (or just
+     *        ignoring them and making another pass in the end removing any
+     *        such entry).
+     */
+#endif
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        if (!strcmp(name, t->name))
+            t->cb(dbus, name, next && *next, next, t->user_data);
+    }
+
+    return TRUE;
+}
+
+
+int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data)
+{
+    name_tracker_t *t;
+
+    if ((t = mrp_allocz(sizeof(*t))) != NULL) {
+        if ((t->name = mrp_strdup(name)) != NULL) {
+            t->cb        = cb;
+            t->user_data = user_data;
+
+            if (mrp_dbus_install_filter(dbus,
+                                        DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                                        DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                                        name, NULL)) {
+                mrp_list_append(&dbus->name_trackers, &t->hook);
+
+                t->qid = mrp_dbus_call(dbus,
+                                       DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                                       DBUS_ADMIN_SERVICE, "GetNameOwner", 5000,
+                                       name_owner_query_cb, t,
+                                       DBUS_TYPE_STRING, t->name,
+                                       DBUS_TYPE_INVALID);
+                return TRUE;
+            }
+            else {
+                mrp_free(t->name);
+                mrp_free(t);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    mrp_dbus_remove_filter(dbus,
+                           DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                           DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                           name, NULL);
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        if (t->cb == cb && t->user_data == user_data && !strcmp(t->name,name)) {
+            mrp_list_delete(&t->hook);
+            mrp_free(t->name);
+
+            if (!t->qid)
+                mrp_free(t);
+            else {
+                t->cb        = NULL;
+                t->user_data = NULL;
+                t->name      = NULL;
+            }
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void purge_name_trackers(mrp_dbus_t *dbus)
+{
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        mrp_list_delete(p);
+        mrp_dbus_remove_filter(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                               DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                               t->name, NULL);
+        mrp_free(t->name);
+        mrp_free(t);
+    }
+}
+
+
+static handler_t *handler_alloc(const char *sender, const char *path,
+                                const char *interface, const char *member,
+                                mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_t *h;
+
+    if ((h = mrp_allocz(sizeof(*h))) != NULL) {
+        h->sender    = mrp_strdup(sender);
+        h->path      = mrp_strdup(path);
+        h->interface = mrp_strdup(interface);
+        h->member    = mrp_strdup(member);
+
+        if ((path && !h->path) || !h->interface || !h->member) {
+            handler_free(h);
+            return NULL;
+        }
+
+        h->handler   = handler;
+        h->user_data = user_data;
+
+        return h;
+    }
+
+    return NULL;
+}
+
+
+static void handler_free(handler_t *h)
+{
+    if (h != NULL) {
+        mrp_free(h->sender);
+        mrp_free(h->path);
+        mrp_free(h->interface);
+        mrp_free(h->member);
+
+        mrp_free(h);
+    }
+}
+
+
+static handler_list_t *handler_list_alloc(const char *member)
+{
+    handler_list_t *l;
+
+    if ((l = mrp_allocz(sizeof(*l))) != NULL) {
+        if ((l->member = mrp_strdup(member)) != NULL)
+            mrp_list_init(&l->handlers);
+        else {
+            mrp_free(l);
+            l = NULL;
+        }
+    }
+
+    return l;
+}
+
+
+static inline void handler_list_free(handler_list_t *l)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+        mrp_list_delete(p);
+        handler_free(h);
+    }
+
+    mrp_free(l->member);
+    mrp_free(l);
+}
+
+
+static void handler_list_free_cb(void *key, void *entry)
+{
+    MRP_UNUSED(key);
+
+    handler_list_free((handler_list_t *)entry);
+}
+
+
+static inline int handler_specificity(handler_t *h)
+{
+    int score = 0;
+
+    if (h->path && *h->path)
+        score |= 0x4;
+    if (h->interface && *h->interface)
+        score |= 0x2;
+    if (h->member && *h->member)
+        score |= 0x1;
+
+    return score;
+}
+
+
+static void handler_list_insert(handler_list_t *l, handler_t *handler)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+    int              score;
+
+    score = handler_specificity(handler);
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (score >= handler_specificity(h)) {
+            mrp_list_append(h->hook.prev, &handler->hook);  /* add before h */
+            return;
+        }
+    }
+
+    mrp_list_append(&l->handlers, &handler->hook);
+}
+
+
+static handler_t *handler_list_lookup(handler_list_t *l, const char *path,
+                                      const char *interface, const char *member,
+                                      mrp_dbus_handler_t handler,
+                                      void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (h->handler == handler && user_data == h->user_data &&
+            path      && !strcmp(path, h->path) &&
+            interface && !strcmp(interface, h->interface) &&
+            member    && !strcmp(member, h->member))
+            return h;
+    }
+
+    return NULL;
+}
+
+
+static handler_t *handler_list_find(handler_list_t *l, const char *path,
+                                    const char *interface, const char *member)
+{
+#define MATCHES(h, field) (!*field || !*h->field || !strcmp(field, h->field))
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (MATCHES(h, path) && MATCHES(h, interface) && MATCHES(h, member))
+            return h;
+    }
+
+    return NULL;
+#undef MATCHES
+}
+
+
+int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_list_t *methods;
+    handler_t      *m;
+
+    if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) {
+        if ((methods = handler_list_alloc(member)) == NULL)
+            return FALSE;
+
+        mrp_htbl_insert(dbus->methods, methods->member, methods);
+    }
+
+    m = handler_alloc(NULL, path, interface, member, handler, user_data);
+    if (m != NULL) {
+        handler_list_insert(methods, m);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_list_t *methods;
+    handler_t      *m;
+
+    if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL)
+        return FALSE;
+
+    m = handler_list_lookup(methods, path, interface, member,
+                            handler, user_data);
+    if (m != NULL) {
+        mrp_list_delete(&m->hook);
+        handler_free(m);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data)
+{
+    handler_list_t *signals;
+    handler_t      *s;
+
+    if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) {
+        if ((signals = handler_list_alloc(member)) == NULL)
+            return FALSE;
+
+        if (!mrp_htbl_insert(dbus->signals, signals->member, signals)) {
+            handler_list_free(signals);
+            return FALSE;
+        }
+    }
+
+    s = handler_alloc(sender, path, interface, member, handler, user_data);
+    if (s != NULL) {
+        handler_list_insert(signals, s);
+        return TRUE;
+    }
+    else {
+        handler_free(s);
+        if (mrp_list_empty(&signals->handlers))
+            mrp_htbl_remove(dbus->signals, signals->member, TRUE);
+        return FALSE;
+    }
+}
+
+
+
+int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data)
+{
+    handler_list_t *signals;
+    handler_t      *s;
+
+    MRP_UNUSED(sender);
+
+    if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL)
+        return FALSE;
+
+    s = handler_list_lookup(signals, path, interface, member,
+                            handler, user_data);
+    if (s != NULL) {
+        mrp_list_delete(&s->hook);
+        handler_free(s);
+
+        if (mrp_list_empty(&signals->handlers))
+            mrp_htbl_remove(dbus->signals, (void *)member, TRUE);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+
+int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus,
+                              mrp_dbus_handler_t handler, void *user_data,
+                              const char *sender, const char *path,
+                              const char *interface, const char *member, ...)
+{
+    va_list ap;
+    int     success;
+
+
+    if (mrp_dbus_add_signal_handler(dbus, sender, path, interface, member,
+                                    handler, user_data)) {
+        va_start(ap, member);
+        success = mrp_dbus_install_filterv(dbus,
+                                           sender, path, interface, member, ap);
+        va_end(ap);
+
+        if (success)
+            return TRUE;
+        else
+            mrp_dbus_del_signal_handler(dbus, sender, path, interface, member,
+                                        handler, user_data);
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus,
+                                mrp_dbus_handler_t handler, void *user_data,
+                                const char *sender, const char *path,
+                                const char *interface, const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    status = mrp_dbus_del_signal_handler(dbus, sender, path, interface, member,
+                                         handler, user_data);
+    va_start(ap, member);
+    status &= mrp_dbus_remove_filterv(dbus,
+                                      sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+int mrp_dbus_install_filterv(mrp_dbus_t *dbus, const char *sender,
+                             const char *path, const char *interface,
+                             const char *member, va_list args)
+{
+#define ADD_TAG(tag, value) do {                                          \
+        if (value != NULL) {                                              \
+            l = snprintf(p, n, "%s%s='%s'", p == filter ? "" : ",",       \
+                         tag, value);                                     \
+            if (l >= n)                                                   \
+                return FALSE;                                             \
+            n -= l;                                                       \
+            p += l;                                                       \
+        }                                                                 \
+    } while (0)
+
+    va_list   ap;
+    DBusError error;
+    char      filter[1024], *p, argn[16], *val;
+    int       n, l, i;
+
+    p = filter;
+    n = sizeof(filter);
+
+    ADD_TAG("type"     , "signal");
+    ADD_TAG("sender"   ,  sender);
+    ADD_TAG("path"     ,  path);
+    ADD_TAG("interface",  interface);
+    ADD_TAG("member"   ,  member);
+
+    va_copy(ap, args);
+    i = 0;
+    while ((val = va_arg(ap, char *)) != NULL) {
+        snprintf(argn, sizeof(argn), "arg%d", i);
+        ADD_TAG(argn, val);
+        i++;
+    }
+    va_end(ap);
+
+    dbus_error_init(&error);
+    dbus_bus_add_match(dbus->conn, filter, &error);
+
+    if (dbus_error_is_set(&error)) {
+        mrp_log_error("Failed to install filter '%s' (error: %s).", filter,
+                      mrp_dbus_errmsg(&error));
+        dbus_error_free(&error);
+
+        return FALSE;
+    }
+    else
+        return TRUE;
+
+}
+
+
+int mrp_dbus_install_filter(mrp_dbus_t *dbus, const char *sender,
+                            const char *path, const char *interface,
+                            const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    va_start(ap, member);
+    status = mrp_dbus_install_filterv(dbus,
+                                      sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, const char *sender,
+                            const char *path, const char *interface,
+                            const char *member, va_list args)
+{
+    va_list ap;
+    char    filter[1024], *p, argn[16], *val;
+    int     n, l, i;
+
+    p = filter;
+    n = sizeof(filter);
+
+    ADD_TAG("type"     , "signal");
+    ADD_TAG("sender"   ,  sender);
+    ADD_TAG("path"     ,  path);
+    ADD_TAG("interface",  interface);
+    ADD_TAG("member"   ,  member);
+
+    va_copy(ap, args);
+    i = 0;
+    while ((val = va_arg(ap, char *)) != NULL) {
+        snprintf(argn, sizeof(argn), "arg%d", i);
+        ADD_TAG(argn, val);
+        i++;
+    }
+    va_end(ap);
+
+    dbus_bus_remove_match(dbus->conn, filter, NULL);
+    return TRUE;
+#undef ADD_TAG
+}
+
+
+int mrp_dbus_remove_filter(mrp_dbus_t *dbus, const char *sender,
+                           const char *path, const char *interface,
+                           const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    va_start(ap, member);
+    status = mrp_dbus_remove_filterv(dbus, sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+static inline mrp_dbus_msg_t *create_message(DBusMessage *msg)
+{
+    mrp_dbus_msg_t *m;
+
+    if (msg != NULL) {
+        if ((m = mrp_allocz(sizeof(*m))) != NULL) {
+            mrp_refcnt_init(&m->refcnt);
+            mrp_list_init(&m->iterators);
+            mrp_list_init(&m->arrays);
+            m->msg = dbus_message_ref(msg);
+        }
+    }
+    else
+        m = NULL;
+
+    return m;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m)
+{
+    return mrp_ref_obj(m, refcnt);
+}
+
+
+static void rewind_message(mrp_dbus_msg_t *m)
+{
+    mrp_list_hook_t *p, *n;
+    msg_iter_t      *it;
+    msg_array_t     *a;
+
+    mrp_list_foreach(&m->iterators, p, n) {
+        it = mrp_list_entry(p, typeof(*it), hook);
+
+        mrp_list_delete(&it->hook);
+        mrp_free(it->peeked);
+        mrp_free(it);
+    }
+
+    mrp_list_foreach(&m->arrays, p, n) {
+        a = mrp_list_entry(p, typeof(*a), hook);
+
+        free_msg_array(a);
+    }
+}
+
+
+static void free_message(mrp_dbus_msg_t *m)
+{
+    mrp_list_hook_t *p, *n;
+    msg_iter_t      *it;
+    msg_array_t     *a;
+
+    mrp_list_foreach(&m->iterators, p, n) {
+        it = mrp_list_entry(p, typeof(*it), hook);
+
+        mrp_list_delete(&it->hook);
+        mrp_free(it->peeked);
+        mrp_free(it);
+    }
+
+    mrp_list_foreach(&m->arrays, p, n) {
+        a = mrp_list_entry(p, typeof(*a), hook);
+
+        free_msg_array(a);
+    }
+
+    mrp_free(m);
+}
+
+
+int mrp_dbus_msg_unref(mrp_dbus_msg_t *m)
+{
+    DBusMessage *msg;
+
+    if (mrp_unref_obj(m, refcnt)) {
+        msg = m->msg;
+
+        free_message(m);
+
+        if (msg != NULL)
+            dbus_message_unref(msg);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static DBusHandlerResult dispatch_method(DBusConnection *c,
+                                         DBusMessage *msg, void *data)
+{
+#define SAFESTR(str) (str ? str : "<none>")
+    const char *path      = dbus_message_get_path(msg);
+    const char *interface = dbus_message_get_interface(msg);
+    const char *member    = dbus_message_get_member(msg);
+
+    mrp_dbus_t     *dbus = (mrp_dbus_t *)data;
+    mrp_dbus_msg_t *m    = NULL;
+    int             r    = DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+    handler_list_t *l;
+    handler_t      *h;
+
+    MRP_UNUSED(c);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL || !member)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    mrp_debug("path='%s', interface='%s', member='%s')...",
+              SAFESTR(path), SAFESTR(interface), SAFESTR(member));
+
+    if ((l = mrp_htbl_lookup(dbus->methods, (void *)member)) != NULL) {
+    retry:
+        if ((h = handler_list_find(l, path, interface, member)) != NULL) {
+            if (m == NULL)
+                m = create_message(msg);
+
+            if (m != NULL && h->handler(dbus, m, h->user_data))
+                r = DBUS_HANDLER_RESULT_HANDLED;
+
+            goto out;
+        }
+    }
+    else {
+        if ((l = mrp_htbl_lookup(dbus->methods, "")) != NULL)
+            goto retry;
+    }
+
+ out:
+    mrp_dbus_msg_unref(m);
+
+    if (r == DBUS_HANDLER_RESULT_NOT_YET_HANDLED)
+        mrp_debug("Unhandled method path=%s, %s.%s.", SAFESTR(path),
+                  SAFESTR(interface), SAFESTR(member));
+
+    return r;
+}
+
+
+static DBusHandlerResult dispatch_signal(DBusConnection *c,
+                                         DBusMessage *msg, void *data)
+{
+#define MATCHES(h, field) (!*field || !h->field || !*h->field || \
+                           !strcmp(field, h->field))
+
+    const char *path      = dbus_message_get_path(msg);
+    const char *interface = dbus_message_get_interface(msg);
+    const char *member    = dbus_message_get_member(msg);
+
+    mrp_dbus_t      *dbus = (mrp_dbus_t *)data;
+    mrp_dbus_msg_t  *m    = NULL;
+    mrp_list_hook_t *p, *n;
+    handler_list_t  *l;
+    handler_t       *h;
+    int              retried = FALSE;
+    int              handled = FALSE;
+
+    MRP_UNUSED(c);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL || !member)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    mrp_debug("%s(path='%s', interface='%s', member='%s')...",
+              __FUNCTION__,
+              SAFESTR(path), SAFESTR(interface), SAFESTR(member));
+
+    if ((l = mrp_htbl_lookup(dbus->signals, (void *)member)) != NULL) {
+    retry:
+        mrp_list_foreach(&l->handlers, p, n) {
+            h = mrp_list_entry(p, handler_t, hook);
+
+            if (MATCHES(h,path) && MATCHES(h,interface) && MATCHES(h,member)) {
+                if (m == NULL)
+                    m = create_message(msg);
+
+                if (m == NULL)
+                    goto out;
+
+                h->handler(dbus, m, h->user_data);
+                handled = TRUE;
+
+                rewind_message(m);
+            }
+        }
+    }
+
+    if (!retried) {
+        if ((l = mrp_htbl_lookup(dbus->signals, "")) != NULL) {
+            retried = TRUE;
+            goto retry;
+        }
+    }
+
+    if (!handled)
+        mrp_debug("Unhandled signal path=%s, %s.%s.", SAFESTR(path),
+                  SAFESTR(interface), SAFESTR(member));
+
+ out:
+    mrp_dbus_msg_unref(m);
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+#undef MATCHES
+#undef SAFESTR
+}
+
+
+static int append_args_inttype(DBusMessage *msg, int type, va_list ap)
+{
+    void  *vptr;
+    void **aptr;
+    int    atype, alen;
+    int    r = TRUE;
+
+    while (type != MRP_DBUS_TYPE_INVALID && r) {
+        switch (type) {
+        case MRP_DBUS_TYPE_BYTE:
+        case MRP_DBUS_TYPE_BOOLEAN:
+        case MRP_DBUS_TYPE_INT16:
+        case MRP_DBUS_TYPE_UINT16:
+        case MRP_DBUS_TYPE_INT32:
+        case MRP_DBUS_TYPE_UINT32:
+        case MRP_DBUS_TYPE_INT64:
+        case MRP_DBUS_TYPE_UINT64:
+        case MRP_DBUS_TYPE_DOUBLE:
+        case MRP_DBUS_TYPE_UNIX_FD:
+            vptr = va_arg(ap, void *);
+            r = dbus_message_append_args(msg, type, vptr, DBUS_TYPE_INVALID);
+            break;
+
+        case MRP_DBUS_TYPE_STRING:
+        case MRP_DBUS_TYPE_OBJECT_PATH:
+        case MRP_DBUS_TYPE_SIGNATURE:
+            vptr = va_arg(ap, void *);
+            r = dbus_message_append_args(msg, type, &vptr, DBUS_TYPE_INVALID);
+            break;
+
+        case MRP_DBUS_TYPE_ARRAY:
+            atype = va_arg(ap, int);
+            aptr  = va_arg(ap, void **);
+            alen  = va_arg(ap, int);
+            r = dbus_message_append_args(msg, DBUS_TYPE_ARRAY,
+                                         atype, &aptr, alen, DBUS_TYPE_INVALID);
+            break;
+
+        default:
+            return FALSE;
+        }
+
+        type = va_arg(ap, int);
+    }
+
+    return r;
+}
+
+
+static void call_reply_cb(DBusPendingCall *pend, void *user_data)
+{
+    call_t         *call = (call_t *)user_data;
+    DBusMessage    *reply;
+    mrp_dbus_msg_t *m;
+
+    reply = dbus_pending_call_steal_reply(pend);
+    m     = create_message(reply);
+
+    call->pend = NULL;
+    mrp_list_delete(&call->hook);
+
+    call->cb(call->dbus, m, call->user_data);
+
+    mrp_dbus_msg_unref(m);
+    dbus_message_unref(reply);
+    dbus_pending_call_unref(pend);
+
+    call_free(call);
+}
+
+
+int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data, int type, ...)
+{
+    va_list          ap;
+    int32_t          id;
+    call_t          *call;
+    DBusMessage     *msg;
+    DBusPendingCall *pend;
+    int              success;
+
+    call = NULL;
+    pend = NULL;
+
+    msg = dbus_message_new_method_call(dest, path, interface, member);
+
+    if (msg == NULL)
+        return 0;
+
+    if (cb != NULL) {
+        if ((call = mrp_allocz(sizeof(*call))) != NULL) {
+            mrp_list_init(&call->hook);
+
+            call->dbus      = dbus;
+            call->id        = dbus->call_id++;
+            call->cb        = cb;
+            call->user_data = user_data;
+
+            id = call->id;
+        }
+        else
+            goto fail;
+    }
+    else
+        id = dbus->call_id++;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = append_args_inttype(msg, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (cb == NULL) {
+        dbus_message_set_no_reply(msg, TRUE);
+        if (!dbus_connection_send(dbus->conn, msg, NULL))
+            goto fail;
+    }
+    else {
+        if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout))
+            goto fail;
+
+        if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL))
+            goto fail;
+    }
+
+    if (cb != NULL) {
+        mrp_list_append(&dbus->calls, &call->hook);
+        call->pend = pend;
+    }
+
+    dbus_message_unref(msg);
+
+    return id;
+
+ fail:
+    if (pend != NULL)
+        dbus_pending_call_unref(pend);
+
+    if(msg != NULL)
+        dbus_message_unref(msg);
+
+    call_free(call);
+
+    return 0;
+}
+
+
+int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data,
+                      mrp_dbus_msg_t *m)
+{
+    int32_t          id;
+    call_t          *call;
+    DBusPendingCall *pend;
+    DBusMessage     *msg;
+    int              method;
+
+    call = NULL;
+    pend = NULL;
+    msg  = m->msg;
+
+    if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL) {
+        if (cb != NULL)
+            goto fail;
+        else
+            method = FALSE;
+    }
+    else
+        method = TRUE;
+
+    if (cb != NULL) {
+        if ((call = mrp_allocz(sizeof(*call))) != NULL) {
+            mrp_list_init(&call->hook);
+
+            call->dbus      = dbus;
+            call->id        = dbus->call_id++;
+            call->cb        = cb;
+            call->user_data = user_data;
+
+            id = call->id;
+        }
+        else
+            goto fail;
+    }
+    else
+        id = dbus->call_id++;
+
+    if (!dbus_message_set_destination(msg, dest))
+        goto fail;
+    if (!dbus_message_set_path(msg, path))
+        goto fail;
+    if (!dbus_message_set_interface(msg, interface))
+        goto fail;
+    if (!dbus_message_set_member(msg, member))
+        goto fail;
+
+    if (cb == NULL) {
+        if (method)
+            dbus_message_set_no_reply(msg, TRUE);
+        if (!dbus_connection_send(dbus->conn, msg, NULL))
+            goto fail;
+    }
+    else {
+        if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout))
+            goto fail;
+
+        if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL))
+            goto fail;
+    }
+
+    if (cb != NULL) {
+        mrp_list_append(&dbus->calls, &call->hook);
+        call->pend = pend;
+    }
+
+    return id;
+
+ fail:
+    if (pend != NULL)
+        dbus_pending_call_unref(pend);
+
+    call_free(call);
+
+    return 0;
+}
+
+
+int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *m)
+{
+    return dbus_connection_send(dbus->conn, m->msg, NULL);
+}
+
+
+int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id)
+{
+    mrp_list_hook_t *p, *n;
+    call_t          *call;
+
+    mrp_list_foreach(&dbus->calls, p, n) {
+        call = mrp_list_entry(p, call_t, hook);
+
+        if (call->id == id) {
+            mrp_list_delete(p);
+
+            dbus_pending_call_cancel(call->pend);
+            dbus_pending_call_unref(call->pend);
+            call->pend = NULL;
+
+            call_free(call);
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, int type, ...)
+{
+    va_list      ap;
+    DBusMessage *msg, *rpl;
+    int          success;
+
+    msg = m->msg;
+    rpl = dbus_message_new_method_return(msg);
+
+    if (rpl == NULL)
+        return FALSE;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = append_args_inttype(rpl, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (!dbus_connection_send(dbus->conn, rpl, NULL))
+        goto fail;
+
+    dbus_message_unref(rpl);
+
+    return TRUE;
+
+ fail:
+    if(rpl != NULL)
+        dbus_message_unref(rpl);
+
+    return FALSE;
+}
+
+
+int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *m,
+                         const char *errname, const char *errmsg, int type, ...)
+{
+    va_list      ap;
+    DBusMessage *msg, *rpl;
+    int          success;
+
+    msg = m->msg;
+    rpl = dbus_message_new_error(msg, errname, errmsg);
+
+    if (rpl == NULL)
+        return FALSE;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = append_args_inttype(rpl, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (!dbus_connection_send(dbus->conn, rpl, NULL))
+        goto fail;
+
+    dbus_message_unref(rpl);
+
+    return TRUE;
+
+ fail:
+    if(rpl != NULL)
+        dbus_message_unref(rpl);
+
+    return FALSE;
+}
+
+
+static void call_free(call_t *call)
+{
+    if (call != NULL)
+        mrp_free(call);
+}
+
+
+static void purge_calls(mrp_dbus_t *dbus)
+{
+    mrp_list_hook_t *p, *n;
+    call_t          *call;
+
+    mrp_list_foreach(&dbus->calls, p, n) {
+        call = mrp_list_entry(p, call_t, hook);
+
+        mrp_list_delete(&call->hook);
+
+        if (call->pend != NULL)
+            dbus_pending_call_unref(call->pend);
+
+        mrp_free(call);
+    }
+}
+
+
+int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path,
+                    const char *interface, const char *member, int type, ...)
+{
+    va_list      ap;
+    DBusMessage *msg;
+    int          success;
+
+    msg = dbus_message_new_signal(path, interface, member);
+
+    if (msg == NULL)
+        return 0;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = append_args_inttype(msg, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (dest && *dest && !dbus_message_set_destination(msg, dest))
+        goto fail;
+
+    if (!dbus_connection_send(dbus->conn, msg, NULL))
+        goto fail;
+
+    dbus_message_unref(msg);
+
+    return TRUE;
+
+ fail:
+    /*
+     * XXX TODO: Hmm... IIRC, libdbus unrefs messages upon failure. If it
+     *           was really so, this would corrupt/crash. Check this from
+     *           libdbus code.
+     */
+    if(msg != NULL)
+        dbus_message_unref(msg);
+
+    return 0;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *bus,
+                                         const char *destination,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member)
+{
+    mrp_dbus_msg_t *m;
+    DBusMessage    *msg;
+
+    MRP_UNUSED(bus);
+
+    msg = dbus_message_new_method_call(destination, path, interface, member);
+
+    if (msg != NULL) {
+        m = create_message(msg);
+        dbus_message_unref(msg);
+    }
+    else
+        m = NULL;
+
+    return m;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *bus,
+                                           mrp_dbus_msg_t *m)
+{
+    mrp_dbus_msg_t *mr;
+    DBusMessage    *msg;
+
+    MRP_UNUSED(bus);
+
+    msg = dbus_message_new_method_return(m->msg);
+
+    if (msg != NULL) {
+        mr = create_message(msg);
+        dbus_message_unref(msg);
+    }
+    else
+        mr = NULL;
+
+    return mr;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *bus, mrp_dbus_msg_t *m,
+                                   mrp_dbus_err_t *err)
+{
+    mrp_dbus_msg_t *me;
+    DBusMessage    *msg;
+
+    MRP_UNUSED(bus);
+
+    msg = dbus_message_new_error(m->msg, err->name, err->message);
+
+    if (msg != NULL) {
+        me = create_message(msg);
+        dbus_message_unref(msg);
+    }
+    else
+        me = NULL;
+
+    return me;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *bus,
+                                    const char *destination,
+                                    const char *path,
+                                    const char *interface,
+                                    const char *member)
+{
+    mrp_dbus_msg_t *m;
+    DBusMessage    *msg;
+
+    MRP_UNUSED(bus);
+
+    msg = dbus_message_new_signal(path, interface, member);
+
+    if (msg != NULL) {
+        if (!destination || dbus_message_set_destination(msg, destination)) {
+            m = create_message(msg);
+            dbus_message_unref(msg);
+        }
+        else {
+            dbus_message_unref(msg);
+            m = NULL;
+        }
+    }
+    else
+        m = NULL;
+
+    return m;
+}
+
+
+mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *m)
+{
+    return (mrp_dbus_msg_type_t)dbus_message_get_type(m->msg);
+}
+
+#define WRAP_GETTER(type, what)                         \
+    type mrp_dbus_msg_##what(mrp_dbus_msg_t *m)         \
+    {                                                   \
+        return dbus_message_get_##what(m->msg);         \
+    }                                                   \
+    struct __mrp_dbus_allow_trailing_semicolon
+
+WRAP_GETTER(const char *, path);
+WRAP_GETTER(const char *, interface);
+WRAP_GETTER(const char *, member);
+WRAP_GETTER(const char *, destination);
+WRAP_GETTER(const char *, sender);
+
+#undef WRAP_GETTER
+
+
+static msg_iter_t *message_iterator(mrp_dbus_msg_t *m, int append)
+{
+    msg_iter_t *it;
+
+    if (mrp_list_empty(&m->iterators)) {
+        if ((it = mrp_allocz(sizeof(*it))) != NULL) {
+            mrp_list_init(&it->hook);
+            mrp_list_append(&m->iterators, &it->hook);
+
+            if (append)
+                dbus_message_iter_init_append(m->msg, &it->it);
+            else
+                dbus_message_iter_init(m->msg, &it->it);
+        }
+    }
+    else
+        it = mrp_list_entry(&m->iterators.next, typeof(*it), hook);
+
+    return it;
+}
+
+
+msg_iter_t *current_iterator(mrp_dbus_msg_t *m)
+{
+    msg_iter_t *it;
+
+    if (!mrp_list_empty(&m->iterators))
+        it = mrp_list_entry(m->iterators.prev, typeof(*it), hook);
+    else
+        it = NULL;
+
+    return it;
+}
+
+
+int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type,
+                                const char *contents)
+{
+    msg_iter_t *it, *parent;
+
+    if ((parent = current_iterator(m))       == NULL &&
+        (parent = message_iterator(m, TRUE)) == NULL)
+        return FALSE;
+
+    if ((it = mrp_allocz(sizeof(*it))) != NULL) {
+        mrp_list_init(&it->hook);
+
+        if (dbus_message_iter_open_container(&parent->it, type, contents,
+                                             &it->it)) {
+            mrp_list_append(&m->iterators, &it->hook);
+
+            return TRUE;
+        }
+
+        mrp_free(it);
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m)
+{
+    msg_iter_t *it, *parent;
+    int         r;
+
+    it = current_iterator(m);
+
+    if (it == NULL || it == message_iterator(m, FALSE))
+        return FALSE;
+
+    mrp_list_delete(&it->hook);
+
+    if ((parent = current_iterator(m)) != NULL)
+        r = dbus_message_iter_close_container(&parent->it, &it->it);
+    else
+        r = FALSE;
+
+    mrp_free(it);
+
+    return r;
+}
+
+
+int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep)
+{
+    msg_iter_t *it;
+
+    if (!dbus_type_is_basic(type))
+        return FALSE;
+
+    if ((it = current_iterator(m))       != NULL ||
+        (it = message_iterator(m, TRUE)) != NULL) {
+        if (type != MRP_DBUS_TYPE_STRING &&
+            type != MRP_DBUS_TYPE_OBJECT_PATH &&
+            type != MRP_DBUS_TYPE_SIGNATURE)
+            return dbus_message_iter_append_basic(&it->it, type, valuep);
+        else
+            return dbus_message_iter_append_basic(&it->it, type, &valuep);
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *m, char type,
+                                 const char *contents)
+{
+    msg_iter_t *it, *parent;
+    char       *signature;
+
+    if ((parent = current_iterator(m))        == NULL &&
+        (parent = message_iterator(m, FALSE)) == NULL)
+        return FALSE;
+
+    if (dbus_message_iter_get_arg_type(&parent->it) != type)
+        return FALSE;
+
+    if ((it = mrp_allocz(sizeof(*it))) != NULL) {
+        mrp_list_init(&it->hook);
+        mrp_list_append(&m->iterators, &it->hook);
+
+        dbus_message_iter_recurse(&parent->it, &it->it);
+
+        if (contents != NULL) {
+            /* XXX TODO: proper signature checking */
+            signature = dbus_message_iter_get_signature(&it->it);
+            if (strcmp(contents, signature))
+                mrp_log_error("*** %s(): signature mismath ('%s' != '%s')",
+                              __FUNCTION__, contents, signature);
+            mrp_free(signature);
+        }
+
+        dbus_message_iter_next(&parent->it);
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m)
+{
+    msg_iter_t *it;
+
+    if ((it = current_iterator(m)) == NULL || it == message_iterator(m, FALSE))
+        return FALSE;
+
+    mrp_list_delete(&it->hook);
+
+    mrp_free(it->peeked);
+    mrp_free(it);
+
+    return TRUE;
+}
+
+
+int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep)
+{
+    msg_iter_t *it;
+
+    if (!dbus_type_is_basic(type))
+        return FALSE;
+
+    if ((it = current_iterator(m))        != NULL ||
+        (it = message_iterator(m, FALSE)) != NULL) {
+        if (dbus_message_iter_get_arg_type(&it->it) == type) {
+            dbus_message_iter_get_basic(&it->it, valuep);
+            dbus_message_iter_next(&it->it);
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void free_msg_array(msg_array_t *a)
+{
+    if (a == NULL)
+        return;
+
+    mrp_list_delete(&a->hook);
+    mrp_free(a->items);
+    mrp_free(a);
+}
+
+
+int mrp_dbus_msg_read_array(mrp_dbus_msg_t *m, char type,
+                            void **itemsp, size_t *nitemp)
+{
+    msg_iter_t      *it;
+    msg_array_t     *a;
+    DBusMessageIter  sub;
+    void            *items;
+    int              nitem, atype;
+
+    if (!dbus_type_is_basic(type))
+        return FALSE;
+
+    if ((it = current_iterator(m))        == NULL &&
+        (it = message_iterator(m, FALSE)) == NULL)
+        return FALSE;
+
+    if (dbus_message_iter_get_arg_type(&it->it) != DBUS_TYPE_ARRAY)
+        return FALSE;
+
+    dbus_message_iter_recurse(&it->it, &sub);
+    atype = dbus_message_iter_get_arg_type(&sub);
+
+    if (atype == MRP_DBUS_TYPE_INVALID) {
+        items = NULL;
+        nitem = 0;
+
+        goto out;
+    }
+
+    if (atype != type)
+        return FALSE;
+
+    /* for fixed types, just use the libdbus function */
+    if (type != MRP_DBUS_TYPE_STRING && type != MRP_DBUS_TYPE_OBJECT_PATH) {
+        nitem = -1;
+        items = NULL;
+        dbus_message_iter_get_fixed_array(&sub, (void *)&items, &nitem);
+
+        if (nitem == -1)
+            return FALSE;
+    }
+    /* for string-like types, collect items into an implicitly freed array */
+    else {
+        a = mrp_allocz(sizeof(*a));
+
+        if (a == NULL)
+            return FALSE;
+
+        mrp_list_init(&a->hook);
+
+        while (dbus_message_iter_get_arg_type(&sub) != DBUS_TYPE_INVALID) {
+            if (mrp_reallocz(a->items, a->nitem, a->nitem + 1) != NULL) {
+                dbus_message_iter_get_basic(&sub, a->items + a->nitem);
+                a->nitem++;
+                dbus_message_iter_next(&sub);
+            }
+            else {
+                free_msg_array(a);
+                return FALSE;
+            }
+        }
+
+        mrp_list_append(&m->arrays, &a->hook);
+
+        items = a->items;
+        nitem = a->nitem;
+    }
+
+ out:
+    dbus_message_iter_next(&it->it);
+
+    *itemsp = items;
+    *nitemp = (size_t)nitem;
+
+    return TRUE;
+}
+
+
+mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents)
+{
+    msg_iter_t      *it;
+    DBusMessageIter  sub;
+    char             type;
+
+    if ((it = current_iterator(m))        != NULL ||
+        (it = message_iterator(m, FALSE)) != NULL) {
+        type = dbus_message_iter_get_arg_type(&it->it);
+
+        if (dbus_type_is_container(type)) {
+            mrp_free(it->peeked);
+
+            if (contents != NULL) {
+                dbus_message_iter_recurse(&it->it, &sub);
+                it->peeked = dbus_message_iter_get_signature(&sub);
+                *contents = it->peeked;
+            }
+        }
+
+        return type;
+    }
+
+    return MRP_DBUS_TYPE_INVALID;
+}
diff --git a/src/common/dbus-libdbus.h b/src/common/dbus-libdbus.h
new file mode 100644 (file)
index 0000000..8c729d9
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DBUS_LIBDBUS_H__
+#define __MURPHY_DBUS_LIBDBUS_H__
+
+#include <murphy/common/mainloop.h>
+#include <murphy/common/dbus-error.h>
+#include <dbus/dbus.h>
+
+/** Type for a D-Bus (connection). */
+struct mrp_dbus_s;
+typedef struct mrp_dbus_s mrp_dbus_t;
+
+/** Type for a D-Bus message. */
+struct mrp_dbus_msg_s;
+typedef struct mrp_dbus_msg_s mrp_dbus_msg_t;
+
+/** Type for a D-Bus error. */
+typedef DBusError mrp_dbus_err_t;
+
+/** D-BUS method or signal callback type. */
+typedef int (*mrp_dbus_handler_t)(mrp_dbus_t *, mrp_dbus_msg_t *, void *);
+
+/** Create a new connection to the given bus. */
+mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address,
+                             mrp_dbus_err_t *errp);
+#define mrp_dbus_get mrp_dbus_connect
+
+/** Set up a DBusConnection with a mainloop. */
+int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn);
+
+/** Increase the reference count of the given DBus (connection). */
+mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus);
+
+/** Decrease the reference count of the given DBus (connection). */
+int mrp_dbus_unref(mrp_dbus_t *dbus);
+
+/** Acquire the given name on the given bus (connection). */
+int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error);
+
+/** Release the given name on the given bus (connection). */
+int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error);
+
+/** Type for a name tracking callback. */
+typedef void (*mrp_dbus_name_cb_t)(mrp_dbus_t *, const char *, int,
+                                   const char *, void *);
+/** Start tracking the given name. */
+int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data);
+/** Stop tracking the given name. */
+int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data);
+
+/** Export a method to the bus. */
+int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data);
+
+/** Remove an exported method. */
+int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data);
+
+/** Install a filter and add a handler for the given signal on the bus. */
+MRP_NULLTERM int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus,
+                                           mrp_dbus_handler_t handler,
+                                           void *user_data,
+                                           const char *sender,
+                                           const char *path,
+                                           const char *interface,
+                                           const char *member, ...);
+
+/** Remove the signal handler and filter for the given signal on the bus. */
+MRP_NULLTERM int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus,
+                                             mrp_dbus_handler_t handler,
+                                             void *user_data,
+                                             const char *sender,
+                                             const char *path,
+                                             const char *interface,
+                                             const char *member, ...);
+
+/** Install a filter for the given message on the bus. */
+MRP_NULLTERM int mrp_dbus_install_filter(mrp_dbus_t *dbus,
+                                         const char *sender,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member, ...);
+
+/** Install a filter for the given message on the bus. */
+int mrp_dbus_install_filterv(mrp_dbus_t *dbus,
+                             const char *sender,
+                             const char *path,
+                             const char *interface,
+                             const char *member,
+                             va_list ap);
+
+/** Remove a filter for the given message on the bus. */
+MRP_NULLTERM int mrp_dbus_remove_filter(mrp_dbus_t *dbus,
+                                        const char *sender,
+                                        const char *path,
+                                        const char *interface,
+                                        const char *member, ...);
+
+/** Remove a filter for the given message on the bus. */
+int mrp_dbus_remove_filterv(mrp_dbus_t *dbus,
+                            const char *sender,
+                            const char *path,
+                            const char *interface,
+                            const char *member,
+                            va_list ap);
+
+/** Add a signal handler for the gvien signal on the bus. */
+int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data);
+
+/** Remove the given signal handler for the given signal on the bus. */
+int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data);
+
+/** Type of a method call reply callback. */
+typedef void (*mrp_dbus_reply_cb_t)(mrp_dbus_t *dbus, mrp_dbus_msg_t *reply,
+                                    void *user_data);
+
+/** Call the given method on the bus. */
+int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest,
+                      const char *path, const char *interface,
+                      const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data,
+                      int dbus_type, ...);
+
+/** Cancel an ongoing method call on the bus. */
+int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id);
+
+/** Send a reply to the given method call on the bus. */
+int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, int type, ...);
+
+/** Send an error reply to the given method call on the bus. */
+int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg,
+                         const char *errname, const char *errmsg,
+                         int type, ...);
+
+/** Emit the given signal on the bus. */
+int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path,
+                    const char *interface, const char *member, int type, ...);
+
+/** Send the given method call message on the bus. */
+int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data,
+                      mrp_dbus_msg_t *msg);
+
+/** Send the given message on the bus. */
+int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg);
+
+/** Get our unique name on the bus. */
+const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus);
+
+/** Initialize the given error. */
+static inline mrp_dbus_err_t *mrp_dbus_error_init(mrp_dbus_err_t *err)
+{
+    if (err != NULL)
+        dbus_error_init(err);
+
+    return err;
+}
+
+/** Set the given error buffer up with the error name and message. */
+static inline mrp_dbus_err_t *mrp_dbus_error_set(mrp_dbus_err_t *err,
+                                                 const char *name,
+                                                 const char *message)
+{
+    dbus_set_error_const(err, name, message);
+
+    return err;
+}
+
+
+/** Get the error message from the given bus error message. */
+static inline const char *mrp_dbus_errmsg(mrp_dbus_err_t *err)
+{
+    if (err && dbus_error_is_set(err))
+        return err->message;
+    else
+        return "unknown DBUS error";
+}
+
+
+/** Increase the reference count of a message. */
+mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m);
+
+/** Decrease the reference count of a message, freeing it if necessary. */
+int mrp_dbus_msg_unref(mrp_dbus_msg_t *m);
+
+
+/** Create a new method call message. */
+mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *bus,
+                                         const char *destination,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member);
+
+/** Create a new method return message. */
+mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *bus,
+                                           mrp_dbus_msg_t *msg);
+
+/** Create a new error reply message. */
+mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *bus, mrp_dbus_msg_t *msg,
+                                   mrp_dbus_err_t *err);
+
+/** Create a new signal message. */
+mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *bus,
+                                    const char *destination,
+                                    const char *path,
+                                    const char *interface,
+                                    const char *member);
+
+/** Bus message types. */
+typedef enum {
+#   define TYPE(type) MRP_DBUS_MESSAGE_TYPE_##type = DBUS_MESSAGE_TYPE_##type
+    TYPE(INVALID),
+    TYPE(METHOD_CALL),
+    TYPE(METHOD_RETURN),
+    TYPE(ERROR),
+    TYPE(SIGNAL)
+#   undef TYPE
+} mrp_dbus_msg_type_t;
+
+/** Get the type of the given message. */
+mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *msg);
+
+/** Message type checking convenience functions. */
+#define TYPE_CHECK_FUNCTION(type, TYPE)                                 \
+    static inline int mrp_dbus_msg_is_##type(mrp_dbus_msg_t *msg)       \
+    {                                                                   \
+        return mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_##TYPE;  \
+    }                                                                   \
+    struct __mrp_dbus_allow_traling_semicolon
+
+TYPE_CHECK_FUNCTION(method_call  , METHOD_CALL);
+TYPE_CHECK_FUNCTION(method_return, METHOD_RETURN);
+TYPE_CHECK_FUNCTION(error        , ERROR);
+TYPE_CHECK_FUNCTION(signal       , SIGNAL);
+
+/** Message argument types. */
+typedef enum {
+#define TYPE(t) MRP_DBUS_TYPE_##t = DBUS_TYPE_##t
+    TYPE(INVALID),
+    TYPE(BYTE),
+    TYPE(BOOLEAN),
+    TYPE(INT16),
+    TYPE(UINT16),
+    TYPE(INT32),
+    TYPE(UINT32),
+    TYPE(INT64),
+    TYPE(UINT64),
+    TYPE(DOUBLE),
+    TYPE(STRING),
+    TYPE(OBJECT_PATH),
+    TYPE(SIGNATURE),
+    TYPE(UNIX_FD),
+    TYPE(ARRAY),
+    TYPE(VARIANT),
+    TYPE(STRUCT),
+    TYPE(DICT_ENTRY),
+#undef TYPE
+} mrp_dbus_type_t;
+
+/** Message argument types as strings. */
+#define MRP_DBUS_TYPE_BYTE_AS_STRING        DBUS_TYPE_BYTE_AS_STRING
+#define MRP_DBUS_TYPE_BOOLEAN_AS_STRING     DBUS_TYPE_BOOLEAN_AS_STRING
+#define MRP_DBUS_TYPE_INT16_AS_STRING       DBUS_TYPE_INT16_AS_STRING
+#define MRP_DBUS_TYPE_UINT16_AS_STRING      DBUS_TYPE_UINT16_AS_STRING
+#define MRP_DBUS_TYPE_INT32_AS_STRING       DBUS_TYPE_INT32_AS_STRING
+#define MRP_DBUS_TYPE_UINT32_AS_STRING      DBUS_TYPE_UINT32_AS_STRING
+#define MRP_DBUS_TYPE_INT64_AS_STRING       DBUS_TYPE_INT64_AS_STRING
+#define MRP_DBUS_TYPE_UINT64_AS_STRING      DBUS_TYPE_UINT64_AS_STRING
+#define MRP_DBUS_TYPE_DOUBLE_AS_STRING      DBUS_TYPE_DOUBLE_AS_STRING
+#define MRP_DBUS_TYPE_STRING_AS_STRING      DBUS_TYPE_STRING_AS_STRING
+#define MRP_DBUS_TYPE_OBJECT_PATH_AS_STRING DBUS_TYPE_OBJECT_PATH_AS_STRING
+#define MRP_DBUS_TYPE_SIGNATURE_AS_STRING   DBUS_TYPE_SIGNATURE_AS_STRING
+#define MRP_DBUS_TYPE_UNIX_FD_AS_STRING     DBUS_TYPE_UNIX_FD_AS_STRING
+#define MRP_DBUS_TYPE_ARRAY_AS_STRING       DBUS_TYPE_ARRAY_AS_STRING
+#define MRP_DBUS_TYPE_VARIANT_AS_STRING     DBUS_TYPE_VARIANT_AS_STRING
+#define MRP_DBUS_TYPE_STRUCT_AS_STRING      DBUS_TYPE_STRUCT_AS_STRING
+#define MRP_DBUS_TYPE_DICT_ENTRY_AS_STRING  DBUS_TYPE_DICT_ENTRY_AS_STRING
+
+/** Get the path of the given message. */
+const char *mrp_dbus_msg_path(mrp_dbus_msg_t *msg);
+
+/** Get the interface of the given message. */
+const char *mrp_dbus_msg_interface(mrp_dbus_msg_t *msg);
+
+/** Get the member of the given message. */
+const char *mrp_dbus_msg_member(mrp_dbus_msg_t *msg);
+
+/** Get the destination of the given message. */
+const char *mrp_dbus_msg_destination(mrp_dbus_msg_t *msg);
+
+/** Get the sender of the given message. */
+const char *mrp_dbus_msg_sender(mrp_dbus_msg_t *msg);
+
+/** Open a new container of the given type and cotained types. */
+int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type,
+                                const char *contents);
+
+/** Close the current container. */
+int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m);
+
+/** Append an argument of a basic type to the given message. */
+int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep);
+
+/** Get the type of the current message argument. */
+mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents);
+
+/** Open the current container (of the given type and contents) for reading. */
+int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *msg, char type,
+                                 const char *contents);
+
+/** Exit from the container being currently read. */
+int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m);
+
+/** Read the next argument (of basic type) from the given message. */
+int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep);
+
+/** Read the next array of one of the basic types. */
+int mrp_dbus_msg_read_array(mrp_dbus_msg_t *msg, char type,
+                            void **itemsp, size_t *nitemp);
+
+#endif /* __MURPHY_DBUS_LIBDBUS_H__ */
diff --git a/src/common/dbus-sdbus-glue.c b/src/common/dbus-sdbus-glue.c
new file mode 100644 (file)
index 0000000..a2b98c6
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+
+#include <murphy/common/dbus-sdbus.h>
+
+#define USEC_TO_MSEC(usec) ((unsigned int)((usec) / 1000))
+#define MSEC_TO_USEC(msec) ((uint64_t)(msec) * 1000)
+
+typedef struct {
+    sd_bus         *bus;
+    mrp_mainloop_t *ml;
+    mrp_subloop_t  *sl;
+    int             events;
+} bus_glue_t;
+
+
+static int bus_prepare(void *user_data)
+{
+    MRP_UNUSED(user_data);
+
+    return FALSE;
+}
+
+
+static int bus_query(void *user_data, struct pollfd *fds, int nfd, int *timeout)
+{
+    bus_glue_t *b = (bus_glue_t *)user_data;
+    uint64_t    usec;
+
+    if (nfd > 0) {
+        fds[0].fd      = sd_bus_get_fd(b->bus);
+        fds[0].events  = sd_bus_get_events(b->bus) | POLLIN | POLLHUP;
+        fds[0].revents = 0;
+
+        if (sd_bus_get_timeout(b->bus, &usec) < 0)
+            *timeout = -1;
+        else
+            *timeout = USEC_TO_MSEC(usec);
+
+        mrp_debug("fd: %d, events: 0x%x, timeout: %u", fds[0].fd,
+                  fds[0].events, *timeout);
+    }
+
+    return 1;
+}
+
+
+static int bus_check(void *user_data, struct pollfd *fds, int nfd)
+{
+    bus_glue_t *b = (bus_glue_t *)user_data;
+
+    if (nfd > 0) {
+        b->events = fds[0].revents;
+
+        if (b->events != 0)
+            return TRUE;
+    }
+    else
+        b->events = 0;
+
+    return FALSE;
+}
+
+
+static void bus_dispatch(void *user_data)
+{
+    bus_glue_t *b = (bus_glue_t *)user_data;
+
+    mrp_debug("dispatching events 0x%x to sd_bus %p", b->events, b->bus);
+
+    if (b->events & MRP_IO_EVENT_HUP)
+        mrp_debug("sd_bus peer has closed the connection");
+
+    while (sd_bus_process(b->bus, NULL) > 0)
+        sd_bus_flush(b->bus);
+
+    mrp_debug("done dispatching");
+}
+
+
+int mrp_dbus_setup_with_mainloop(mrp_mainloop_t *ml, sd_bus *bus)
+{
+    static mrp_subloop_ops_t bus_ops = {
+        .prepare  = bus_prepare,
+        .query    = bus_query,
+        .check    = bus_check,
+        .dispatch = bus_dispatch
+    };
+
+    bus_glue_t *b;
+
+
+    if ((b = mrp_allocz(sizeof(*b))) != NULL) {
+        /* XXX TODO: Hmm... is this really needed ? */
+        while (sd_bus_process(bus, NULL) > 0)
+            sd_bus_flush(bus);
+
+        b->bus = bus;
+        b->ml  = ml;
+        b->sl  = mrp_add_subloop(ml, &bus_ops, b);
+
+        if (b->sl != NULL)
+            return TRUE;
+        else
+            mrp_free(b);
+    }
+
+    return FALSE;
+}
diff --git a/src/common/dbus-sdbus-transport.c b/src/common/dbus-sdbus-transport.c
new file mode 100644 (file)
index 0000000..3b62ac3
--- /dev/null
@@ -0,0 +1,1782 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/dbus-sdbus.h>
+#include <murphy/common/dbus-transport.h>
+
+#define DBUS  "dbus"
+#define DBUSL 4
+
+#define TRANSPORT_PATH       "/murphy/transport"
+#define TRANSPORT_INTERFACE  "Murphy.Transport"
+#define TRANSPORT_MESSAGE    "DeliverMessage"
+#define TRANSPORT_DATA       "DeliverData"
+#define TRANSPORT_RAW        "DeliverRaw"
+#define TRANSPORT_METHOD     "DeliverMessage"
+
+#define ANY_ADDRESS          "any"
+
+typedef struct {
+    MRP_TRANSPORT_PUBLIC_FIELDS;         /* common transport fields */
+    mrp_dbus_t     *dbus;                /* D-BUS connection */
+    int             bound : 1;           /* whether bound to an address */
+    int             peer_resolved : 1;   /* connected and peer name known */
+    mrp_dbusaddr_t  local;               /* address we're bound to */
+    mrp_dbusaddr_t  remote;              /* address we're connected to */
+} dbus_t;
+
+
+static uint32_t nauto;                   /* for autobinding */
+
+
+static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data);
+static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data);
+static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data);
+
+static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data);
+
+static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  mrp_msg_t *msg);
+static mrp_msg_t *msg_decode(mrp_dbus_msg_t *msg, const char **sender_id);
+
+static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination,
+                                   const char *path, const char *interface,
+                                   const char *member, const char *sender_id,
+                                   void *data, uint16_t tag);
+static void *data_decode(mrp_dbus_msg_t *msg, uint16_t *tag,
+                         const char **sender_id);
+
+static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  void *data, size_t size);
+static void *raw_decode(mrp_dbus_msg_t *msg, size_t *sizep,
+                        const char **sender_id);
+
+
+static socklen_t parse_address(const char *str, mrp_dbusaddr_t *addr,
+                               socklen_t size)
+{
+    const char *p, *e;
+    char       *q;
+    size_t      l, n;
+
+    if (size < sizeof(*addr)) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    if (strncmp(str, DBUS":", DBUSL + 1))
+        return 0;
+    else
+        str += DBUSL + 1;
+
+    /*
+     * The format of the address is
+     *     dbus:[bus-address]@address/path
+     * eg.
+     *     dbus:[session]@:1.33/client1, or
+     *     dbus:[unix:abstract=/tmp/dbus-Xx2Kpi...a572]@:1.33/client1
+     */
+
+    p = str;
+    q = addr->db_fqa;
+    l = sizeof(addr->db_fqa);
+
+    /* get bus address */
+    if (*p != '[') {
+        errno = EINVAL;
+        return 0;
+    }
+    else
+        p++;
+
+    e = strchr(p, ']');
+
+    if (e == NULL) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    n = e - p;
+    if (n >= l) {
+        errno = ENAMETOOLONG;
+        return 0;
+    }
+
+    /* save bus address */
+    strncpy(q, p, n);
+    q[n] = '\0';
+    addr->db_bus = q;
+
+    q += n + 1;
+    l -= n + 1;
+    p  = e + 1;
+
+    /* get (local or remote) address on bus */
+    if (*p != '@')
+        addr->db_addr = ANY_ADDRESS;
+    else {
+        p++;
+        e = strchr(p, '/');
+
+        if (e == NULL) {
+            errno = EINVAL;
+            return 0;
+        }
+
+        n = e - p;
+        if (n >= l) {
+            errno = ENAMETOOLONG;
+            return 0;
+        }
+
+        /* save address on bus */
+        strncpy(q, p, n);
+        q[n] = '\0';
+        addr->db_addr = q;
+
+        q += n + 1;
+        l -= n + 1;
+        p  = e;
+    }
+
+    /* get object path */
+    if (*p != '/') {
+        errno = EINVAL;
+        return 0;
+    }
+
+    n = snprintf(q, l, "%s%s", TRANSPORT_PATH, p);
+    if (n >= l) {
+        errno = ENAMETOOLONG;
+        return 0;
+    }
+
+    addr->db_path   = q;
+    addr->db_family = MRP_AF_DBUS;
+
+    return sizeof(*addr);
+}
+
+
+static mrp_dbusaddr_t *copy_address(mrp_dbusaddr_t *dst, mrp_dbusaddr_t *src)
+{
+    char   *p, *q;
+    size_t  l, n;
+
+    dst->db_family = src->db_family;
+
+    /* copy bus address */
+    p = src->db_bus;
+    q = dst->db_fqa;
+    l = sizeof(dst->db_fqa);
+
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_bus = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    /* copy address */
+    p = src->db_addr;
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_addr = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    /* copy path */
+    p = src->db_path;
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_path = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    return dst;
+}
+
+
+static inline int check_address(mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addr;
+
+    return (a && a->db_family == MRP_AF_DBUS && addrlen == sizeof(*a));
+}
+
+
+static size_t peer_address(mrp_sockaddr_t *addrp, const char *sender,
+                           const char *path)
+{
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    const char     *p;
+    char           *q;
+    int             l, n;
+
+    q = addr->db_fqa;
+    l = sizeof(addr->db_fqa);
+    p = ANY_ADDRESS;
+    n = 3;
+
+    addr->db_bus = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    addr->db_addr = q;
+    p = sender;
+    n = strlen(sender);
+
+    if (l < n + 1)
+        return 0;
+
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    addr->db_path = q;
+    p = path;
+    n = strlen(p);
+
+    if (l < n + 1)
+        return 0;
+
+    memcpy(q, p, n + 1);
+
+    return sizeof(addrp);
+}
+
+
+static socklen_t dbus_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    socklen_t len;
+
+    len = parse_address(str, (mrp_dbusaddr_t *)addr, size);
+
+    if (len > 0) {
+        if (typep != NULL)
+            *typep = DBUS;
+    }
+
+    return len;
+}
+
+
+static int dbus_open(mrp_transport_t *mt)
+{
+    MRP_UNUSED(mt);
+
+    return TRUE;
+}
+
+
+static int dbus_createfrom(mrp_transport_t *mt, void *conn)
+{
+    dbus_t     *t    = (dbus_t *)mt;
+    mrp_dbus_t *dbus = (mrp_dbus_t *)conn;
+
+    t->dbus = mrp_dbus_ref(dbus);
+
+    if (t->dbus != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static int dbus_bind(mrp_transport_t *mt, mrp_sockaddr_t *addrp,
+                     socklen_t addrlen)
+{
+    dbus_t          *t    = (dbus_t *)mt;
+    mrp_dbus_t      *dbus = NULL;
+    mrp_dbusaddr_t  *addr = (mrp_dbusaddr_t *)addrp;
+    int            (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *);
+    const char      *method;
+
+    if (t->bound) {
+        errno = EINVAL;
+        goto fail;
+    }
+
+    if (!check_address(addrp, addrlen)) {
+        errno = EINVAL;
+        goto fail;
+    }
+
+    if (t->dbus == NULL) {
+        dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL);
+
+        if (dbus == NULL) {
+            errno = ECONNRESET;
+            goto fail;
+        }
+        else {
+            t->dbus = dbus;
+
+            if (addr->db_addr != NULL && strcmp(addr->db_addr, ANY_ADDRESS)) {
+                if (!mrp_dbus_acquire_name(t->dbus, addr->db_addr, NULL)) {
+                    errno = EADDRINUSE; /* XXX TODO, should check error... */
+                    goto fail;
+                }
+            }
+        }
+    }
+    else {
+        /* XXX TODO: should check given address against address of the bus */
+    }
+
+    copy_address(&t->local, addr);
+
+    switch (t->mode) {
+    case MRP_TRANSPORT_MODE_DATA:
+        method = TRANSPORT_DATA;
+        cb     = dbus_data_cb;
+        break;
+    case MRP_TRANSPORT_MODE_RAW:
+        method = TRANSPORT_RAW;
+        cb     = dbus_raw_cb;
+        break;
+    case MRP_TRANSPORT_MODE_MSG:
+        method = TRANSPORT_MESSAGE;
+        cb     = dbus_msg_cb;
+        break;
+    default:
+        errno = EPROTOTYPE;
+        goto fail;
+    }
+
+    if (!mrp_dbus_export_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE,
+                                method, cb, t)) {
+        errno = EIO;
+        goto fail;
+    }
+    else {
+        t->bound = TRUE;
+        return TRUE;
+    }
+
+ fail:
+    if (dbus != NULL) {
+        mrp_dbus_unref(dbus);
+        t->dbus = NULL;
+    }
+
+    return FALSE;
+}
+
+
+static int dbus_autobind(mrp_transport_t *mt, mrp_sockaddr_t *addrp)
+{
+    mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addrp;
+    char            astr[MRP_SOCKADDR_SIZE];
+    mrp_sockaddr_t  addr;
+    socklen_t       alen;
+
+    snprintf(astr, sizeof(astr), "dbus:[%s]/auto/%u", a->db_bus, nauto++);
+
+    alen = dbus_resolve(astr, &addr, sizeof(addr), NULL);
+
+    if (alen > 0)
+        return dbus_bind(mt, &addr, alen);
+    else
+        return FALSE;
+}
+
+
+static void dbus_close(mrp_transport_t *mt)
+{
+    dbus_t         *t = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr;
+    const char     *method;
+    int           (*cb)(mrp_dbus_t *, mrp_dbus_msg_t *, void *);
+
+    if (t->bound) {
+        switch (t->mode) {
+        case MRP_TRANSPORT_MODE_DATA:
+            method = TRANSPORT_DATA;
+            cb     = dbus_data_cb;
+            break;
+        case MRP_TRANSPORT_MODE_RAW:
+            method = TRANSPORT_RAW;
+            cb     = dbus_raw_cb;
+            break;
+        default:
+        case MRP_TRANSPORT_MODE_MSG:
+            method = TRANSPORT_MESSAGE;
+            cb     = dbus_msg_cb;
+        }
+
+        addr = (mrp_dbusaddr_t *)&t->local;
+        mrp_dbus_remove_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE,
+                               method, cb, t);
+    }
+
+    if (t->connected && t->remote.db_addr != NULL)
+        mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t);
+
+    mrp_dbus_unref(t->dbus);
+    t->dbus = NULL;
+}
+
+
+static int dbus_msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    mrp_msg_t       *msg;
+
+    MRP_UNUSED(dbus);
+
+    msg = msg_decode(dmsg, &sender_path);
+
+    if (msg != NULL) {
+        sender = mrp_dbus_msg_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvmsg(mt, msg, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvmsgfrom(mt, msg, &addr, alen, mt->user_data);
+                });
+        }
+
+        mrp_msg_unref(msg);
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode message.");
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_data_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    uint16_t         tag;
+    void            *decoded;
+
+    MRP_UNUSED(dbus);
+
+    decoded = data_decode(dmsg, &tag, &sender_path);
+
+    if (decoded != NULL) {
+        sender = mrp_dbus_msg_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvdata(mt, decoded, tag, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvdatafrom(mt, decoded, tag, &addr, alen,
+                                         mt->user_data);
+                });
+        }
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode custom data message.");
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_raw_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    void            *data;
+    size_t           size;
+
+    MRP_UNUSED(dbus);
+
+    data = raw_decode(dmsg, &size, &sender_path);
+
+    if (data != NULL) {
+        sender = mrp_dbus_msg_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvraw(mt, data, size, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvrawfrom(mt, data, size, &addr, alen,
+                                        mt->user_data);
+                });
+        }
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode raw message.");
+    }
+
+    return TRUE;
+}
+
+
+static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data)
+{
+    dbus_t         *t = (dbus_t *)user_data;
+    mrp_sockaddr_t  addr;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(name);
+
+    if (up) {
+        peer_address(&addr, owner, t->remote.db_path);
+        copy_address(&t->remote, (mrp_dbusaddr_t *)&addr);
+        t->peer_resolved = TRUE;
+    }
+    else {
+        /*
+         * XXX TODO:
+         *     It would be really tempting here to call
+         *         mt->evt.closed(mt, ECONNRESET, mt->user_data)
+         *     to notify the user about the fact our peer went down.
+         *     However, that would not be in line with the other
+         *     transports which call the closed event handler only
+         *     upon foricble transport closes upon errors.
+         *
+         *     The transport interface abstraction (especially the
+         *     available set of events) anyway needs some eyeballing,
+         *     so the right thing to do might be to define a new event
+         *     for disconnection and call the handler for that here...
+         */
+    }
+
+}
+
+
+static int dbus_connect(mrp_transport_t *mt, mrp_sockaddr_t *addrp,
+                        socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+
+    if (!check_address(addrp, addrlen)) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    if (t->dbus == NULL) {
+        t->dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL);
+
+        if (t->dbus == NULL) {
+            errno = ECONNRESET;
+            return FALSE;
+        }
+    }
+    else {
+        /* XXX TODO: check given address against address of the bus */
+    }
+
+    if (!t->bound)
+        if (!dbus_autobind(mt, addrp))
+            return FALSE;
+
+    if (mrp_dbus_follow_name(t->dbus, addr->db_addr, peer_state_cb, t)) {
+        copy_address(&t->remote, addr);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int dbus_disconnect(mrp_transport_t *mt)
+{
+    dbus_t *t = (dbus_t *)mt;
+
+    if (t->connected && t->remote.db_addr != NULL) {
+        mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t);
+        mrp_clear(&t->remote);
+        t->peer_resolved = FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_sendmsgto(mrp_transport_t *mt, mrp_msg_t *msg,
+                          mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    mrp_dbus_msg_t *m;
+    int             success;
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+                return FALSE;
+
+        m = msg_encode(t->dbus, addr->db_addr, addr->db_path,
+                       TRANSPORT_INTERFACE, TRANSPORT_MESSAGE,
+                       t->local.db_path, msg);
+
+        if (m != NULL) {
+            if (mrp_dbus_send_msg(t->dbus, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            mrp_dbus_msg_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_sendmsg(mrp_transport_t *mt, mrp_msg_t *msg)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_sendmsgto(mt, msg, addr, alen);
+}
+
+
+static int dbus_sendrawto(mrp_transport_t *mt, void *data, size_t size,
+                          mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    mrp_dbus_msg_t *m;
+    int             success;
+
+
+    MRP_UNUSED(mt);
+    MRP_UNUSED(data);
+    MRP_UNUSED(size);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+            return FALSE;
+
+        m = raw_encode(t->dbus, addr->db_addr, addr->db_path,
+                       TRANSPORT_INTERFACE, TRANSPORT_RAW,
+                       t->local.db_path, data, size);
+
+        if (m != NULL) {
+            if (mrp_dbus_send_msg(t->dbus, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            mrp_dbus_msg_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_sendraw(mrp_transport_t *mt, void *data, size_t size)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_sendrawto(mt, data, size, addr, alen);
+}
+
+
+static int dbus_senddatato(mrp_transport_t *mt, void *data, uint16_t tag,
+                           mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    mrp_dbus_msg_t *m;
+    int             success;
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+            return FALSE;
+
+        m = data_encode(t->dbus, addr->db_addr, addr->db_path,
+                        TRANSPORT_INTERFACE, TRANSPORT_DATA,
+                        t->local.db_path, data, tag);
+
+        if (m != NULL) {
+            if (mrp_dbus_send_msg(t->dbus, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            mrp_dbus_msg_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_senddata(mrp_transport_t *mt, void *data, uint16_t tag)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_senddatato(mt, data, tag, addr, alen);
+}
+
+
+static const char *get_array_signature(uint16_t type)
+{
+#define MAP(from, to)                                 \
+    case MRP_MSG_FIELD_##from:                        \
+        return MRP_DBUS_TYPE_##to##_AS_STRING;
+
+    switch (type) {
+        MAP(STRING, STRING);
+        MAP(BOOL  , BOOLEAN);
+        MAP(UINT8 , UINT16);
+        MAP(SINT8 , INT16);
+        MAP(UINT16, UINT16);
+        MAP(SINT16, INT16);
+        MAP(UINT32, UINT32);
+        MAP(SINT32, INT32);
+        MAP(UINT64, UINT64);
+        MAP(SINT64, INT64);
+        MAP(DOUBLE, DOUBLE);
+        MAP(BLOB  , BYTE);
+    default:
+        return NULL;
+    }
+}
+
+
+static mrp_dbus_msg_t *msg_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  mrp_msg_t *msg)
+{
+    /*
+     * Notes: There is a type mismatch between our and DBUS types for
+     *        8-bit integers (D-BUS does not have a signed 8-bit type)
+     *        and boolean types (D-BUS has uint32_t booleans, C99 fails
+     *        to specify the type and gcc uses a signed char). The
+     *        QUIRKY versions of the macros take care of these mismatches.
+     */
+
+#define BASIC_SIMPLE(_i, _mtype, _dtype, _val)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = &(_val);                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(_i, type, vptr))               \
+                goto fail;                                                \
+            break
+
+#define BASIC_STRING(_i, _mtype, _dtype, _val)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = (_val);                                                \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(_i, type, vptr))               \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_i, _mtype, _dtype, _mval, _dval)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type  = MRP_DBUS_TYPE_##_dtype;                               \
+            _dval = _mval;                                                \
+            vptr  = &_dval;                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(_i, type, vptr))               \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_i, _mtype, _dtype, _val)                            \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type = MRP_DBUS_TYPE_##_dtype;                        \
+                    vptr = &_val;                                         \
+                                                                          \
+                    if (!mrp_dbus_msg_append_basic(_i, type, vptr))       \
+                        goto fail;                                        \
+                    break
+
+#define ARRAY_STRING(_i, _mtype, _dtype, _val)                            \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type = MRP_DBUS_TYPE_##_dtype;                        \
+                    vptr = _val;                                          \
+                                                                          \
+                    if (!mrp_dbus_msg_append_basic(_i, type, vptr))       \
+                        goto fail;                                        \
+                    break
+
+#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type  = MRP_DBUS_TYPE_##_dtype;                       \
+                    _dvar = _mvar;                                        \
+                    vptr  = &_dvar;                                       \
+                                                                          \
+                    if (!mrp_dbus_msg_append_basic(_i, type, vptr))       \
+                        goto fail;                                        \
+                    break
+
+    mrp_dbus_msg_t  *m;
+    mrp_list_hook_t *p, *n;
+    mrp_msg_field_t *f;
+    uint16_t         base;
+    uint32_t         asize, i;
+    const char      *sig;
+    int              type, len;
+    void            *vptr;
+    uint32_t         bln;
+    uint16_t         u16, blb;
+    int16_t          s16;
+
+    m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member);
+
+    if (m == NULL)
+        return NULL;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH,
+                                   (void *)sender_id))
+        goto fail;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &msg->nfield))
+        goto fail;
+
+    mrp_list_foreach(&msg->fields, p, n) {
+        f = mrp_list_entry(p, typeof(*f), hook);
+
+        if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) ||
+            !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type))
+            goto fail;
+
+        switch (f->type) {
+            BASIC_STRING(m, STRING, STRING , f->str);
+            BASIC_QUIRKY(m, BOOL  , BOOLEAN, f->bln, bln);
+            BASIC_QUIRKY(m, UINT8 , UINT16 , f->u8 , u16);
+            BASIC_QUIRKY(m, SINT8 ,  INT16 , f->s8 , s16);
+            BASIC_SIMPLE(m, UINT16, UINT16 , f->u16);
+            BASIC_SIMPLE(m, SINT16,  INT16 , f->s16);
+            BASIC_SIMPLE(m, UINT32, UINT32 , f->u32);
+            BASIC_SIMPLE(m, SINT32,  INT32 , f->s32);
+            BASIC_SIMPLE(m, UINT64, UINT64 , f->u64);
+            BASIC_SIMPLE(m, SINT64,  INT64 , f->s64);
+            BASIC_SIMPLE(m, DOUBLE, DOUBLE , f->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            vptr  = f->blb;
+            len   = (int)f->size[0];
+            sig   = get_array_signature(f->type);
+            asize = len;
+            if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize))
+                goto fail;
+
+            if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, NULL))
+                goto fail;
+
+            for (i = 0; i < asize; i++) {
+                blb = ((uint8_t *)f->blb)[i];
+                if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &blb))
+                    goto fail;
+            }
+
+            if (!mrp_dbus_msg_close_container(m))
+                goto fail;
+            break;
+
+        default:
+            if (f->type & MRP_MSG_FIELD_ARRAY) {
+                base  = f->type & ~(MRP_MSG_FIELD_ARRAY);
+                asize = f->size[0];
+                sig   = get_array_signature(base);
+
+                if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize))
+                    goto fail;
+
+                if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                    goto fail;
+
+                for (i = 0; i < asize; i++) {
+                    switch (base) {
+                        ARRAY_STRING(m, STRING, STRING , f->astr[i]);
+                        ARRAY_QUIRKY(m, BOOL  , BOOLEAN, f->abln[i], bln);
+                        ARRAY_QUIRKY(m, UINT8 , UINT16 , f->au8[i] , u16);
+                        ARRAY_QUIRKY(m, SINT8 ,  INT16 , f->as8[i] , s16);
+                        ARRAY_SIMPLE(m, UINT16, UINT16 , f->au16[i]);
+                        ARRAY_SIMPLE(m, SINT16,  INT16 , f->as16[i]);
+                        ARRAY_SIMPLE(m, UINT32, UINT32 , f->au32[i]);
+                        ARRAY_SIMPLE(m, SINT32,  INT32 , f->as32[i]);
+                        ARRAY_SIMPLE(m, UINT64, UINT64 , f->au64[i]);
+                        ARRAY_SIMPLE(m, DOUBLE, DOUBLE , f->adbl[i]);
+
+                    case MRP_MSG_FIELD_BLOB:
+                        goto fail;
+
+                    default:
+                        goto fail;
+                    }
+                }
+
+                if (!mrp_dbus_msg_close_container(m))
+                    goto fail;
+            }
+            else
+                goto fail;
+        }
+    }
+
+    return m;
+
+ fail:
+    if (m != NULL)
+        mrp_dbus_msg_unref(m);
+
+    errno = ECOMM;
+
+    return FALSE;
+
+#undef BASIC_SIMPLE
+#undef BASIC_STRING
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_STRING
+#undef ARRAY_QUIRKY
+}
+
+
+static mrp_msg_t *msg_decode(mrp_dbus_msg_t *m, const char **sender_id)
+{
+#define BASIC_SIMPLE(_i, _mtype, _dtype, _var)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_var)))                        \
+                goto fail;                                                \
+                                                                          \
+            if (!mrp_msg_append(msg, tag, type, (_var)))                  \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_dvar)))                       \
+                goto fail;                                                \
+                                                                          \
+            _mvar = _dvar;                                                \
+            if (!mrp_msg_append(msg, tag, type, (_mvar)))                 \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_i, _mtype, _dtype, _var)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_var)))                        \
+                goto fail;                                                \
+            break
+
+#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(_i, MRP_DBUS_TYPE_##_dtype,      \
+                                         &(_dvar)))                       \
+                goto fail;                                                \
+                                                                          \
+            _mvar = _dvar;                                                \
+            break
+
+#define APPEND_ARRAY(_type, _var)                                         \
+                case MRP_MSG_FIELD_##_type:                               \
+                    if (!mrp_msg_append(msg, tag,                         \
+                                        MRP_MSG_FIELD_ARRAY |             \
+                                        MRP_MSG_FIELD_##_type,            \
+                                        n, _var))                         \
+                        goto fail;                                        \
+                    break
+
+    mrp_msg_t       *msg;
+    mrp_msg_value_t  v;
+    uint16_t         u16;
+    int16_t          s16;
+    uint32_t         u32;
+    uint16_t         nfield, tag, type, base, i;
+    uint32_t         n, j;
+    int              asize;
+    const char      *sender, *sig;
+
+    msg = NULL;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender))
+        goto fail;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    msg = mrp_msg_create_empty();
+
+    if (msg == NULL)
+        goto fail;
+
+    for (i = 0; i < nfield; i++) {
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+            goto fail;
+
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type))
+            goto fail;
+
+        switch (type) {
+            BASIC_SIMPLE(m, STRING, STRING , v.str);
+            BASIC_QUIRKY(m, BOOL  , BOOLEAN, v.bln, u32);
+            BASIC_QUIRKY(m, UINT8 , UINT16 , v.u8 , u16);
+            BASIC_QUIRKY(m, SINT8 ,  INT16 , v.s8 , s16);
+            BASIC_SIMPLE(m, UINT16, UINT16 , v.u16);
+            BASIC_SIMPLE(m, SINT16,  INT16 , v.s16);
+            BASIC_SIMPLE(m, UINT32, UINT32 , v.u32);
+            BASIC_SIMPLE(m, SINT32,  INT32 , v.s32);
+            BASIC_SIMPLE(m, UINT64, UINT64 , v.u64);
+            BASIC_SIMPLE(m, SINT64,  INT64 , v.s64);
+            BASIC_SIMPLE(m, DOUBLE, DOUBLE , v.dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            {
+                uint8_t blb[n];
+
+                if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL))
+                    goto fail;
+
+                for (j = 0; j < n; j++) {
+                    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE,
+                                                 blb + j))
+                        goto fail;
+                }
+
+                if (!mrp_dbus_msg_exit_container(m))
+                    goto fail;
+
+                asize = n;
+                if (!mrp_msg_append(msg, tag, type, asize, blb))
+                    goto fail;
+            }
+            break;
+
+        default:
+            if (!(type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = type & ~(MRP_MSG_FIELD_ARRAY);
+
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            sig = get_array_signature(base);
+            if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            {
+                char     *astr[n];
+                uint32_t  dbln[n];
+                bool      abln[n];
+                uint8_t   au8 [n];
+                int8_t    as8 [n];
+                uint16_t  au16[n];
+                int16_t   as16[n];
+                uint32_t  au32[n];
+                int32_t   as32[n];
+                uint64_t  au64[n];
+                int64_t   as64[n];
+                double    adbl[n];
+
+                for (j = 0; j < n; j++) {
+                    switch (base) {
+                        ARRAY_SIMPLE(m, STRING, STRING , astr[j]);
+                        ARRAY_QUIRKY(m, BOOL  , BOOLEAN, abln[j], dbln[j]);
+                        ARRAY_QUIRKY(m, UINT8 , UINT16 , au8[j] , au16[j]);
+                        ARRAY_QUIRKY(m, SINT8 ,  INT16 , as8[j] , as16[j]);
+                        ARRAY_SIMPLE(m, UINT16, UINT16 , au16[j]);
+                        ARRAY_SIMPLE(m, SINT16,  INT16 , as16[j]);
+                        ARRAY_SIMPLE(m, UINT32, UINT32 , au32[j]);
+                        ARRAY_SIMPLE(m, SINT32,  INT32 , as32[j]);
+                        ARRAY_SIMPLE(m, UINT64, UINT64 , au64[j]);
+                        ARRAY_SIMPLE(m, SINT64,  INT64 , as64[j]);
+                        ARRAY_SIMPLE(m, DOUBLE, DOUBLE , adbl[j]);
+                    default:
+                        goto fail;
+                    }
+                }
+
+                switch (base) {
+                    APPEND_ARRAY(STRING, astr);
+                    APPEND_ARRAY(BOOL  , abln);
+                    APPEND_ARRAY(UINT8 , au8 );
+                    APPEND_ARRAY(SINT8 , as8 );
+                    APPEND_ARRAY(UINT16, au16);
+                    APPEND_ARRAY(SINT16, as16);
+                    APPEND_ARRAY(UINT32, au32);
+                    APPEND_ARRAY(SINT32, as32);
+                    APPEND_ARRAY(UINT64, au64);
+                    APPEND_ARRAY(SINT64, as64);
+                    APPEND_ARRAY(DOUBLE, adbl);
+                default:
+                    goto fail;
+                }
+            }
+
+            if (!mrp_dbus_msg_exit_container(m))
+                goto fail;
+        }
+    }
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return msg;
+
+ fail:
+    mrp_msg_unref(msg);
+    errno = EBADMSG;
+
+    return NULL;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+#undef APPEND_ARRAY
+}
+
+
+static mrp_dbus_msg_t *data_encode(mrp_dbus_t *dbus, const char *destination,
+                                   const char *path, const char *interface,
+                                   const char *member, const char *sender_id,
+                                   void *data, uint16_t tag)
+{
+#define BASIC_SIMPLE(_mtype, _dtype, _val)                                \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = &(_val);                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define BASIC_STRING(_mtype, _dtype, _val)                                \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = _val;                                                  \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_mtype, _dtype, _mval, _dval)                        \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type  = MRP_DBUS_TYPE_##_dtype;                               \
+            _dval = _mval;                                                \
+            vptr  = &_dval;                                               \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_mtype, _dtype, _val)                                \
+         case MRP_MSG_FIELD_##_mtype:                                     \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = &_val;                                                 \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define ARRAY_STRING(_mtype, _dtype, _val)                                \
+         case MRP_MSG_FIELD_##_mtype:                                     \
+            type = MRP_DBUS_TYPE_##_dtype;                                \
+            vptr = _val;                                                  \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+#define ARRAY_QUIRKY(_mtype, _dtype, _mvar, _dvar)                        \
+         case MRP_MSG_FIELD_##_mtype:                                     \
+            type  = MRP_DBUS_TYPE_##_dtype;                               \
+            _dvar = _mvar;                                                \
+            vptr = &_dvar;                                                \
+                                                                          \
+            if (!mrp_dbus_msg_append_basic(m, type, vptr))                \
+                goto fail;                                                \
+            break
+
+    mrp_dbus_msg_t    *m;
+    mrp_data_descr_t  *descr;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           type, base;
+    mrp_msg_value_t   *v;
+    void              *vptr;
+    uint32_t           n, j;
+    int                i, blblen;
+    const char        *sig;
+    uint16_t           u16;
+    int16_t            s16;
+    uint32_t           bln, asize;
+
+    m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member);
+
+    if (m == NULL)
+        return NULL;
+
+    descr = mrp_msg_find_type(tag);
+
+    if (descr == NULL)
+        goto fail;
+
+    fields = descr->fields;
+    nfield = descr->nfield;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH,
+                                   (void *)sender_id))
+        goto fail;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+        goto fail;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    for (i = 0, f = fields; i < nfield; i++, f++) {
+        if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->tag) ||
+            !mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT16, &f->type))
+            goto fail;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (f->type) {
+            BASIC_STRING(STRING, STRING , v->str);
+            BASIC_QUIRKY(BOOL  , BOOLEAN, v->bln, bln);
+            BASIC_QUIRKY(UINT8 , UINT16 , v->u8 , u16);
+            BASIC_QUIRKY(SINT8 ,  INT16 , v->s8 , s16);
+            BASIC_SIMPLE(UINT16, UINT16 , v->u16);
+            BASIC_SIMPLE(SINT16,  INT16 , v->s16);
+            BASIC_SIMPLE(UINT32, UINT32 , v->u32);
+            BASIC_SIMPLE(SINT32,  INT32 , v->s32);
+            BASIC_SIMPLE(UINT64, UINT64 , v->u64);
+            BASIC_SIMPLE(SINT64,  INT64 , v->s64);
+            BASIC_SIMPLE(DOUBLE, DOUBLE , v->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            sig    = get_array_signature(f->type);
+            blblen = mrp_data_get_blob_size(data, descr, i);
+            asize  = blblen;
+
+            if (blblen == -1)
+                goto fail;
+
+            if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &asize))
+                goto fail;
+
+            if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            for (i = 0; i < blblen; i++)
+                if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE,
+                                               f->blb + i))
+                    goto fail;
+
+            if (!mrp_dbus_msg_close_container(m))
+                goto fail;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = f->type & ~(MRP_MSG_FIELD_ARRAY);
+            n    = mrp_data_get_array_size(data, descr, i);
+            sig  = get_array_signature(base);
+
+            if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            for (j = 0; j < n; j++) {
+                switch (base) {
+                    ARRAY_STRING(STRING, STRING , v->astr[j]);
+                    ARRAY_QUIRKY(BOOL  , BOOLEAN, v->abln[j], bln);
+                    ARRAY_QUIRKY(UINT8 , UINT16 , v->au8[j] , u16);
+                    ARRAY_QUIRKY(SINT8 ,  INT16 , v->as8[j] , s16);
+                    ARRAY_SIMPLE(UINT16, UINT16 , v->au16[j]);
+                    ARRAY_SIMPLE(SINT16,  INT16 , v->as16[j]);
+                    ARRAY_SIMPLE(UINT32, UINT32 , v->au32[j]);
+                    ARRAY_SIMPLE(SINT32,  INT32 , v->as32[j]);
+                    ARRAY_SIMPLE(UINT64, UINT64 , v->au64[j]);
+                    ARRAY_SIMPLE(DOUBLE, DOUBLE , v->adbl[j]);
+
+                case MRP_MSG_FIELD_BLOB:
+                    goto fail;
+
+                default:
+                    goto fail;
+                }
+            }
+
+            if (!mrp_dbus_msg_close_container(m))
+                goto fail;
+        }
+    }
+
+    return m;
+
+ fail:
+    if (m != NULL)
+        mrp_dbus_msg_unref(m);
+
+    errno = ECOMM;
+
+    return NULL;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+}
+
+
+static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield,
+                                      uint16_t tag)
+{
+    mrp_data_member_t *f;
+    int                i;
+
+    for (i = 0, f = fields; i < nfield; i++, f++)
+        if (f->tag == tag)
+            return f;
+
+    return NULL;
+}
+
+
+static void *data_decode(mrp_dbus_msg_t *m, uint16_t *tagp,
+                         const char **sender_id)
+{
+#define HANDLE_SIMPLE(_i, _mtype, _dtype, _var)                           \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype,       \
+                                         &(_var)))                        \
+                goto fail;                                                \
+            break
+
+#define HANDLE_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                   \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_##_dtype,       \
+                                         &(_dvar)))                       \
+                goto fail;                                                \
+                                                                          \
+            _mvar = _dvar;                                                \
+            break
+
+    void              *data;
+    mrp_data_descr_t  *descr;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           tag, type, base;
+    mrp_msg_value_t   *v;
+    uint32_t           n, j, size;
+    int                i;
+    const char        *sender, *sig;
+    uint32_t           u32;
+    uint16_t           u16;
+    int16_t            s16;
+
+    tag  = 0;
+    data = NULL;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender))
+        goto fail;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+        goto fail;
+
+    descr = mrp_msg_find_type(tag);
+
+    if (descr == NULL)
+        goto fail;
+
+    *tagp = tag;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    if (nfield != descr->nfield)
+        goto fail;
+
+    fields = descr->fields;
+    data   = mrp_allocz(descr->size);
+
+    if (MRP_UNLIKELY(data == NULL))
+        goto fail;
+
+    for (i = 0; i < nfield; i++) {
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &tag))
+            goto fail;
+
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT16, &type))
+            goto fail;
+
+        f = member_type(fields, nfield, tag);
+
+        if (MRP_UNLIKELY(f == NULL))
+            goto fail;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (type) {
+            HANDLE_SIMPLE(&im, STRING, STRING , v->str);
+            HANDLE_QUIRKY(&im, BOOL  , BOOLEAN, v->bln, u32);
+            HANDLE_QUIRKY(&im, UINT8 , UINT16 , v->u8 , u16);
+            HANDLE_QUIRKY(&im, SINT8 ,  INT16 , v->s8 , s16);
+            HANDLE_SIMPLE(&im, UINT16, UINT16 , v->u16);
+            HANDLE_SIMPLE(&im, SINT16,  INT16 , v->s16);
+            HANDLE_SIMPLE(&im, UINT32, UINT32 , v->u32);
+            HANDLE_SIMPLE(&im, SINT32,  INT32 , v->s32);
+            HANDLE_SIMPLE(&im, UINT64, UINT64 , v->u64);
+            HANDLE_SIMPLE(&im, SINT64,  INT64 , v->s64);
+            HANDLE_SIMPLE(&im, DOUBLE, DOUBLE , v->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &size))
+                goto fail;
+
+            sig = MRP_DBUS_TYPE_BYTE_AS_STRING;
+
+            if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+                goto fail;
+
+            {
+                uint8_t blb[size];
+
+                for (j = 0; j < size; j++)
+                    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE,
+                                                 blb + j))
+                        goto fail;
+
+                v->blb = mrp_alloc(size);
+
+                if (v->blb == NULL && size != 0)
+                    goto fail;
+
+                memcpy(v->blb, blb, size);
+            }
+
+            if (!mrp_dbus_msg_exit_container(m))
+                goto fail;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = type & ~(MRP_MSG_FIELD_ARRAY);
+
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, NULL))
+                goto fail;
+
+            size = n;
+
+            switch (base) {
+            case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break;
+            case MRP_MSG_FIELD_BOOL:   size *= sizeof(*v->abln); break;
+            case MRP_MSG_FIELD_UINT8:  size *= sizeof(*v->au8);  break;
+            case MRP_MSG_FIELD_SINT8:  size *= sizeof(*v->as8);  break;
+            case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break;
+            case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break;
+            case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break;
+            case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break;
+            case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break;
+            case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break;
+            case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break;
+            default:
+                goto fail;
+            }
+
+            v->aany = mrp_allocz(size);
+            if (v->aany == NULL)
+                goto fail;
+
+            for (j = 0; j < n; j++) {
+                uint32_t  dbln[n];
+                uint16_t  au16[n];
+                int16_t   as16[n];
+
+                switch (base) {
+                    HANDLE_SIMPLE(&ia, STRING, STRING , v->astr[j]);
+                    HANDLE_QUIRKY(&ia, BOOL  , BOOLEAN, v->abln[j], dbln[j]);
+                    HANDLE_QUIRKY(&ia, UINT8 , UINT16 , v->au8[j] , au16[j]);
+                    HANDLE_QUIRKY(&ia, SINT8 ,  INT16 , v->as8[j] , as16[j]);
+                    HANDLE_SIMPLE(&ia, UINT16, UINT16 , v->au16[j]);
+                    HANDLE_SIMPLE(&ia, SINT16,  INT16 , v->as16[j]);
+                    HANDLE_SIMPLE(&ia, UINT32, UINT32 , v->au32[j]);
+                    HANDLE_SIMPLE(&ia, SINT32,  INT32 , v->as32[j]);
+                    HANDLE_SIMPLE(&ia, UINT64, UINT64 , v->au64[j]);
+                    HANDLE_SIMPLE(&ia, SINT64,  INT64 , v->as64[j]);
+                    HANDLE_SIMPLE(&ia, DOUBLE, DOUBLE , v->adbl[j]);
+                }
+
+                if (base == MRP_MSG_FIELD_STRING) {
+                    v->astr[j] = mrp_strdup(v->astr[j]);
+                    if (v->astr[j] == NULL)
+                        goto fail;
+                }
+            }
+
+            if (!mrp_dbus_msg_exit_container(m))
+                goto fail;
+        }
+
+        if (f->type == MRP_MSG_FIELD_STRING) {
+            v->str = mrp_strdup(v->str);
+            if (v->str == NULL)
+                goto fail;
+        }
+    }
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return data;
+
+ fail:
+    mrp_data_free(data, tag);
+    errno = EBADMSG;
+
+    return NULL;
+}
+
+
+static mrp_dbus_msg_t *raw_encode(mrp_dbus_t *dbus, const char *destination,
+                                  const char *path, const char *interface,
+                                  const char *member, const char *sender_id,
+                                  void *data, size_t size)
+{
+    mrp_dbus_msg_t *m;
+    const char     *sig;
+    uint32_t        i, n;
+
+    m = mrp_dbus_msg_method_call(dbus, destination, path, interface, member);
+
+    if (m == NULL)
+        return NULL;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_OBJECT_PATH,
+                                   (void *)sender_id))
+        goto fail;
+
+    sig = MRP_DBUS_TYPE_BYTE_AS_STRING;
+    n   = size;
+
+    if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+        goto fail;
+
+    if (!mrp_dbus_msg_open_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+        goto fail;
+
+    for (i = 0; i < n; i++)
+        if (!mrp_dbus_msg_append_basic(m, MRP_DBUS_TYPE_BYTE, data + i))
+            goto fail;
+
+    if (!mrp_dbus_msg_close_container(m))
+        goto fail;
+
+    return m;
+
+ fail:
+    mrp_dbus_msg_unref(m);
+
+    errno = ECOMM;
+
+    return NULL;
+}
+
+
+static void *raw_decode(mrp_dbus_msg_t *m, size_t *sizep,
+                        const char **sender_id)
+{
+    const char *sender, *sig;
+    void       *data;
+    uint32_t    n, i;
+
+    data = NULL;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_OBJECT_PATH, &sender))
+        goto fail;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_UINT32, &n))
+        goto fail;
+
+    sig = MRP_DBUS_TYPE_BYTE_AS_STRING;
+
+    if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sig))
+        goto fail;
+
+    {
+        uint8_t databuf[n];
+
+        for (i = 0; i < n; i++)
+            if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_BYTE, databuf + i))
+                goto fail;
+
+        data = mrp_alloc(n);
+
+        if (data == NULL && n != 0)
+            goto fail;
+
+        memcpy(data, databuf, n);
+    }
+
+    if (!mrp_dbus_msg_exit_container(m))
+        goto fail;
+
+    if (sizep != NULL)
+        *sizep = (size_t)n;
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return data;
+
+ fail:
+    errno = EBADMSG;
+
+    return NULL;
+}
+
+
+MRP_REGISTER_TRANSPORT(dbus, DBUS, dbus_t, dbus_resolve,
+                       dbus_open, dbus_createfrom, dbus_close, NULL,
+                       dbus_bind, NULL, NULL,
+                       dbus_connect, dbus_disconnect,
+                       dbus_sendmsg, dbus_sendmsgto,
+                       dbus_sendraw, dbus_sendrawto,
+                       dbus_senddata, dbus_senddatato,
+                       NULL, NULL,
+                       NULL, NULL);
diff --git a/src/common/dbus-sdbus.c b/src/common/dbus-sdbus.c
new file mode 100644 (file)
index 0000000..a2fb9e9
--- /dev/null
@@ -0,0 +1,1955 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/refcnt.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/dbus-sdbus.h>
+
+#define BUS_SERVICE      "org.freedesktop.DBus"
+#define BUS_PATH         "/org/freedesktop/DBus"
+#define BUS_INTERFACE    "org.freedesktop.DBus"
+#define BUS_NAME_CHANGED "NameOwnerChanged"
+#define BUS_GET_OWNER    "GetNameOwner"
+
+/* XXX check these... */
+#define SDBUS_ERROR_FAILED   "org.DBus.error.failed"
+
+/* D-Bus name request flags and reply statuses. */
+#define SDBUS_NAME_REQUEST_REPLACE 0x2
+#define SDBUS_NAME_REQUEST_DONTQ   0x4
+
+#define SDBUS_NAME_STATUS_OWNER    0x1
+#define SDBUS_NAME_STATUS_QUEUING  0x2
+#define SDBUS_NAME_STATUS_EXISTS   0x3
+#define SDBUS_NAME_STATUS_GOTIT    0x4
+
+#define SDBUS_NAME_STATUS_RELEASED 0x1
+#define SDBUS_NAME_STATUS_UNKNOWN  0x2
+#define SDBUS_NAME_STATUS_FOREIGN  0x3
+
+#define USEC_TO_MSEC(usec) ((unsigned int)((usec) / 1000))
+#define MSEC_TO_USEC(msec) ((uint64_t)(msec) * 1000)
+
+struct mrp_dbus_s {
+    char            *address;            /* bus address */
+    sd_bus          *bus;                /* actual D-BUS connection */
+    mrp_mainloop_t  *ml;                 /* murphy mainloop */
+    mrp_subloop_t   *sl;                 /* subloop for pumping the bus */
+    mrp_htbl_t      *objects;            /* object path (refcount) table */
+    mrp_htbl_t      *methods;            /* method handler table */
+    mrp_htbl_t      *signals;            /* signal handler table */
+    mrp_list_hook_t  name_trackers;      /* peer (name) watchers */
+    mrp_list_hook_t  calls;              /* pending calls */
+    uint32_t         call_id;            /* next call id */
+    const char      *unique_name;        /* our unique D-BUS address */
+    int              priv;               /* whether a private connection */
+    int              signal_filter;      /* if signal dispatching is set up */
+    int              register_fallback;  /* if the fallback object is set up */
+    mrp_refcnt_t     refcnt;             /* reference count */
+};
+
+
+struct mrp_dbus_msg_s {
+    sd_bus_message  *msg;                /* actual D-Bus message */
+    mrp_refcnt_t     refcnt;             /* reference count */
+    mrp_list_hook_t  arrays;             /* implicitly freed related arrays */
+};
+
+
+typedef struct {
+    mrp_dbus_type_t   type;
+    mrp_list_hook_t   hook;
+    void             *items;
+    size_t            nitem;
+} msg_array_t;
+
+/*
+ * Notes:
+ *
+ * At the moment we administer DBUS method and signal handlers
+ * in a very primitive way (subject to be changed later). For
+ * every bus instance we maintain two hash tables, one for methods
+ * and another for signals. Each method and signal handler is
+ * hashed in only by it's method/signal name to a linked list of
+ * method or signal handlers.
+ *
+ * When dispatching a method, we look up the chain with a matching
+ * method name, or the chain for "" in case a matching chain is
+ * not found, and invoke the handler which best matches the
+ * received message (by looking at the path, interface and name).
+ * Only one such handler is invoked at most.
+ *
+ * For signals we look up both the chain with a matching name and
+ * the chain for "" and invoke all signal handlers that match the
+ * received message (regardless of their return value).
+ */
+
+typedef struct {
+    char *path;                         /* object path */
+    int   cnt;                          /* reference count */
+} object_t;
+
+typedef struct {
+    char            *member;            /* signal/method name */
+    mrp_list_hook_t  handlers;          /* handlers with matching member */
+} handler_list_t;
+
+typedef struct {
+    mrp_list_hook_t     hook;
+    char               *sender;
+    char               *path;
+    char               *interface;
+    char               *member;
+    mrp_dbus_handler_t  handler;
+    void               *user_data;
+} handler_t;
+
+#define method_t handler_t
+#define signal_t handler_t
+
+
+typedef struct {
+    mrp_list_hook_t     hook;           /* hook to name tracker list */
+    char               *name;           /* name to track */
+    mrp_dbus_name_cb_t  cb;             /* status change callback */
+    void               *user_data;      /* opaque callback user data */
+    int32_t             qid;            /* initial query ID */
+} name_tracker_t;
+
+
+typedef struct {
+    mrp_dbus_t          *dbus;           /* DBUS connection */
+    int32_t              id;             /* call id */
+    mrp_dbus_reply_cb_t  cb;             /* completion notification callback */
+    void                *user_data;      /* opaque callback data */
+    uint64_t             serial;         /* DBUS call */
+    mrp_list_hook_t      hook;           /* hook to list of pending calls */
+    sd_bus_message      *msg;            /* original message */
+} call_t;
+
+
+typedef struct {
+    mrp_mainloop_t *ml;                  /* mainloop for bus connection */
+    const char     *address;             /* address of bus */
+} bus_spec_t;
+
+static mrp_htbl_t *buses;
+
+
+
+static int dispatch_signal(sd_bus *b, int r, sd_bus_message *msg, void *data);
+static int dispatch_method(sd_bus *b, int r, sd_bus_message *msg, void *data);
+
+static void purge_name_trackers(mrp_dbus_t *dbus);
+static void purge_calls(mrp_dbus_t *dbus);
+static void handler_list_free_cb(void *key, void *entry);
+static void handler_free(handler_t *h);
+static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m,
+                                void *data);
+static void call_free(call_t *call);
+static void object_free_cb(void *key, void *entry);
+
+
+static int purge_objects(void *key, void *entry, void *user_data)
+{
+    mrp_dbus_t *dbus = (mrp_dbus_t *)user_data;
+    object_t   *o    = (object_t *)entry;
+
+    MRP_UNUSED(key);
+
+    sd_bus_remove_object(dbus->bus, o->path, dispatch_method, dbus);
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static int purge_filters(void *key, void *entry, void *user_data)
+{
+    mrp_dbus_t      *dbus = (mrp_dbus_t *)user_data;
+    handler_list_t  *l    = (handler_list_t *)entry;
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    MRP_UNUSED(key);
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+        mrp_dbus_remove_filter(dbus,
+                               h->sender, h->path, h->interface,
+                               h->member, NULL);
+    }
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static void dbus_disconnect(mrp_dbus_t *dbus)
+{
+    if (dbus) {
+        mrp_htbl_remove(buses, dbus->bus, FALSE);
+
+        if (dbus->objects) {
+            mrp_htbl_foreach(dbus->signals, purge_objects, dbus);
+            mrp_htbl_destroy(dbus->objects, TRUE);
+        }
+
+        if (dbus->signals) {
+            mrp_htbl_foreach(dbus->signals, purge_filters, dbus);
+            mrp_htbl_destroy(dbus->signals, TRUE);
+        }
+        if (dbus->methods)
+            mrp_htbl_destroy(dbus->methods, TRUE);
+
+        purge_name_trackers(dbus);
+        purge_calls(dbus);
+
+        if (dbus->bus != NULL) {
+            if (dbus->signal_filter)
+                sd_bus_remove_filter(dbus->bus, dispatch_signal, dbus);
+            if (dbus->register_fallback)
+                sd_bus_remove_fallback(dbus->bus, "/", dispatch_method, dbus);
+            if (dbus->priv)
+                sd_bus_close(dbus->bus);
+            else
+                sd_bus_unref(dbus->bus);
+        }
+
+        mrp_free(dbus->address);
+        dbus->bus = NULL;
+        dbus->ml  = NULL;
+
+        mrp_free(dbus);
+    }
+}
+
+
+static int bus_cmp(const void *key1, const void *key2)
+{
+    return key2 - key1;
+}
+
+
+static uint32_t bus_hash(const void *key)
+{
+    uint32_t h;
+
+    h   = (ptrdiff_t)key;
+    h >>= 2 * sizeof(key);
+
+    return h;
+}
+
+
+static int find_bus_by_spec(void *key, void *object, void *user_data)
+{
+    mrp_dbus_t *dbus = (mrp_dbus_t *)object;
+    bus_spec_t *spec = (bus_spec_t *)user_data;
+
+    MRP_UNUSED(key);
+
+    if (dbus->ml == spec->ml && !strcmp(dbus->address, spec->address))
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static mrp_dbus_t *dbus_get(mrp_mainloop_t *ml, const char *address)
+{
+    mrp_htbl_config_t hcfg;
+    bus_spec_t        spec;
+
+    if (buses == NULL) {
+        mrp_clear(&hcfg);
+
+        hcfg.comp = bus_cmp;
+        hcfg.hash = bus_hash;
+        hcfg.free = NULL;
+
+        buses = mrp_htbl_create(&hcfg);
+
+        return NULL;
+    }
+    else {
+        spec.ml      = ml;
+        spec.address = address;
+
+        return mrp_htbl_find(buses, find_bus_by_spec, &spec);
+    }
+}
+
+
+mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address,
+                             mrp_dbus_err_t *errp)
+{
+    mrp_htbl_config_t  hcfg;
+    mrp_dbus_t        *dbus;
+
+    if ((dbus = dbus_get(ml, address)) != NULL)
+        return mrp_dbus_ref(dbus);
+
+    if ((dbus = mrp_allocz(sizeof(*dbus))) == NULL)
+        return NULL;
+
+    mrp_list_init(&dbus->calls);
+    mrp_list_init(&dbus->name_trackers);
+    mrp_refcnt_init(&dbus->refcnt);
+
+    dbus->ml = ml;
+
+    mrp_dbus_error_init(errp);
+
+    /*
+     * connect to the bus
+     */
+
+    if (!strcmp(address, "system")) {
+        if (sd_bus_open_system(&dbus->bus) != 0)
+            goto fail;
+    }
+    else if (!strcmp(address, "session")) {
+        if (sd_bus_open_user(&dbus->bus) != 0)
+            goto fail;
+    }
+    else {
+        dbus->priv = TRUE;
+
+        if (sd_bus_new(&dbus->bus) != 0)
+            goto fail;
+        else {
+            if (sd_bus_set_address(dbus->bus, address) != 0)
+                goto fail;
+
+            if (sd_bus_start(dbus->bus) != 0)
+                goto fail;
+        }
+    }
+
+    dbus->address = mrp_strdup(address);
+    if (sd_bus_get_unique_name(dbus->bus, &dbus->unique_name) != 0)
+        goto fail;
+
+    /*
+     * set up with mainloop
+     */
+
+    if (!mrp_dbus_setup_with_mainloop(ml, dbus->bus))
+        goto fail;
+
+    /*
+     * set up our message dispatchers and take our name on the bus
+     */
+
+    if (sd_bus_add_filter(dbus->bus, dispatch_signal, dbus) != 0) {
+        mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED,
+                         "Failed to set up signal dispatching.");
+        goto fail;
+    }
+    dbus->signal_filter = TRUE;
+
+    if (sd_bus_add_fallback(dbus->bus, "/", dispatch_method, dbus) != 0) {
+        mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED,
+                           "Failed to set up method dispatching.");
+        goto fail;
+    }
+    dbus->register_fallback = TRUE;
+
+    mrp_clear(&hcfg);
+    hcfg.comp = mrp_string_comp;
+    hcfg.hash = mrp_string_hash;
+    hcfg.free = object_free_cb;
+
+    if ((dbus->objects = mrp_htbl_create(&hcfg)) == NULL) {
+        mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED,
+                           "Failed to create DBUS object path table.");
+        goto fail;
+    }
+
+    hcfg.free = handler_list_free_cb;
+
+    if ((dbus->methods = mrp_htbl_create(&hcfg)) == NULL) {
+        mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED,
+                           "Failed to create DBUS method table.");
+        goto fail;
+    }
+
+    if ((dbus->signals = mrp_htbl_create(&hcfg)) == NULL) {
+        mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED,
+                           "Failed to create DBUS signal table.");
+        goto fail;
+    }
+
+
+    /*
+     * install handler for NameOwnerChanged for tracking clients/peers
+     */
+
+    if (!mrp_dbus_add_signal_handler(dbus,
+                                     BUS_SERVICE, BUS_PATH, BUS_INTERFACE,
+                                     BUS_NAME_CHANGED, name_owner_change_cb,
+                                     NULL)) {
+        mrp_dbus_error_set(errp, SDBUS_ERROR_FAILED,
+                           "Failed to install NameOwnerChanged handler.");
+        goto fail;
+    }
+
+    /* install a 'safe' filter to avoid receiving all name change signals */
+    mrp_dbus_install_filter(dbus,
+                            BUS_SERVICE, BUS_PATH, BUS_INTERFACE,
+                            BUS_NAME_CHANGED, BUS_SERVICE, NULL);
+
+    mrp_list_init(&dbus->name_trackers);
+    dbus->call_id = 1;
+
+    if (mrp_htbl_insert(buses, dbus->bus, dbus))
+        return dbus;
+
+ fail:
+    dbus_disconnect(dbus);
+    return NULL;
+}
+
+
+mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus)
+{
+    return mrp_ref_obj(dbus, refcnt);
+}
+
+
+int mrp_dbus_unref(mrp_dbus_t *dbus)
+{
+    if (mrp_unref_obj(dbus, refcnt)) {
+        dbus_disconnect(dbus);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error)
+{
+    int flags = SDBUS_NAME_REQUEST_REPLACE | SDBUS_NAME_REQUEST_DONTQ;
+    int status;
+
+    mrp_dbus_error_init(error);
+
+    status = sd_bus_request_name(dbus->bus, name, flags);
+
+    if (status == SDBUS_NAME_STATUS_OWNER || status == SDBUS_NAME_STATUS_GOTIT)
+        return TRUE;
+    else {
+        mrp_dbus_error_set(error, SDBUS_ERROR_FAILED, "failed to request name");
+
+        return FALSE;
+    }
+}
+
+
+int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error)
+{
+    int status;
+
+    mrp_dbus_error_init(error);
+
+    status = sd_bus_release_name(dbus->bus, name);
+
+    if (status == SDBUS_NAME_STATUS_RELEASED)
+        return TRUE;
+    else {
+        mrp_dbus_error_set(error, SDBUS_ERROR_FAILED, "failed to release name");
+
+        return FALSE;
+    }
+}
+
+
+const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus)
+{
+    return dbus->unique_name;
+}
+
+
+static void name_owner_query_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data)
+{
+    name_tracker_t *t = (name_tracker_t *)data;
+    const char     *owner;
+    int             state;
+
+    if (t->cb != NULL) {                /* tracker still active */
+        t->qid = 0;
+        state  = !mrp_dbus_msg_is_error(m);
+
+        if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &owner))
+            owner = "<unknown>";
+
+        t->cb(dbus, t->name, state, owner, t->user_data);
+    }
+    else                                /* already requested to delete */
+        mrp_free(t);
+}
+
+
+static int name_owner_change_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, void *data)
+{
+    const char      *name, *prev, *next;
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    MRP_UNUSED(data);
+
+    if (mrp_dbus_msg_type(m) != MRP_DBUS_MESSAGE_TYPE_SIGNAL)
+        return FALSE;
+
+    if (!mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &name) ||
+        !mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &prev) ||
+        !mrp_dbus_msg_read_basic(m, MRP_DBUS_TYPE_STRING, &next))
+        return FALSE;
+
+#if 0
+    /*
+     * Notes: XXX TODO
+     *    In principle t->cb could call mrp_dbus_forget for some other D-BUS
+     *    address than name. If that happened to be n (== p->hook.next) this
+     *    would result in a crash or memory corruption in the next iteration
+     *    of this loop (when handling n). We can easily get around this
+     *    problem by
+     *
+     *     1. administering in mrp_dbus_t that we're handing a NameOwnerChange
+     *     2. checking for this in mrp_dbus_forget_name and if it is the case
+     *        only marking the affected entry for deletion
+     *     3. removing entries marked for deletion in this loop (or just
+     *        ignoring them and making another pass in the end removing any
+     *        such entry).
+     */
+#endif
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        if (!strcmp(name, t->name))
+            t->cb(dbus, name, next && *next, next, t->user_data);
+    }
+
+    return TRUE;
+}
+
+
+int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data)
+{
+    name_tracker_t *t;
+
+    if ((t = mrp_allocz(sizeof(*t))) != NULL) {
+        if ((t->name = mrp_strdup(name)) != NULL) {
+            t->cb        = cb;
+            t->user_data = user_data;
+
+            if (mrp_dbus_install_filter(dbus,
+                                        BUS_SERVICE, BUS_PATH, BUS_INTERFACE,
+                                        BUS_NAME_CHANGED, name,
+                                        NULL)) {
+                mrp_list_append(&dbus->name_trackers, &t->hook);
+
+                t->qid = mrp_dbus_call(dbus,
+                                       BUS_SERVICE, BUS_PATH, BUS_INTERFACE,
+                                       BUS_GET_OWNER, 5000,
+                                       name_owner_query_cb, t,
+                                       MRP_DBUS_TYPE_STRING, t->name,
+                                       MRP_DBUS_TYPE_INVALID);
+                return TRUE;
+            }
+            else {
+                mrp_free(t->name);
+                mrp_free(t);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    mrp_dbus_remove_filter(dbus,
+                           BUS_SERVICE, BUS_PATH, BUS_INTERFACE,
+                           BUS_NAME_CHANGED, name,
+                           NULL);
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        if (t->cb == cb && t->user_data == user_data && !strcmp(t->name,name)) {
+            mrp_list_delete(&t->hook);
+            mrp_free(t->name);
+
+            if (!t->qid)
+                mrp_free(t);
+            else {
+                t->cb        = NULL;
+                t->user_data = NULL;
+                t->name      = NULL;
+            }
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void purge_name_trackers(mrp_dbus_t *dbus)
+{
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        mrp_list_delete(p);
+        mrp_dbus_remove_filter(dbus,
+                               BUS_SERVICE, BUS_PATH, BUS_INTERFACE,
+                               BUS_NAME_CHANGED, t->name,
+                               NULL);
+        mrp_free(t->name);
+        mrp_free(t);
+    }
+}
+
+
+static handler_t *handler_alloc(const char *sender, const char *path,
+                                const char *interface, const char *member,
+                                mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_t *h;
+
+    if ((h = mrp_allocz(sizeof(*h))) != NULL) {
+        h->sender    = mrp_strdup(sender);
+        h->path      = mrp_strdup(path);
+        h->interface = mrp_strdup(interface);
+        h->member    = mrp_strdup(member);
+
+        if ((path && !h->path) || !h->interface || !h->member) {
+            handler_free(h);
+            return NULL;
+        }
+
+        h->handler   = handler;
+        h->user_data = user_data;
+
+        return h;
+    }
+
+    return NULL;
+}
+
+
+static void handler_free(handler_t *h)
+{
+    if (h != NULL) {
+        mrp_free(h->sender);
+        mrp_free(h->path);
+        mrp_free(h->interface);
+        mrp_free(h->member);
+
+        mrp_free(h);
+    }
+}
+
+
+static handler_list_t *handler_list_alloc(const char *member)
+{
+    handler_list_t *l;
+
+    if ((l = mrp_allocz(sizeof(*l))) != NULL) {
+        if ((l->member = mrp_strdup(member)) != NULL)
+            mrp_list_init(&l->handlers);
+        else {
+            mrp_free(l);
+            l = NULL;
+        }
+    }
+
+    return l;
+}
+
+
+static inline void handler_list_free(handler_list_t *l)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+        mrp_list_delete(p);
+        handler_free(h);
+    }
+
+    mrp_free(l->member);
+    mrp_free(l);
+}
+
+
+static void handler_list_free_cb(void *key, void *entry)
+{
+    MRP_UNUSED(key);
+
+    handler_list_free((handler_list_t *)entry);
+}
+
+
+static inline int handler_specificity(handler_t *h)
+{
+    int score = 0;
+
+    if (h->path && *h->path)
+        score |= 0x4;
+    if (h->interface && *h->interface)
+        score |= 0x2;
+    if (h->member && *h->member)
+        score |= 0x1;
+
+    return score;
+}
+
+
+static void handler_list_insert(handler_list_t *l, handler_t *handler)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+    int              score;
+
+    score = handler_specificity(handler);
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (score >= handler_specificity(h)) {
+            mrp_list_append(h->hook.prev, &handler->hook);  /* add before h */
+            return;
+        }
+    }
+
+    mrp_list_append(&l->handlers, &handler->hook);
+}
+
+
+static handler_t *handler_list_lookup(handler_list_t *l, const char *path,
+                                      const char *interface, const char *member,
+                                      mrp_dbus_handler_t handler,
+                                      void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (h->handler == handler && user_data == h->user_data &&
+            path      && !strcmp(path, h->path) &&
+            interface && !strcmp(interface, h->interface) &&
+            member    && !strcmp(member, h->member))
+            return h;
+    }
+
+    return NULL;
+}
+
+
+static handler_t *handler_list_find(handler_list_t *l, const char *path,
+                                    const char *interface, const char *member)
+{
+#define MATCHES(h, field) (!*field || !*h->field || !strcmp(field, h->field))
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (MATCHES(h, path) && MATCHES(h, interface) && MATCHES(h, member))
+            return h;
+    }
+
+    return NULL;
+#undef MATCHES
+}
+
+
+static void object_free_cb(void *key, void *entry)
+{
+    object_t *o = (object_t *)entry;
+
+    MRP_UNUSED(key);
+
+    mrp_free(o->path);
+    mrp_free(o);
+}
+
+
+static object_t *object_add(mrp_dbus_t *dbus, const char *path)
+{
+    object_t *o;
+
+    o = mrp_alloc(sizeof(*o));
+
+    if (o != NULL) {
+        o->path = mrp_strdup(path);
+        o->cnt  = 1;
+
+        if (o->path == NULL) {
+            mrp_free(o);
+            return NULL;
+        }
+
+        if (sd_bus_add_object(dbus->bus, o->path, dispatch_method, dbus) == 0) {
+            if (mrp_htbl_insert(dbus->objects, o->path, o))
+                return o;
+            else
+                sd_bus_remove_object(dbus->bus, o->path, dispatch_method, dbus);
+        }
+
+        mrp_free(o->path);
+        mrp_free(o);
+    }
+
+    return NULL;
+}
+
+
+static object_t *object_lookup(mrp_dbus_t *dbus, const char *path)
+{
+    return mrp_htbl_lookup(dbus->objects, (void *)path);
+}
+
+
+static int object_ref(mrp_dbus_t *dbus, const char *path)
+{
+    object_t *o;
+
+    if ((o = object_lookup(dbus, path)) != NULL) {
+        o->cnt++;
+        return TRUE;
+    }
+
+    if (object_add(dbus, path) != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static void object_unref(mrp_dbus_t *dbus, const char *path)
+{
+    object_t *o;
+
+    if ((o = object_lookup(dbus, path)) != NULL) {
+        o->cnt--;
+
+        if (o->cnt <= 0) {
+            mrp_htbl_remove(dbus->objects, (void *)path, FALSE);
+            sd_bus_remove_object(dbus->bus, o->path, dispatch_method, dbus);
+
+            mrp_free(o->path);
+            mrp_free(o);
+        }
+    }
+}
+
+
+int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_list_t *methods;
+    handler_t      *m;
+
+    if (!object_ref(dbus, path))
+        return FALSE;
+
+    if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) {
+        if ((methods = handler_list_alloc(member)) == NULL)
+            goto fail;
+
+        mrp_htbl_insert(dbus->methods, methods->member, methods);
+    }
+
+    m = handler_alloc(NULL, path, interface, member, handler, user_data);
+
+    if (m != NULL) {
+        handler_list_insert(methods, m);
+
+        return TRUE;
+    }
+
+ fail:
+    object_unref(dbus, path);
+
+    return FALSE;
+}
+
+
+int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_list_t *methods;
+    handler_t      *m;
+
+    if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL)
+        return FALSE;
+
+    m = handler_list_lookup(methods, path, interface, member,
+                            handler, user_data);
+    if (m != NULL) {
+        object_unref(dbus, path);
+        mrp_list_delete(&m->hook);
+        handler_free(m);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data)
+{
+    handler_list_t *signals;
+    handler_t      *s;
+
+    if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) {
+        if ((signals = handler_list_alloc(member)) == NULL)
+            return FALSE;
+
+        if (!mrp_htbl_insert(dbus->signals, signals->member, signals)) {
+            handler_list_free(signals);
+            return FALSE;
+        }
+    }
+
+    s = handler_alloc(sender, path, interface, member, handler, user_data);
+    if (s != NULL) {
+        handler_list_insert(signals, s);
+        return TRUE;
+    }
+    else {
+        handler_free(s);
+        if (mrp_list_empty(&signals->handlers))
+            mrp_htbl_remove(dbus->signals, signals->member, TRUE);
+        return FALSE;
+    }
+}
+
+
+
+int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data)
+{
+    handler_list_t *signals;
+    handler_t      *s;
+
+    MRP_UNUSED(sender);
+
+    if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL)
+        return FALSE;
+
+    s = handler_list_lookup(signals, path, interface, member,
+                            handler, user_data);
+    if (s != NULL) {
+        mrp_list_delete(&s->hook);
+        handler_free(s);
+
+        if (mrp_list_empty(&signals->handlers))
+            mrp_htbl_remove(dbus->signals, (void *)member, TRUE);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+
+int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus,
+                              mrp_dbus_handler_t handler, void *user_data,
+                              const char *sender, const char *path,
+                              const char *interface, const char *member, ...)
+{
+    va_list ap;
+    int     success;
+
+
+    if (mrp_dbus_add_signal_handler(dbus, sender, path, interface, member,
+                                    handler, user_data)) {
+        va_start(ap, member);
+        success = mrp_dbus_install_filterv(dbus,
+                                           sender, path, interface, member, ap);
+        va_end(ap);
+
+        if (success)
+            return TRUE;
+        else
+            mrp_dbus_del_signal_handler(dbus, sender, path, interface, member,
+                                        handler, user_data);
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus,
+                                mrp_dbus_handler_t handler, void *user_data,
+                                const char *sender, const char *path,
+                                const char *interface, const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    status = mrp_dbus_del_signal_handler(dbus, sender, path, interface, member,
+                                         handler, user_data);
+    va_start(ap, member);
+    status &= mrp_dbus_remove_filterv(dbus,
+                                      sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+int mrp_dbus_install_filterv(mrp_dbus_t *dbus, const char *sender,
+                             const char *path, const char *interface,
+                             const char *member, va_list args)
+{
+#define ADD_TAG(tag, value) do {                                          \
+        if (value != NULL) {                                              \
+            l = snprintf(p, n, "%s%s='%s'", p == filter ? "" : ",",       \
+                         tag, value);                                     \
+            if (l >= n)                                                   \
+                return FALSE;                                             \
+            n -= l;                                                       \
+            p += l;                                                       \
+        }                                                                 \
+    } while (0)
+
+    va_list   ap;
+    char      filter[1024], *p, argn[16], *val;
+    int       n, l, i;
+
+    p = filter;
+    n = sizeof(filter);
+
+    ADD_TAG("type"     , "signal");
+    ADD_TAG("sender"   ,  sender);
+    ADD_TAG("path"     ,  path);
+    ADD_TAG("interface",  interface);
+    ADD_TAG("member"   ,  member);
+
+    va_copy(ap, args);
+    i = 0;
+    while ((val = va_arg(ap, char *)) != NULL) {
+        snprintf(argn, sizeof(argn), "arg%d", i);
+        ADD_TAG(argn, val);
+        i++;
+    }
+    va_end(ap);
+
+    if (sd_bus_add_match(dbus->bus, filter, NULL, NULL) != 0) {
+        mrp_log_error("Failed to install filter '%s'.", filter);
+
+        return FALSE;
+    }
+    else
+        return TRUE;
+
+}
+
+
+int mrp_dbus_install_filter(mrp_dbus_t *dbus, const char *sender,
+                            const char *path, const char *interface,
+                            const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    va_start(ap, member);
+    status = mrp_dbus_install_filterv(dbus,
+                                      sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, const char *sender,
+                            const char *path, const char *interface,
+                            const char *member, va_list args)
+{
+    va_list ap;
+    char    filter[1024], *p, argn[16], *val;
+    int     n, l, i;
+
+    p = filter;
+    n = sizeof(filter);
+
+    ADD_TAG("type"     , "signal");
+    ADD_TAG("sender"   ,  sender);
+    ADD_TAG("path"     ,  path);
+    ADD_TAG("interface",  interface);
+    ADD_TAG("member"   ,  member);
+
+    va_copy(ap, args);
+    i = 0;
+    while ((val = va_arg(ap, char *)) != NULL) {
+        snprintf(argn, sizeof(argn), "arg%d", i);
+        ADD_TAG(argn, val);
+        i++;
+    }
+    va_end(ap);
+
+    sd_bus_remove_match(dbus->bus, filter, NULL, NULL);
+
+    return TRUE;
+#undef ADD_TAG
+}
+
+
+int mrp_dbus_remove_filter(mrp_dbus_t *dbus, const char *sender,
+                           const char *path, const char *interface,
+                           const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    va_start(ap, member);
+    status = mrp_dbus_remove_filterv(dbus, sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+static int element_size(mrp_dbus_type_t type)
+{
+    switch (type) {
+    case MRP_DBUS_TYPE_BYTE:        return sizeof(char);
+    case MRP_DBUS_TYPE_BOOLEAN:     return sizeof(uint32_t);
+    case MRP_DBUS_TYPE_INT16:
+    case MRP_DBUS_TYPE_UINT16:      return sizeof(uint16_t);
+    case MRP_DBUS_TYPE_INT32:
+    case MRP_DBUS_TYPE_UINT32:      return sizeof(uint32_t);
+    case MRP_DBUS_TYPE_INT64:
+    case MRP_DBUS_TYPE_UINT64:      return sizeof(uint64_t);
+    case MRP_DBUS_TYPE_DOUBLE:      return sizeof(double);
+    case MRP_DBUS_TYPE_STRING:      return sizeof(char *);
+    case MRP_DBUS_TYPE_OBJECT_PATH: return sizeof(char *);
+    case MRP_DBUS_TYPE_SIGNATURE:   return sizeof(char *);
+    default:
+        return FALSE;
+    }
+
+}
+
+
+static inline mrp_dbus_msg_t *create_message(sd_bus_message *msg, int ref)
+{
+    mrp_dbus_msg_t *m;
+
+    if (msg != NULL) {
+        if ((m = mrp_allocz(sizeof(*m))) != NULL) {
+            mrp_refcnt_init(&m->refcnt);
+            mrp_list_init(&m->arrays);
+            if (ref)
+                m->msg = sd_bus_message_ref(msg);
+            else
+                m->msg = msg;
+        }
+
+        return m;
+    }
+    else
+        return NULL;
+}
+
+
+static void free_msg_array(msg_array_t *a)
+{
+    void   *ptr;
+    size_t  esize, i;
+    int     string;
+
+    if (a == NULL)
+        return;
+
+    mrp_list_delete(&a->hook);
+
+    if ((esize = element_size(a->type)) != 0) {
+        if (a->type == MRP_DBUS_TYPE_STRING ||
+            a->type == MRP_DBUS_TYPE_OBJECT_PATH ||
+            a->type == MRP_DBUS_TYPE_SIGNATURE)
+            string = TRUE;
+        else
+            string = FALSE;
+
+        if (string)
+            for (i = 0, ptr = a->items; i < a->nitem; i++, ptr += esize)
+                mrp_free(ptr);
+
+        mrp_free(a->items);
+    }
+    else
+        mrp_log_error("Hmm... looks like we have a corrupted implicit array.");
+
+    mrp_free(a);
+}
+
+
+static void free_message(mrp_dbus_msg_t *m)
+{
+    mrp_list_hook_t *p, *n;
+    msg_array_t     *a;
+
+    mrp_list_foreach(&m->arrays, p, n) {
+        a = mrp_list_entry(p, typeof(*a), hook);
+        free_msg_array(a);
+    }
+
+    mrp_free(m);
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m)
+{
+    return mrp_ref_obj(m, refcnt);
+}
+
+
+int mrp_dbus_msg_unref(mrp_dbus_msg_t *m)
+{
+    if (mrp_unref_obj(m, refcnt)) {
+        sd_bus_message_unref(m->msg);
+        free_message(m);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static inline int verify_type(sd_bus_message *msg, int expected_type)
+{
+    uint8_t type;
+
+    if (sd_bus_message_get_type(msg, &type) != 0 || type == expected_type)
+        return FALSE;
+    else
+        return TRUE;
+}
+
+
+static int dispatch_method(sd_bus *bus,int ret, sd_bus_message *msg, void *data)
+{
+#define SAFESTR(str) (str ? str : "<none>")
+    mrp_dbus_t     *dbus      = (mrp_dbus_t *)data;
+    mrp_dbus_msg_t *m         = NULL;
+    const char     *path      = sd_bus_message_get_path(msg);
+    const char     *interface = sd_bus_message_get_interface(msg);
+    const char     *member    = sd_bus_message_get_member(msg);
+    int             r         = FALSE;
+    handler_list_t *l;
+    handler_t      *h;
+
+    MRP_UNUSED(bus);
+    MRP_UNUSED(ret);
+
+    if (!verify_type(msg, MRP_DBUS_MESSAGE_TYPE_METHOD_CALL) || !member)
+        return r;
+
+    mrp_debug("path='%s', interface='%s', member='%s')...",
+              SAFESTR(path), SAFESTR(interface), SAFESTR(member));
+
+    if ((l = mrp_htbl_lookup(dbus->methods, (void *)member)) != NULL) {
+    retry:
+        if ((h = handler_list_find(l, path, interface, member)) != NULL) {
+            sd_bus_message_rewind(msg, TRUE);
+
+            if (m == NULL)
+                m = create_message(msg, TRUE);
+
+            if (h->handler(dbus, m, h->user_data))
+                r = TRUE;
+
+            goto out;
+        }
+    }
+    else {
+        if ((l = mrp_htbl_lookup(dbus->methods, "")) != NULL)
+            goto retry;
+    }
+
+ out:
+    if (!r)
+        mrp_debug("Unhandled method path=%s, %s.%s.", SAFESTR(path),
+                  SAFESTR(interface), SAFESTR(member));
+
+    mrp_dbus_msg_unref(m);
+
+    return r;
+}
+
+
+static int dispatch_signal(sd_bus *bus,int ret, sd_bus_message *msg, void *data)
+{
+#define MATCHES(h, field) (!*field || !h->field || !*h->field || \
+                           !strcmp(field, h->field))
+    mrp_dbus_t     *dbus      = (mrp_dbus_t *)data;
+    mrp_dbus_msg_t *m         = NULL;
+    const char     *path      = sd_bus_message_get_path(msg);
+    const char     *interface = sd_bus_message_get_interface(msg);
+    const char     *member    = sd_bus_message_get_member(msg);
+    mrp_list_hook_t *p, *n;
+    handler_list_t  *l;
+    handler_t       *h;
+    int              retried = FALSE;
+    int              handled = FALSE;
+
+    MRP_UNUSED(bus);
+    MRP_UNUSED(ret);
+
+    if (!verify_type(msg, MRP_DBUS_MESSAGE_TYPE_SIGNAL) || !member)
+        return FALSE;
+
+    mrp_debug("%s(path='%s', interface='%s', member='%s')...",
+              __FUNCTION__, SAFESTR(path), SAFESTR(interface), SAFESTR(member));
+
+    if ((l = mrp_htbl_lookup(dbus->signals, (void *)member)) != NULL) {
+    retry:
+        mrp_list_foreach(&l->handlers, p, n) {
+            h = mrp_list_entry(p, handler_t, hook);
+
+            if (MATCHES(h,path) && MATCHES(h,interface) && MATCHES(h,member)) {
+                sd_bus_message_rewind(msg, TRUE);
+
+                if (m == NULL)
+                    m = create_message(msg, TRUE);
+
+                h->handler(dbus, m, h->user_data);
+                handled = TRUE;
+            }
+        }
+    }
+
+    if (!retried) {
+        if ((l = mrp_htbl_lookup(dbus->signals, "")) != NULL) {
+            retried = TRUE;
+            goto retry;
+        }
+    }
+
+    if (!handled)
+        mrp_debug("Unhandled signal path=%s, %s.%s.", SAFESTR(path),
+                  SAFESTR(interface), SAFESTR(member));
+
+    mrp_dbus_msg_unref(m);
+
+    return FALSE;
+#undef MATCHES
+#undef SAFESTR
+}
+
+
+static int append_args_strtype(mrp_dbus_msg_t *msg, const char *types,
+                               va_list ap)
+{
+    MRP_UNUSED(msg);
+    MRP_UNUSED(types);
+    MRP_UNUSED(ap);
+
+    return FALSE;
+}
+
+
+static int append_args_inttype(sd_bus_message *msg, int type, va_list ap)
+{
+    void            *vptr;
+    int              atype, elen, i;
+    void           **aptr;
+    int              alen;
+    char             stype[2] = { '\0', '\0' };
+    int              r        = 0;
+
+    (void)append_args_strtype;
+
+    while (type != MRP_DBUS_TYPE_INVALID) {
+        switch (type) {
+        case MRP_DBUS_TYPE_BYTE:
+        case MRP_DBUS_TYPE_BOOLEAN:
+        case MRP_DBUS_TYPE_INT16:
+        case MRP_DBUS_TYPE_UINT16:
+        case MRP_DBUS_TYPE_INT32:
+        case MRP_DBUS_TYPE_UINT32:
+        case MRP_DBUS_TYPE_INT64:
+        case MRP_DBUS_TYPE_UINT64:
+        case MRP_DBUS_TYPE_DOUBLE:
+        case MRP_DBUS_TYPE_STRING:
+        case MRP_DBUS_TYPE_OBJECT_PATH:
+        case MRP_DBUS_TYPE_SIGNATURE:
+        case MRP_DBUS_TYPE_UNIX_FD:
+            vptr = va_arg(ap, void *);
+            r = sd_bus_message_append_basic(msg, type, vptr);
+            break;
+
+        case MRP_DBUS_TYPE_ARRAY:
+            atype = va_arg(ap, int);
+            aptr  = va_arg(ap, void **);
+            alen  = va_arg(ap, int);
+
+            switch (atype) {
+#define LEN(_type, _size) case MRP_DBUS_TYPE_##_type: elen = _size; break
+                LEN(BYTE       , sizeof(uint8_t));
+                LEN(BOOLEAN    , sizeof(uint32_t));
+                LEN(INT16      , sizeof(int16_t));
+                LEN(UINT16     , sizeof(uint16_t));
+                LEN(INT32      , sizeof(int32_t));
+                LEN(UINT32     , sizeof(uint32_t));
+                LEN(INT64      , sizeof(int64_t));
+                LEN(UINT64     , sizeof(uint64_t));
+                LEN(DOUBLE     , sizeof(double));
+                LEN(STRING     , sizeof(const char *));
+                LEN(OBJECT_PATH, sizeof(const char *));
+                LEN(SIGNATURE  , sizeof(const char *));
+                LEN(UNIX_FD    , sizeof(int));
+#undef LEN
+            default:
+                return FALSE;
+            }
+
+            stype[0] = atype;
+            if (sd_bus_message_open_container(msg, type, stype) != 0)
+                return FALSE;
+            for (i = 0; i < alen; i++, aptr += elen)
+                if (sd_bus_message_append_basic(msg, atype, aptr) != 0)
+                    return FALSE;
+            if (sd_bus_message_close_container(msg) != 0)
+                return FALSE;
+            else
+                return TRUE;
+            break;
+
+        default:
+            return FALSE;
+        }
+
+        type = va_arg(ap, int);
+    }
+
+    return (r == 0 ? TRUE : FALSE);
+}
+
+
+static int call_reply_cb(sd_bus *bus, int ret, sd_bus_message *msg,
+                         void *user_data)
+{
+    call_t         *call  = (call_t *)user_data;
+    mrp_dbus_msg_t *reply = create_message(msg, TRUE);
+    sd_bus_error    error;
+
+    MRP_UNUSED(bus);
+
+    call->serial = 0;
+    mrp_list_delete(&call->hook);
+
+    if (ret == 0) {
+        reply = create_message(msg, TRUE);
+        sd_bus_message_rewind(reply->msg, TRUE);
+    }
+    else {
+        sd_bus_message *err = NULL;
+
+        if (ret == ETIMEDOUT)
+            error = SD_BUS_ERROR_MAKE(MRP_DBUS_ERROR_TIMEOUT,
+                                      "D-Bus call timed out");
+        else
+            error = SD_BUS_ERROR_MAKE(MRP_DBUS_ERROR_FAILED,
+                                      "D-Bus call failed");
+
+        if (sd_bus_message_new_method_error(bus, call->msg, &error, &err) == 0)
+            reply = create_message(err, FALSE);
+        else
+            reply = NULL;
+    }
+
+    call->cb(call->dbus, reply, call->user_data);
+    call_free(call);
+    mrp_dbus_msg_unref(reply);
+
+    return TRUE;
+}
+
+
+int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data, int type, ...)
+{
+    va_list          ap;
+    int32_t          id;
+    call_t          *call;
+    sd_bus_message  *msg;
+    int              success;
+
+    call = NULL;
+
+    if (sd_bus_message_new_method_call(dbus->bus, dest, path, interface, member,
+                                       &msg) != 0)
+        return 0;
+
+    if (cb != NULL) {
+        if ((call = mrp_allocz(sizeof(*call))) != NULL) {
+            mrp_list_init(&call->hook);
+
+            call->dbus      = dbus;
+            call->id        = dbus->call_id++;
+            call->cb        = cb;
+            call->user_data = user_data;
+
+            id = call->id;
+        }
+        else
+            goto fail;
+    }
+    else
+        id = dbus->call_id++;
+
+    if (type == MRP_DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = append_args_inttype(msg, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (cb == NULL) {
+        sd_bus_message_set_no_reply(msg, TRUE);
+        if (sd_bus_send(dbus->bus, msg, NULL) != 0)
+            goto fail;
+        sd_bus_message_unref(msg);
+    }
+    else {
+        if (sd_bus_send_with_reply(dbus->bus, msg, call_reply_cb, call,
+                                   timeout * 1000, &call->serial) != 0)
+            goto fail;
+
+        mrp_list_append(&dbus->calls, &call->hook);
+        call->msg = msg;
+    }
+
+    return id;
+
+ fail:
+    sd_bus_message_unref(msg);
+    call_free(call);
+
+    return 0;
+}
+
+
+int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *m)
+{
+    /*bus_message_dump(m->msg);*/
+
+    if (sd_bus_send(dbus->bus, m->msg, NULL) == 0)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id)
+{
+    mrp_list_hook_t *p, *n;
+    call_t          *call;
+
+    mrp_list_foreach(&dbus->calls, p, n) {
+        call = mrp_list_entry(p, call_t, hook);
+
+        if (call->id == id) {
+            mrp_list_delete(p);
+
+            sd_bus_send_with_reply_cancel(dbus->bus, call->serial);
+            call->serial = 0;
+
+            call_free(call);
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *m, int type, ...)
+{
+    va_list         ap;
+    sd_bus_message *rpl;
+    int             success;
+
+    if (sd_bus_message_new_method_return(dbus->bus, m->msg, &rpl) != 0)
+        return FALSE;
+
+    va_start(ap, type);
+    success = append_args_inttype(rpl, type, ap);
+    va_end(ap);
+
+    if (!success)
+        goto fail;
+
+    if (sd_bus_send(dbus->bus, rpl, NULL) != 0)
+        goto fail;
+
+    sd_bus_message_unref(rpl);
+
+    return TRUE;
+
+ fail:
+    sd_bus_message_unref(rpl);
+
+    return FALSE;
+}
+
+
+int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *m,
+                         const char *errname, const char *errmsg, int type, ...)
+{
+    va_list         ap;
+    sd_bus_message *rpl;
+    int             success;
+    sd_bus_error    err = SD_BUS_ERROR_NULL;;
+
+    sd_bus_error_set_const(&err, errname, errmsg);
+
+    if (sd_bus_message_new_method_error(dbus->bus, m->msg, &err, &rpl) != 0)
+        return FALSE;
+
+    va_start(ap, type);
+    success = append_args_inttype(rpl, type, ap);
+    va_end(ap);
+
+    if (!success)
+        goto fail;
+
+    if (sd_bus_send(dbus->bus, rpl, NULL) != 0)
+        goto fail;
+
+    sd_bus_message_unref(rpl);
+
+    return TRUE;
+
+ fail:
+    sd_bus_message_unref(rpl);
+
+    return FALSE;
+}
+
+
+static void call_free(call_t *call)
+{
+    if (call != NULL) {
+        sd_bus_message_unref(call->msg);
+        mrp_free(call);
+    }
+}
+
+
+static void purge_calls(mrp_dbus_t *dbus)
+{
+    mrp_list_hook_t *p, *n;
+    call_t          *call;
+
+    mrp_list_foreach(&dbus->calls, p, n) {
+        call = mrp_list_entry(p, call_t, hook);
+
+        mrp_list_delete(&call->hook);
+
+        if (call->serial != 0)
+            sd_bus_send_with_reply_cancel(dbus->bus, call->serial);
+
+        mrp_free(call);
+    }
+}
+
+
+int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path,
+                    const char *interface, const char *member, int type, ...)
+{
+    va_list         ap;
+    sd_bus_message *msg;
+    int             success;
+
+    if (sd_bus_message_new_signal(dbus->bus, path, interface, member,
+                                  &msg) != 0)
+        return 0;
+
+    va_start(ap, type);
+    success = append_args_inttype(msg, type, ap);
+    va_end(ap);
+
+    if (!success)
+        goto fail;
+
+    if (dest != NULL)
+        if (sd_bus_message_set_destination(msg, dest) != 0)
+            goto fail;
+
+    if (sd_bus_send(dbus->bus, msg, NULL) != 0)
+        goto fail;
+
+    sd_bus_message_unref(msg);
+
+    return TRUE;
+
+ fail:
+    sd_bus_message_unref(msg);
+
+    return 0;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *dbus,
+                                         const char *destination,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member)
+{
+    sd_bus_message *msg;
+
+    if (sd_bus_message_new_method_call(dbus->bus, destination,
+                                       path, interface, member, &msg) == 0)
+        return create_message(msg, FALSE);
+    else
+        return NULL;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *dbus,
+                                           mrp_dbus_msg_t *msg)
+{
+    sd_bus_message *req, *rpl;
+
+    req = (sd_bus_message *)msg;
+
+    if (sd_bus_message_new_method_return(dbus->bus, req, &rpl) == 0)
+        return create_message(rpl, FALSE);
+    else
+        return NULL;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *m,
+                                   mrp_dbus_err_t *err)
+{
+    sd_bus_message *req, *rpl;
+
+    req = m->msg;
+
+    if (sd_bus_message_new_method_error(dbus->bus, req, err, &rpl) == 0)
+        return create_message(rpl, FALSE);
+    else
+        return NULL;
+}
+
+
+mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *dbus,
+                                    const char *destination,
+                                    const char *path,
+                                    const char *interface,
+                                    const char *member)
+{
+    sd_bus_message *msg = NULL;
+
+    if (sd_bus_message_new_signal(dbus->bus, path, interface, member,
+                                  &msg) == 0) {
+        if (destination != NULL) {
+            if (sd_bus_message_set_destination(msg, destination) != 0) {
+                sd_bus_message_unref(msg);
+                msg = NULL;
+            }
+        }
+    }
+
+    return create_message(msg, FALSE);
+}
+
+
+mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *m)
+{
+    uint8_t type;
+
+    if (sd_bus_message_get_type(m->msg, &type) == 0)
+        return (mrp_dbus_msg_type_t)type;
+    else
+        return MRP_DBUS_MESSAGE_TYPE_INVALID;
+}
+
+#define WRAP_GETTER(type, what)                                        \
+    type mrp_dbus_msg_##what(mrp_dbus_msg_t *m)                        \
+    {                                                                  \
+        return sd_bus_message_get_##what((sd_bus_message *)m->msg);    \
+    }                                                                  \
+    struct __mrp_dbus_allow_trailing_semicolon
+
+WRAP_GETTER(const char *, path);
+WRAP_GETTER(const char *, interface);
+WRAP_GETTER(const char *, member);
+WRAP_GETTER(const char *, destination);
+WRAP_GETTER(const char *, sender);
+
+#undef WRAP_GETTER
+
+
+int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type,
+                                const char *contents)
+{
+    return sd_bus_message_open_container(m->msg, type, contents) == 0;
+}
+
+
+int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m)
+{
+    return sd_bus_message_close_container(m->msg) == 0;
+}
+
+
+int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep)
+{
+    return sd_bus_message_append_basic(m->msg, type, valuep) == 0;
+}
+
+
+int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *m, char type,
+                                 const char *contents)
+{
+    return sd_bus_message_enter_container(m->msg, type, contents) == 1;
+}
+
+
+int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m)
+{
+    return sd_bus_message_exit_container(m->msg) == 1;
+}
+
+
+int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep)
+{
+    return sd_bus_message_read_basic(m->msg, type, valuep) == 1;
+}
+
+
+int mrp_dbus_msg_read_array(mrp_dbus_msg_t *m, char type,
+                            void **itemsp, size_t *nitemp)
+{
+    char          sub[2] = { (char)type, '\0' };
+    msg_array_t  *a;
+    int           offs;
+    size_t        esize;
+
+    if ((esize = element_size(type)) == 0)
+        return FALSE;
+
+    if (!mrp_dbus_msg_enter_container(m, MRP_DBUS_TYPE_ARRAY, sub))
+        return FALSE;
+
+    if ((a = mrp_allocz(sizeof(*a))) == NULL)
+        goto fail;
+
+    a->type = type;
+    mrp_list_init(&a->hook);
+
+    offs = 0;
+    while (mrp_dbus_msg_arg_type(m, NULL) != MRP_DBUS_TYPE_INVALID) {
+        if (!mrp_realloc(a->items, offs + esize))
+            goto fail;
+
+        if (!mrp_dbus_msg_read_basic(m, type, a->items + offs))
+            goto fail;
+        else
+            a->nitem++;
+
+        offs += esize;
+    }
+
+    mrp_dbus_msg_exit_container(m);
+
+    mrp_list_append(&m->arrays, &a->hook);
+    *itemsp = a->items;
+    *nitemp = a->nitem;
+
+    return TRUE;
+
+ fail:
+    mrp_dbus_msg_exit_container(m);
+    free_msg_array(a);
+
+    return FALSE;
+}
+
+
+mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents)
+{
+    char type;
+
+    if (sd_bus_message_peek_type(m->msg, &type, contents) >= 0)
+        return (mrp_dbus_type_t)type;
+    else
+        return MRP_DBUS_TYPE_INVALID;
+}
diff --git a/src/common/dbus-sdbus.h b/src/common/dbus-sdbus.h
new file mode 100644 (file)
index 0000000..982b0f6
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SD_BUS_H__
+#define __MURPHY_SD_BUS_H__
+
+#include <systemd/sd-bus.h>
+#include <systemd/sd-bus-protocol.h>
+
+#include <murphy/common/mainloop.h>
+#include <murphy/common/dbus-error.h>
+
+/** Type for a D-Bus (connection). */
+struct mrp_dbus_s;
+typedef struct mrp_dbus_s mrp_dbus_t;
+
+/** Type for a D-Bus message. */
+typedef struct mrp_dbus_msg_s mrp_dbus_msg_t;
+
+/** Type for a D-Bus error. */
+typedef sd_bus_error mrp_dbus_err_t;
+
+/** D-BUS method or signal callback type. */
+typedef int (*mrp_dbus_handler_t)(mrp_dbus_t *, mrp_dbus_msg_t *, void *);
+
+/** Create a new connection to the given bus. */
+mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address,
+                             mrp_dbus_err_t *errp);
+#define mrp_dbus_get mrp_dbus_connect
+
+/** Set up an sd-bus instance with a mainloop. */
+int mrp_dbus_setup_sd_bus(mrp_mainloop_t *ml, sd_bus *bus);
+
+/** Increase the reference count of the given DBus (connection). */
+mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus);
+
+/** Decrease the reference count of the given DBus (connection). */
+int mrp_dbus_unref(mrp_dbus_t *dbus);
+
+/** Acquire the given name on the given bus (connection). */
+int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error);
+
+/** Release the given name on the given bus (connection). */
+int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name,
+                          mrp_dbus_err_t *error);
+
+/** Type for a name tracking callback. */
+typedef void (*mrp_dbus_name_cb_t)(mrp_dbus_t *, const char *, int,
+                                   const char *, void *);
+/** Start tracking the given name. */
+int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data);
+/** Stop tracking the given name. */
+int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data);
+
+/** Export a method to the bus. */
+int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data);
+
+/** Remove an exported method. */
+int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data);
+
+/** Install a filter and add a handler for the given signal on the bus. */
+MRP_NULLTERM int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus,
+                                           mrp_dbus_handler_t handler,
+                                           void *user_data,
+                                           const char *sender,
+                                           const char *path,
+                                           const char *interface,
+                                           const char *member, ...);
+
+/** Remove the signal handler and filter for the given signal on the bus. */
+MRP_NULLTERM int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus,
+                                             mrp_dbus_handler_t handler,
+                                             void *user_data,
+                                             const char *sender,
+                                             const char *path,
+                                             const char *interface,
+                                             const char *member, ...);
+
+/** Install a filter for the given message on the bus. */
+MRP_NULLTERM int mrp_dbus_install_filter(mrp_dbus_t *dbus,
+                                         const char *sender,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member, ...);
+
+/** Install a filter for the given message on the bus. */
+int mrp_dbus_install_filterv(mrp_dbus_t *dbus,
+                             const char *sender,
+                             const char *path,
+                             const char *interface,
+                             const char *member,
+                             va_list ap);
+
+/** Remove a filter for the given message on the bus. */
+MRP_NULLTERM int mrp_dbus_remove_filter(mrp_dbus_t *dbus,
+                                        const char *sender,
+                                        const char *path,
+                                        const char *interface,
+                                        const char *member, ...);
+
+/** Remove a filter for the given message on the bus. */
+int mrp_dbus_remove_filterv(mrp_dbus_t *dbus,
+                            const char *sender,
+                            const char *path,
+                            const char *interface,
+                            const char *member,
+                            va_list ap);
+
+/** Add a signal handler for the gvien signal on the bus. */
+int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data);
+
+/** Remove the given signal handler for the given signal on the bus. */
+int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data);
+
+/** Type of a method call reply callback. */
+typedef void (*mrp_dbus_reply_cb_t)(mrp_dbus_t *dbus, mrp_dbus_msg_t *reply,
+                                    void *user_data);
+
+/** Call the given method on the bus. */
+int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest,
+                      const char *path, const char *interface,
+                      const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data,
+                      int dbus_type, ...);
+
+/** Cancel an ongoing method call on the bus. */
+int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id);
+
+/** Send a reply to the given method call on the bus. */
+int mrp_dbus_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, int type, ...);
+
+/** Send an error reply to the given method call on the bus. */
+int mrp_dbus_reply_error(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg,
+                         const char *errname, const char *errmsg,
+                         int type, ...);
+
+/** Emit the given signal on the bus. */
+int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path,
+                    const char *interface, const char *member, int type, ...);
+
+/** Send the given message on the bus. */
+int mrp_dbus_send_msg(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg);
+
+/** Get our unique name on the bus. */
+const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus);
+
+/** Initialize the given error. */
+static inline mrp_dbus_err_t *mrp_dbus_error_init(mrp_dbus_err_t *err)
+{
+    if (err != NULL)
+        memset(err, 0, sizeof(*err));
+
+    return err;
+}
+
+
+/** Set the given error buffer up with the error name and message. */
+static inline mrp_dbus_err_t *mrp_dbus_error_set(mrp_dbus_err_t *err,
+                                                 const char *name,
+                                                 const char *message)
+{
+    sd_bus_error_set(err, name, "%s", message);
+
+    return err;
+}
+
+
+/** Get the error message from the given bus error message. */
+static inline const char *mrp_dbus_errmsg(mrp_dbus_err_t *err)
+{
+    if (err && sd_bus_error_is_set(err))
+        return err->message;
+    else
+        return "unknown DBUS error";
+}
+
+
+/** Increase the reference count of a message. */
+mrp_dbus_msg_t *mrp_dbus_msg_ref(mrp_dbus_msg_t *m);
+
+/** Decrease the reference count of a message, freeing it if necessary. */
+int mrp_dbus_msg_unref(mrp_dbus_msg_t *m);
+
+
+/** Create a new method call message. */
+mrp_dbus_msg_t *mrp_dbus_msg_method_call(mrp_dbus_t *bus,
+                                         const char *destination,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member);
+
+/** Create a new method return message. */
+mrp_dbus_msg_t *mrp_dbus_msg_method_return(mrp_dbus_t *bus,
+                                           mrp_dbus_msg_t *msg);
+
+/** Create a new error reply message. */
+mrp_dbus_msg_t *mrp_dbus_msg_error(mrp_dbus_t *bus, mrp_dbus_msg_t *msg,
+                                   mrp_dbus_err_t *err);
+
+/** Create a new signal message. */
+mrp_dbus_msg_t *mrp_dbus_msg_signal(mrp_dbus_t *bus,
+                                    const char *destination,
+                                    const char *path,
+                                    const char *interface,
+                                    const char *member);
+
+/** Bus message types. */
+typedef enum {
+#ifndef SD_BUS_MESSAGE_TYPE_INVALID
+#   define SD_BUS_MESSAGE_TYPE_INVALID _SD_BUS_MESSAGE_TYPE_INVALID
+#endif
+#   define MAP(t, f) MRP_DBUS_MESSAGE_TYPE_##t = SD_BUS_MESSAGE_TYPE_##f
+    MAP(INVALID      , INVALID),
+    MAP(METHOD_CALL  , METHOD_CALL),
+    MAP(METHOD_RETURN, METHOD_RETURN),
+    MAP(ERROR        , METHOD_ERROR),
+    MAP(SIGNAL       , SIGNAL)
+#   undef MAP
+} mrp_dbus_msg_type_t;
+
+/** Get the type of the given message. */
+mrp_dbus_msg_type_t mrp_dbus_msg_type(mrp_dbus_msg_t *msg);
+
+/** Message type checking convenience functions. */
+#define TYPE_CHECK_FUNCTION(type, TYPE)                                 \
+    static inline int mrp_dbus_msg_is_##type(mrp_dbus_msg_t *msg)       \
+    {                                                                   \
+        return mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_##TYPE;  \
+    }                                                                   \
+    struct __mrp_dbus_allow_traling_semicolon
+
+TYPE_CHECK_FUNCTION(method_call  , METHOD_CALL);
+TYPE_CHECK_FUNCTION(method_return, METHOD_RETURN);
+TYPE_CHECK_FUNCTION(error        , ERROR);
+TYPE_CHECK_FUNCTION(signal       , SIGNAL);
+
+/** Message argument types. */
+typedef enum {
+#ifndef SD_BUS_TYPE_INVALID
+#   define SD_BUS_TYPE_INVALID _SD_BUS_TYPE_INVALID
+#endif
+#define TYPE(t) MRP_DBUS_TYPE_##t = SD_BUS_TYPE_##t
+    TYPE(INVALID),
+    TYPE(BYTE),
+    TYPE(BOOLEAN),
+    TYPE(INT16),
+    TYPE(UINT16),
+    TYPE(INT32),
+    TYPE(UINT32),
+    TYPE(INT64),
+    TYPE(UINT64),
+    TYPE(DOUBLE),
+    TYPE(STRING),
+    TYPE(OBJECT_PATH),
+    TYPE(SIGNATURE),
+    TYPE(UNIX_FD),
+    TYPE(ARRAY),
+    TYPE(VARIANT),
+    TYPE(STRUCT),
+    TYPE(DICT_ENTRY),
+    TYPE(STRUCT_BEGIN),
+    TYPE(STRUCT_END),
+    TYPE(DICT_ENTRY_BEGIN),
+    TYPE(DICT_ENTRY_END)
+#undef TYPE
+} mrp_dbus_type_t;
+
+/** Message argument types as strings. */
+static const char _type_as_string[][2] = {
+#define MAP(_type) [SD_BUS_TYPE_##_type] = { SD_BUS_TYPE_##_type, '\0' }
+    MAP(BYTE),
+    MAP(BOOLEAN),
+    MAP(INT16),
+    MAP(UINT16),
+    MAP(INT32),
+    MAP(UINT32),
+    MAP(INT64),
+    MAP(UINT64),
+    MAP(DOUBLE),
+    MAP(STRING),
+    MAP(OBJECT_PATH),
+    MAP(SIGNATURE),
+    MAP(UNIX_FD),
+    MAP(ARRAY),
+    MAP(VARIANT),
+    MAP(STRUCT),
+    MAP(DICT_ENTRY),
+    MAP(STRUCT_BEGIN),
+    MAP(STRUCT_END),
+    MAP(DICT_ENTRY_BEGIN),
+    MAP(DICT_ENTRY_END)
+#undef MAP
+};
+
+#define _STRTYPE(_type) _type_as_string[SD_BUS_TYPE_##_type]
+#define _EVAL(_type)    _type
+#define MRP_DBUS_TYPE_BYTE_AS_STRING        _EVAL(_STRTYPE(BYTE))
+#define MRP_DBUS_TYPE_BOOLEAN_AS_STRING     _EVAL(_STRTYPE(BOOLEAN))
+#define MRP_DBUS_TYPE_INT16_AS_STRING       _EVAL(_STRTYPE(INT16))
+#define MRP_DBUS_TYPE_UINT16_AS_STRING      _EVAL(_STRTYPE(UINT16))
+#define MRP_DBUS_TYPE_INT32_AS_STRING       _EVAL(_STRTYPE(INT32))
+#define MRP_DBUS_TYPE_UINT32_AS_STRING      _EVAL(_STRTYPE(UINT32))
+#define MRP_DBUS_TYPE_INT64_AS_STRING       _EVAL(_STRTYPE(INT64))
+#define MRP_DBUS_TYPE_UINT64_AS_STRING      _EVAL(_STRTYPE(UINT64))
+#define MRP_DBUS_TYPE_DOUBLE_AS_STRING      _EVAL(_STRTYPE(DOUBLE))
+#define MRP_DBUS_TYPE_STRING_AS_STRING      _EVAL(_STRTYPE(STRING))
+#define MRP_DBUS_TYPE_OBJECT_PATH_AS_STRING _EVAL(_STRTYPE(OBJECT_PATH))
+#define MRP_DBUS_TYPE_SIGNATURE_AS_STRING   _EVAL(_STRTYPE(SIGNATURE))
+#define MRP_DBUS_TYPE_UNIX_FD_AS_STRING     _EVAL(_STRTYPE(UNIX_FD))
+#define MRP_DBUS_TYPE_ARRAY_AS_STRING       _EVAL(_STRTYPE(ARRAY))
+#define MRP_DBUS_TYPE_VARIANT_AS_STRING     _EVAL(_STRTYPE(VARIANT))
+#define MRP_DBUS_TYPE_STRUCT_AS_STRING      _EVAL(_STRTYPE(STRUCT))
+#define MRP_DBUS_TYPE_DICT_ENTRY_AS_STRING  _EVAL(_STRTYPE(DICT_ENTRY))
+
+/** Get the path of the given message. */
+const char *mrp_dbus_msg_path(mrp_dbus_msg_t *msg);
+
+/** Get the interface of the given message. */
+const char *mrp_dbus_msg_interface(mrp_dbus_msg_t *msg);
+
+/** Get the member of the given message. */
+const char *mrp_dbus_msg_member(mrp_dbus_msg_t *msg);
+
+/** Get the destination of the given message. */
+const char *mrp_dbus_msg_destination(mrp_dbus_msg_t *msg);
+
+/** Get the sender of the given message. */
+const char *mrp_dbus_msg_sender(mrp_dbus_msg_t *msg);
+
+/** Open a new container of the given type and cotained types. */
+int mrp_dbus_msg_open_container(mrp_dbus_msg_t *m, char type,
+                                const char *contents);
+
+/** Close the current container. */
+int mrp_dbus_msg_close_container(mrp_dbus_msg_t *m);
+
+/** Append an argument of a basic type to the given message. */
+int mrp_dbus_msg_append_basic(mrp_dbus_msg_t *m, char type, void *valuep);
+
+/** Get the type of the current message argument. */
+mrp_dbus_type_t mrp_dbus_msg_arg_type(mrp_dbus_msg_t *m, const char **contents);
+
+/** Open the current container (of the given type and contents) for reading. */
+int mrp_dbus_msg_enter_container(mrp_dbus_msg_t *msg, char type,
+                                 const char *contents);
+
+/** Exit from the container being currently read. */
+int mrp_dbus_msg_exit_container(mrp_dbus_msg_t *m);
+
+/** Read the next argument (of basic type) from the given message. */
+int mrp_dbus_msg_read_basic(mrp_dbus_msg_t *m, char type, void *valuep);
+
+/** Read the next array of one of the basic types. */
+int mrp_dbus_msg_read_array(mrp_dbus_msg_t *m, char type,
+                            void **itemsp, size_t *nitemp);
+
+/** Set up an sd_bus to be pumped by a murphy mainloop. */
+int mrp_dbus_setup_with_mainloop(mrp_mainloop_t *ml, sd_bus *bus);
+#endif /* __MURPHY_SD_BUS_H__ */
diff --git a/src/common/dbus-transport.c b/src/common/dbus-transport.c
new file mode 100644 (file)
index 0000000..dda1c8e
--- /dev/null
@@ -0,0 +1,1721 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/libdbus.h>
+#include <murphy/common/dbus-transport.h>
+
+#define DBUS  "dbus"
+#define DBUSL 4
+
+#define TRANSPORT_PATH       "/murphy/transport"
+#define TRANSPORT_INTERFACE  "Murphy.Transport"
+#define TRANSPORT_MESSAGE    "DeliverMessage"
+#define TRANSPORT_DATA       "DeliverData"
+#define TRANSPORT_RAW        "DeliverRaw"
+#define TRANSPORT_METHOD     "DeliverMessage"
+
+#define ANY_ADDRESS          "any"
+
+typedef struct {
+    MRP_TRANSPORT_PUBLIC_FIELDS;         /* common transport fields */
+    mrp_dbus_t     *dbus;                /* D-BUS connection */
+    int             bound : 1;           /* whether bound to an address */
+    int             peer_resolved : 1;   /* connected and peer name known */
+    mrp_dbusaddr_t  local;               /* address we're bound to */
+    mrp_dbusaddr_t  remote;              /* address we're connected to */
+} dbus_t;
+
+
+static uint32_t nauto;                   /* for autobinding */
+
+
+static int dbus_msg_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data);
+static int dbus_data_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data);
+static int dbus_raw_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data);
+
+static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data);
+
+static DBusMessage *msg_encode(const char *sender_id, mrp_msg_t *msg);
+static mrp_msg_t *msg_decode(DBusMessage *m, const char **sender_id);
+
+static DBusMessage *data_encode(const char *sender_id,
+                                void *data, uint16_t tag);
+static void *data_decode(DBusMessage *m, uint16_t *tag, const char **sender_id);
+
+static DBusMessage *raw_encode(const char *sender_id, void *data, size_t size);
+static void *raw_decode(DBusMessage *m, size_t *sizep, const char **sender_id);
+
+
+
+static socklen_t parse_address(const char *str, mrp_dbusaddr_t *addr,
+                               socklen_t size)
+{
+    const char *p, *e;
+    char       *q;
+    size_t      l, n;
+
+    if (size < sizeof(*addr)) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    if (strncmp(str, DBUS":", DBUSL + 1))
+        return 0;
+    else
+        str += DBUSL + 1;
+
+    /*
+     * The format of the address is
+     *     dbus:[bus-address]@address/path
+     * eg.
+     *     dbus:[session]@:1.33/client1, or
+     *     dbus:[unix:abstract=/tmp/dbus-Xx2Kpi...a572]@:1.33/client1
+     */
+
+    p = str;
+    q = addr->db_fqa;
+    l = sizeof(addr->db_fqa);
+
+    /* get bus address */
+    if (*p != '[') {
+        errno = EINVAL;
+        return 0;
+    }
+    else
+        p++;
+
+    e = strchr(p, ']');
+
+    if (e == NULL) {
+        errno = EINVAL;
+        return 0;
+    }
+
+    n = e - p;
+    if (n >= l) {
+        errno = ENAMETOOLONG;
+        return 0;
+    }
+
+    /* save bus address */
+    strncpy(q, p, n);
+    q[n] = '\0';
+    addr->db_bus = q;
+
+    q += n + 1;
+    l -= n + 1;
+    p  = e + 1;
+
+    /* get (local or remote) address on bus */
+    if (*p != '@')
+        addr->db_addr = ANY_ADDRESS;
+    else {
+        p++;
+        e = strchr(p, '/');
+
+        if (e == NULL) {
+            errno = EINVAL;
+            return 0;
+        }
+
+        n = e - p;
+        if (n >= l) {
+            errno = ENAMETOOLONG;
+            return 0;
+        }
+
+        /* save address on bus */
+        strncpy(q, p, n);
+        q[n] = '\0';
+        addr->db_addr = q;
+
+        q += n + 1;
+        l -= n + 1;
+        p  = e;
+    }
+
+    /* get object path */
+    if (*p != '/') {
+        errno = EINVAL;
+        return 0;
+    }
+
+    n = snprintf(q, l, "%s%s", TRANSPORT_PATH, p);
+    if (n >= l) {
+        errno = ENAMETOOLONG;
+        return 0;
+    }
+
+    addr->db_path   = q;
+    addr->db_family = MRP_AF_DBUS;
+
+    return sizeof(*addr);
+}
+
+
+static mrp_dbusaddr_t *copy_address(mrp_dbusaddr_t *dst, mrp_dbusaddr_t *src)
+{
+    char   *p, *q;
+    size_t  l, n;
+
+    dst->db_family = src->db_family;
+
+    /* copy bus address */
+    p = src->db_bus;
+    q = dst->db_fqa;
+    l = sizeof(dst->db_fqa);
+
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_bus = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    /* copy address */
+    p = src->db_addr;
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_addr = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    /* copy path */
+    p = src->db_path;
+    n = strlen(p);
+    if (l < n + 1) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    dst->db_path = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    return dst;
+}
+
+
+static inline int check_address(mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addr;
+
+    return (a && a->db_family == MRP_AF_DBUS && addrlen == sizeof(*a));
+}
+
+
+static size_t peer_address(mrp_sockaddr_t *addrp, const char *sender,
+                           const char *path)
+{
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    const char     *p;
+    char           *q;
+    int             l, n;
+
+    q = addr->db_fqa;
+    l = sizeof(addr->db_fqa);
+    p = ANY_ADDRESS;
+    n = 3;
+
+    addr->db_bus = q;
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    addr->db_addr = q;
+    p = sender;
+    n = strlen(sender);
+
+    if (l < n + 1)
+        return 0;
+
+    memcpy(q, p, n + 1);
+    q += n + 1;
+    l -= n + 1;
+
+    addr->db_path = q;
+    p = path;
+    n = strlen(p);
+
+    if (l < n + 1)
+        return 0;
+
+    memcpy(q, p, n + 1);
+
+    return sizeof(addrp);
+}
+
+
+static socklen_t dbus_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    socklen_t len;
+
+    len = parse_address(str, (mrp_dbusaddr_t *)addr, size);
+
+    if (len > 0) {
+        if (typep != NULL)
+            *typep = DBUS;
+    }
+
+    return len;
+}
+
+
+static int dbus_open(mrp_transport_t *mt)
+{
+    MRP_UNUSED(mt);
+
+    return TRUE;
+}
+
+
+static int dbus_createfrom(mrp_transport_t *mt, void *conn)
+{
+    dbus_t     *t    = (dbus_t *)mt;
+    mrp_dbus_t *dbus = (mrp_dbus_t *)conn;
+
+    t->dbus = mrp_dbus_ref(dbus);
+
+    if (t->dbus != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static int dbus_bind(mrp_transport_t *mt, mrp_sockaddr_t *addrp,
+                     socklen_t addrlen)
+{
+    dbus_t          *t    = (dbus_t *)mt;
+    mrp_dbus_t      *dbus = NULL;
+    mrp_dbusaddr_t  *addr = (mrp_dbusaddr_t *)addrp;
+    int            (*cb)(mrp_dbus_t *, DBusMessage *, void *);
+    const char      *method;
+
+    if (t->bound) {
+        errno = EINVAL;
+        goto fail;
+    }
+
+    if (!check_address(addrp, addrlen)) {
+        errno = EINVAL;
+        goto fail;
+    }
+
+    if (t->dbus == NULL) {
+        dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL);
+
+        if (dbus == NULL) {
+            errno = ECONNRESET;
+            goto fail;
+        }
+        else {
+            t->dbus = dbus;
+
+            if (addr->db_addr != NULL && strcmp(addr->db_addr, ANY_ADDRESS)) {
+                if (!mrp_dbus_acquire_name(t->dbus, addr->db_addr, NULL)) {
+                    errno = EADDRINUSE; /* XXX TODO, should check error... */
+                    goto fail;
+                }
+            }
+        }
+    }
+    else {
+        /* XXX TODO: should check given address against address of the bus */
+    }
+
+    copy_address(&t->local, addr);
+
+    switch (t->mode) {
+    case MRP_TRANSPORT_MODE_DATA:
+        method = TRANSPORT_DATA;
+        cb     = dbus_data_cb;
+        break;
+    case MRP_TRANSPORT_MODE_RAW:
+        method = TRANSPORT_RAW;
+        cb     = dbus_raw_cb;
+        break;
+    case MRP_TRANSPORT_MODE_MSG:
+        method = TRANSPORT_MESSAGE;
+        cb     = dbus_msg_cb;
+        break;
+    default:
+        errno = EPROTOTYPE;
+        goto fail;
+    }
+
+    if (!mrp_dbus_export_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE,
+                                method, cb, t)) {
+        errno = EIO;
+        goto fail;
+    }
+    else {
+        t->bound = TRUE;
+        return TRUE;
+    }
+
+ fail:
+    if (dbus != NULL) {
+        mrp_dbus_unref(dbus);
+        t->dbus = NULL;
+    }
+
+    return FALSE;
+}
+
+
+static int dbus_autobind(mrp_transport_t *mt, mrp_sockaddr_t *addrp)
+{
+    mrp_dbusaddr_t *a = (mrp_dbusaddr_t *)addrp;
+    char            astr[MRP_SOCKADDR_SIZE];
+    mrp_sockaddr_t  addr;
+    socklen_t       alen;
+
+    snprintf(astr, sizeof(astr), "dbus:[%s]/auto/%u", a->db_bus, nauto++);
+
+    alen = dbus_resolve(astr, &addr, sizeof(addr), NULL);
+
+    if (alen > 0)
+        return dbus_bind(mt, &addr, alen);
+    else
+        return FALSE;
+}
+
+
+static void dbus_close(mrp_transport_t *mt)
+{
+    dbus_t         *t = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr;
+    const char     *method;
+    int           (*cb)(mrp_dbus_t *, DBusMessage *, void *);
+
+    if (t->bound) {
+        switch (t->mode) {
+        case MRP_TRANSPORT_MODE_DATA:
+            method = TRANSPORT_DATA;
+            cb     = dbus_data_cb;
+            break;
+        case MRP_TRANSPORT_MODE_RAW:
+            method = TRANSPORT_RAW;
+            cb     = dbus_raw_cb;
+            break;
+        default:
+        case MRP_TRANSPORT_MODE_MSG:
+            method = TRANSPORT_MESSAGE;
+            cb     = dbus_msg_cb;
+        }
+
+        addr = (mrp_dbusaddr_t *)&t->local;
+        mrp_dbus_remove_method(t->dbus, addr->db_path, TRANSPORT_INTERFACE,
+                               method, cb, t);
+    }
+
+    if (t->connected && t->remote.db_addr != NULL)
+        mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t);
+
+    mrp_dbus_unref(t->dbus);
+    t->dbus = NULL;
+}
+
+
+static int dbus_msg_cb(mrp_dbus_t *dbus, DBusMessage *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    mrp_msg_t       *msg;
+
+    MRP_UNUSED(dbus);
+
+    msg = msg_decode(dmsg, &sender_path);
+
+    if (msg != NULL) {
+        sender = dbus_message_get_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvmsg(mt, msg, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvmsgfrom(mt, msg, &addr, alen, mt->user_data);
+                });
+        }
+
+        mrp_msg_unref(msg);
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode message.");
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_data_cb(mrp_dbus_t *dbus, DBusMessage *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    uint16_t         tag;
+    void            *decoded;
+
+    MRP_UNUSED(dbus);
+
+    decoded = data_decode(dmsg, &tag, &sender_path);
+
+    if (decoded != NULL) {
+        sender = dbus_message_get_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvdata(mt, decoded, tag, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvdatafrom(mt, decoded, tag, &addr, alen,
+                                         mt->user_data);
+                });
+        }
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode custom data message.");
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_raw_cb(mrp_dbus_t *dbus, DBusMessage *dmsg, void *user_data)
+{
+    mrp_transport_t *mt = (mrp_transport_t *)user_data;
+    dbus_t          *t  = (dbus_t *)mt;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *sender, *sender_path;
+    void            *data;
+    size_t           size;
+
+    MRP_UNUSED(dbus);
+
+    data = raw_decode(dmsg, &size, &sender_path);
+
+    if (data != NULL) {
+        sender = dbus_message_get_sender(dmsg);
+
+        if (mt->connected) {
+            if (!t->peer_resolved || !strcmp(t->remote.db_addr, sender))
+                MRP_TRANSPORT_BUSY(mt, {
+                        mt->evt.recvraw(mt, data, size, mt->user_data);
+                    });
+        }
+        else {
+            peer_address(&addr, sender, sender_path);
+            alen = sizeof(addr);
+
+            MRP_TRANSPORT_BUSY(mt, {
+                    mt->evt.recvrawfrom(mt, data, size, &addr, alen,
+                                        mt->user_data);
+                });
+        }
+
+        mt->check_destroy(mt);
+    }
+    else {
+        mrp_log_error("Failed to decode raw message.");
+    }
+
+    return TRUE;
+}
+
+
+static void peer_state_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data)
+{
+    dbus_t         *t = (dbus_t *)user_data;
+    mrp_sockaddr_t  addr;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(name);
+
+    if (up) {
+        peer_address(&addr, owner, t->remote.db_path);
+        copy_address(&t->remote, (mrp_dbusaddr_t *)&addr);
+        t->peer_resolved = TRUE;
+    }
+    else {
+        /*
+         * XXX TODO:
+         *     It would be really tempting here to call
+         *         mt->evt.closed(mt, ECONNRESET, mt->user_data)
+         *     to notify the user about the fact our peer went down.
+         *     However, that would not be in line with the other
+         *     transports which call the closed event handler only
+         *     upon foricble transport closes upon errors.
+         *
+         *     The transport interface abstraction (especially the
+         *     available set of events) anyway needs some eyeballing,
+         *     so the right thing to do might be to define a new event
+         *     for disconnection and call the handler for that here...
+         */
+    }
+
+}
+
+
+static int dbus_connect(mrp_transport_t *mt, mrp_sockaddr_t *addrp,
+                        socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+
+    if (!check_address(addrp, addrlen)) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    if (t->dbus == NULL) {
+        t->dbus = mrp_dbus_connect(t->ml, addr->db_bus, NULL);
+
+        if (t->dbus == NULL) {
+            errno = ECONNRESET;
+            return FALSE;
+        }
+    }
+    else {
+        /* XXX TODO: check given address against address of the bus */
+    }
+
+    if (!t->bound)
+        if (!dbus_autobind(mt, addrp))
+            return FALSE;
+
+    if (mrp_dbus_follow_name(t->dbus, addr->db_addr, peer_state_cb, t)) {
+        copy_address(&t->remote, addr);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int dbus_disconnect(mrp_transport_t *mt)
+{
+    dbus_t *t = (dbus_t *)mt;
+
+    if (t->connected && t->remote.db_addr != NULL) {
+        mrp_dbus_forget_name(t->dbus, t->remote.db_addr, peer_state_cb, t);
+        mrp_clear(&t->remote);
+        t->peer_resolved = FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static int dbus_sendmsgto(mrp_transport_t *mt, mrp_msg_t *msg,
+                          mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    DBusMessage    *m;
+    int             success;
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+                return FALSE;
+
+        m = msg_encode(t->local.db_path, msg);
+
+        if (m != NULL) {
+            if (mrp_dbus_send(t->dbus, addr->db_addr, addr->db_path,
+                              TRANSPORT_INTERFACE, TRANSPORT_MESSAGE,
+                              0, NULL, NULL, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            dbus_message_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_sendmsg(mrp_transport_t *mt, mrp_msg_t *msg)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_sendmsgto(mt, msg, addr, alen);
+}
+
+
+static int dbus_sendrawto(mrp_transport_t *mt, void *data, size_t size,
+                          mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    DBusMessage    *m;
+    int             success;
+
+
+    MRP_UNUSED(mt);
+    MRP_UNUSED(data);
+    MRP_UNUSED(size);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+            return FALSE;
+
+        m = raw_encode(t->local.db_path, data, size);
+
+        if (m != NULL) {
+            if (mrp_dbus_send(t->dbus, addr->db_addr, addr->db_path,
+                              TRANSPORT_INTERFACE, TRANSPORT_RAW,
+                              0, NULL, NULL, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            dbus_message_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_sendraw(mrp_transport_t *mt, void *data, size_t size)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_sendrawto(mt, data, size, addr, alen);
+}
+
+
+static int dbus_senddatato(mrp_transport_t *mt, void *data, uint16_t tag,
+                           mrp_sockaddr_t *addrp, socklen_t addrlen)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_dbusaddr_t *addr = (mrp_dbusaddr_t *)addrp;
+    DBusMessage    *m;
+    int             success;
+
+    if (check_address(addrp, addrlen)) {
+        if (t->dbus == NULL && !dbus_autobind(mt, addrp))
+            return FALSE;
+
+        m = data_encode(t->local.db_path, data, tag);
+
+        if (m != NULL) {
+            if (mrp_dbus_send(t->dbus, addr->db_addr, addr->db_path,
+                              TRANSPORT_INTERFACE, TRANSPORT_DATA,
+                              0, NULL, NULL, m))
+                success = TRUE;
+            else {
+                errno   = ECOMM;
+                success = FALSE;
+            }
+
+            dbus_message_unref(m);
+        }
+        else
+            success = FALSE;
+    }
+    else {
+        errno   = EINVAL;
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int dbus_senddata(mrp_transport_t *mt, void *data, uint16_t tag)
+{
+    dbus_t         *t    = (dbus_t *)mt;
+    mrp_sockaddr_t *addr = (mrp_sockaddr_t *)&t->remote;
+    socklen_t       alen = sizeof(t->remote);
+
+    return dbus_senddatato(mt, data, tag, addr, alen);
+}
+
+
+static const char *get_array_signature(uint16_t type)
+{
+#define MAP(from, to)                                 \
+    case MRP_MSG_FIELD_##from:                        \
+        return DBUS_TYPE_##to##_AS_STRING;
+
+    switch (type) {
+        MAP(STRING, STRING);
+        MAP(BOOL  , BOOLEAN);
+        MAP(UINT8 , UINT16);
+        MAP(SINT8 , INT16);
+        MAP(UINT16, UINT16);
+        MAP(SINT16, INT16);
+        MAP(UINT32, UINT32);
+        MAP(SINT32, INT32);
+        MAP(UINT64, UINT64);
+        MAP(SINT64, INT64);
+        MAP(DOUBLE, DOUBLE);
+        MAP(BLOB  , BYTE  );
+    default:
+        return NULL;
+    }
+}
+
+
+static DBusMessage *msg_encode(const char *sender_id, mrp_msg_t *msg)
+{
+    /*
+     * Notes: There is a type mismatch between our and DBUS types for
+     *        8-bit integers (D-BUS does not have a signed 8-bit type)
+     *        and boolean types (D-BUS has uint32_t booleans, C99 fails
+     *        to specify the type and gcc uses a signed char). The
+     *        QUIRKY versions of the macros take care of these mismatches.
+     */
+
+#define BASIC_SIMPLE(_i, _mtype, _dtype, _val)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = DBUS_TYPE_##_dtype;                                    \
+            vptr = &(_val);                                               \
+                                                                          \
+            if (!dbus_message_iter_append_basic(_i, type, vptr))          \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_i, _mtype, _dtype, _mval, _dval)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type  = DBUS_TYPE_##_dtype;                                   \
+            _dval = _mval;                                                \
+            vptr  = &_dval;                                               \
+                                                                          \
+            if (!dbus_message_iter_append_basic(_i, type, vptr))          \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_i, _mtype, _dtype, _val)                            \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type = DBUS_TYPE_##_dtype;                            \
+                    vptr = &_val;                                         \
+                                                                          \
+                    if (!dbus_message_iter_append_basic(_i, type, vptr))  \
+                        goto fail;                                        \
+                    break
+
+#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type  = DBUS_TYPE_##_dtype;                           \
+                    _dvar = _mvar;                                        \
+                    vptr  = &_dvar;                                       \
+                                                                          \
+                    if (!dbus_message_iter_append_basic(_i, type, vptr))  \
+                        goto fail;                                        \
+                    break
+
+    DBusMessage     *m;
+    mrp_list_hook_t *p, *n;
+    mrp_msg_field_t *f;
+    uint16_t         base;
+    uint32_t         asize, i;
+    DBusMessageIter  im, ia;
+    const char      *sig;
+    int              type, len;
+    void            *vptr;
+    dbus_bool_t      bln;
+    uint16_t         u16;
+    int16_t          s16;
+
+    m = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+
+    if (m == NULL)
+        return NULL;
+
+    dbus_message_iter_init_append(m, &im);
+
+    if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_OBJECT_PATH, &sender_id))
+        goto fail;
+
+    if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &msg->nfield))
+        goto fail;
+
+    mrp_list_foreach(&msg->fields, p, n) {
+        f = mrp_list_entry(p, typeof(*f), hook);
+
+        if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->tag) ||
+            !dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->type))
+            goto fail;
+
+        switch (f->type) {
+            BASIC_SIMPLE(&im, STRING, STRING , f->str);
+            BASIC_QUIRKY(&im, BOOL  , BOOLEAN, f->bln, bln);
+            BASIC_QUIRKY(&im, UINT8 , UINT16 , f->u8 , u16);
+            BASIC_QUIRKY(&im, SINT8 ,  INT16 , f->s8 , s16);
+            BASIC_SIMPLE(&im, UINT16, UINT16 , f->u16);
+            BASIC_SIMPLE(&im, SINT16,  INT16 , f->s16);
+            BASIC_SIMPLE(&im, UINT32, UINT32 , f->u32);
+            BASIC_SIMPLE(&im, SINT32,  INT32 , f->s32);
+            BASIC_SIMPLE(&im, UINT64, UINT64 , f->u64);
+            BASIC_SIMPLE(&im, SINT64,  INT64 , f->s64);
+            BASIC_SIMPLE(&im, DOUBLE, DOUBLE , f->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            vptr = f->blb;
+            len  = (int)f->size[0];
+            sig  = get_array_signature(f->type);
+
+            if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY,
+                                                  sig, &ia) ||
+                !dbus_message_iter_append_fixed_array(&ia, sig[0],
+                                                      &vptr, len) ||
+                !dbus_message_iter_close_container(&im, &ia))
+                goto fail;
+            break;
+
+        default:
+            if (f->type & MRP_MSG_FIELD_ARRAY) {
+                base  = f->type & ~(MRP_MSG_FIELD_ARRAY);
+                asize = f->size[0];
+                sig   = get_array_signature(base);
+
+                if (!dbus_message_iter_append_basic(&im,
+                                                    DBUS_TYPE_UINT32, &asize))
+                    goto fail;
+
+                if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY,
+                                                      sig, &ia))
+                    goto fail;
+
+                for (i = 0; i < asize; i++) {
+                    switch (base) {
+                        ARRAY_SIMPLE(&ia, STRING, STRING , f->astr[i]);
+                        ARRAY_QUIRKY(&ia, BOOL  , BOOLEAN, f->abln[i], bln);
+                        ARRAY_QUIRKY(&ia, UINT8 , UINT16 , f->au8[i] , u16);
+                        ARRAY_QUIRKY(&ia, SINT8 ,  INT16 , f->as8[i] , s16);
+                        ARRAY_SIMPLE(&ia, UINT16, UINT16 , f->au16[i]);
+                        ARRAY_SIMPLE(&ia, SINT16,  INT16 , f->as16[i]);
+                        ARRAY_SIMPLE(&ia, UINT32, UINT32 , f->au32[i]);
+                        ARRAY_SIMPLE(&ia, SINT32,  INT32 , f->as32[i]);
+                        ARRAY_SIMPLE(&ia, UINT64, UINT64 , f->au64[i]);
+                        ARRAY_SIMPLE(&ia, DOUBLE, DOUBLE , f->adbl[i]);
+
+                    case MRP_MSG_FIELD_BLOB:
+                        goto fail;
+
+                    default:
+                        goto fail;
+                    }
+                }
+
+                if (!dbus_message_iter_close_container(&im, &ia))
+                    goto fail;
+            }
+            else
+                goto fail;
+        }
+    }
+
+    return m;
+
+ fail:
+    if (m != NULL)
+        dbus_message_unref(m);
+
+    errno = ECOMM;
+
+    return FALSE;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+}
+
+
+static mrp_msg_t *msg_decode(DBusMessage *m, const char **sender_id)
+{
+#define BASIC_SIMPLE(_i, _mtype, _dtype, _var)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \
+                goto fail;                                                \
+            dbus_message_iter_get_basic(_i, &(_var));                     \
+            dbus_message_iter_next(_i);                                   \
+                                                                          \
+            if (!mrp_msg_append(msg, tag, type, (_var)))                  \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \
+                goto fail;                                                \
+            dbus_message_iter_get_basic(_i, &(_dvar));                    \
+            dbus_message_iter_next(_i);                                   \
+                                                                          \
+            _mvar = _dvar;                                                \
+            if (!mrp_msg_append(msg, tag, type, (_mvar)))                 \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_i, _mtype, _dtype, _var)                            \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \
+                goto fail;                                                \
+            dbus_message_iter_get_basic(_i, &(_var));                     \
+            dbus_message_iter_next(_i);                                   \
+            break
+
+#define ARRAY_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                    \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \
+                goto fail;                                                \
+            dbus_message_iter_get_basic(_i, &(_dvar));                    \
+            dbus_message_iter_next(_i);                                   \
+                                                                          \
+            _mvar = _dvar;                                                \
+            break
+
+#define APPEND_ARRAY(_type, _var)                                         \
+                case MRP_MSG_FIELD_##_type:                               \
+                    if (!mrp_msg_append(msg, tag,                         \
+                                        MRP_MSG_FIELD_ARRAY |             \
+                                        MRP_MSG_FIELD_##_type,            \
+                                        n, _var))                         \
+                        goto fail;                                        \
+                    break
+
+    mrp_msg_t       *msg;
+    mrp_msg_value_t  v;
+    uint16_t         u16;
+    int16_t          s16;
+    uint32_t         u32;
+    DBusMessageIter  im, ia;
+    uint16_t         nfield, tag, type, base, i;
+    uint32_t         n, j;
+    int              asize;
+    const char      *sender;
+
+    msg = NULL;
+
+    if (!dbus_message_iter_init(m, &im))
+        goto fail;
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_OBJECT_PATH)
+        goto fail;
+
+    dbus_message_iter_get_basic(&im, &sender);
+    dbus_message_iter_next(&im);
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+        goto fail;
+
+    dbus_message_iter_get_basic(&im, &nfield);
+    dbus_message_iter_next(&im);
+
+    msg = mrp_msg_create_empty();
+
+    if (msg == NULL)
+        goto fail;
+
+    for (i = 0; i < nfield; i++) {
+        if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+            goto fail;
+
+        dbus_message_iter_get_basic(&im, &tag);
+        dbus_message_iter_next(&im);
+
+        if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+            goto fail;
+
+        dbus_message_iter_get_basic(&im, &type);
+        dbus_message_iter_next(&im);
+
+        switch (type) {
+            BASIC_SIMPLE(&im, STRING, STRING , v.str);
+            BASIC_QUIRKY(&im, BOOL  , BOOLEAN, v.bln, u32);
+            BASIC_QUIRKY(&im, UINT8 , UINT16 , v.u8 , u16);
+            BASIC_QUIRKY(&im, SINT8 ,  INT16 , v.s8 , s16);
+            BASIC_SIMPLE(&im, UINT16, UINT16 , v.u16);
+            BASIC_SIMPLE(&im, SINT16,  INT16 , v.s16);
+            BASIC_SIMPLE(&im, UINT32, UINT32 , v.u32);
+            BASIC_SIMPLE(&im, SINT32,  INT32 , v.s32);
+            BASIC_SIMPLE(&im, UINT64, UINT64 , v.u64);
+            BASIC_SIMPLE(&im, SINT64,  INT64 , v.s64);
+            BASIC_SIMPLE(&im, DOUBLE, DOUBLE , v.dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            if (dbus_message_iter_get_element_type(&im) != DBUS_TYPE_BYTE)
+                goto fail;
+
+            dbus_message_iter_recurse(&im, &ia);
+            dbus_message_iter_get_fixed_array(&ia, &v.blb, &asize);
+            dbus_message_iter_next(&im);
+            if (!mrp_msg_append(msg, tag, type, asize, v.blb))
+                goto fail;
+            break;
+
+        default:
+            if (!(type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = type & ~(MRP_MSG_FIELD_ARRAY);
+
+            if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT32)
+                goto fail;
+
+            dbus_message_iter_get_basic(&im, &n);
+            dbus_message_iter_next(&im);
+
+            if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_ARRAY)
+                goto fail;
+            dbus_message_iter_recurse(&im, &ia);
+            dbus_message_iter_next(&im);
+
+            {
+                char     *astr[n];
+                uint32_t  dbln[n];
+                bool      abln[n];
+                uint8_t   au8 [n];
+                int8_t    as8 [n];
+                uint16_t  au16[n];
+                int16_t   as16[n];
+                uint32_t  au32[n];
+                int32_t   as32[n];
+                uint64_t  au64[n];
+                int64_t   as64[n];
+                double    adbl[n];
+
+                for (j = 0; j < n; j++) {
+                    switch (base) {
+                        ARRAY_SIMPLE(&ia, STRING, STRING , astr[j]);
+                        ARRAY_QUIRKY(&ia, BOOL  , BOOLEAN, abln[j], dbln[j]);
+                        ARRAY_QUIRKY(&ia, UINT8 , UINT16 , au8[j] , au16[j]);
+                        ARRAY_QUIRKY(&ia, SINT8 ,  INT16 , as8[j] , as16[j]);
+                        ARRAY_SIMPLE(&ia, UINT16, UINT16 , au16[j]);
+                        ARRAY_SIMPLE(&ia, SINT16,  INT16 , as16[j]);
+                        ARRAY_SIMPLE(&ia, UINT32, UINT32 , au32[j]);
+                        ARRAY_SIMPLE(&ia, SINT32,  INT32 , as32[j]);
+                        ARRAY_SIMPLE(&ia, UINT64, UINT64 , au64[j]);
+                        ARRAY_SIMPLE(&ia, SINT64,  INT64 , as64[j]);
+                        ARRAY_SIMPLE(&ia, DOUBLE, DOUBLE , adbl[j]);
+                    default:
+                        goto fail;
+                    }
+                }
+
+                switch (base) {
+                    APPEND_ARRAY(STRING, astr);
+                    APPEND_ARRAY(BOOL  , abln);
+                    APPEND_ARRAY(UINT8 , au8 );
+                    APPEND_ARRAY(SINT8 , as8 );
+                    APPEND_ARRAY(UINT16, au16);
+                    APPEND_ARRAY(SINT16, as16);
+                    APPEND_ARRAY(UINT32, au32);
+                    APPEND_ARRAY(SINT32, as32);
+                    APPEND_ARRAY(UINT64, au64);
+                    APPEND_ARRAY(SINT64, as64);
+                    APPEND_ARRAY(DOUBLE, adbl);
+                default:
+                    goto fail;
+                }
+            }
+        }
+    }
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return msg;
+
+ fail:
+    mrp_msg_unref(msg);
+    errno = EBADMSG;
+
+    return NULL;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+#undef APPEND_ARRAY
+}
+
+
+static DBusMessage *data_encode(const char *sender_id, void *data, uint16_t tag)
+{
+#define BASIC_SIMPLE(_mtype, _dtype, _val)                                \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type = DBUS_TYPE_##_dtype;                                    \
+            vptr = &(_val);                                               \
+                                                                          \
+            if (!dbus_message_iter_append_basic(&im, type, vptr))         \
+                goto fail;                                                \
+            break
+
+#define BASIC_QUIRKY(_mtype, _dtype, _mval, _dval)                        \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            type  = DBUS_TYPE_##_dtype;                                   \
+            _dval = _mval;                                                \
+            vptr  = &_dval;                                               \
+                                                                          \
+            if (!dbus_message_iter_append_basic(&im, type, vptr))         \
+                goto fail;                                                \
+            break
+
+#define ARRAY_SIMPLE(_mtype, _dtype, _val)                                \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type = DBUS_TYPE_##_dtype;                            \
+                    vptr = &_val;                                         \
+                                                                          \
+                    if (!dbus_message_iter_append_basic(&ia, type, vptr)) \
+                        goto fail;                                        \
+                    break
+
+#define ARRAY_QUIRKY(_mtype, _dtype, _mvar, _dvar)                        \
+                case MRP_MSG_FIELD_##_mtype:                              \
+                    type  = DBUS_TYPE_##_dtype;                           \
+                    _dvar = _mvar;                                        \
+                    vptr = &_dvar;                                        \
+                                                                          \
+                    if (!dbus_message_iter_append_basic(&ia, type, vptr)) \
+                        goto fail;                                        \
+                    break
+
+    DBusMessage       *m;
+    mrp_data_descr_t  *descr;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           type, base;
+    mrp_msg_value_t   *v;
+    void              *vptr;
+    uint32_t           n, j;
+    int                i, blblen;
+    DBusMessageIter    im, ia;
+    const char        *sig;
+    uint16_t           u16;
+    int16_t            s16;
+    uint32_t           bln;
+
+    m = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+
+    if (m == NULL)
+        return NULL;
+
+    descr = mrp_msg_find_type(tag);
+
+    if (descr == NULL)
+        goto fail;
+
+    fields = descr->fields;
+    nfield = descr->nfield;
+
+    dbus_message_iter_init_append(m, &im);
+
+    if (!dbus_message_iter_append_basic(&im,
+                                        DBUS_TYPE_OBJECT_PATH, &sender_id))
+        goto fail;
+
+    if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &tag))
+        goto fail;
+
+    if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &nfield))
+        goto fail;
+
+    for (i = 0, f = fields; i < nfield; i++, f++) {
+        if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->tag) ||
+            !dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT16, &f->type))
+            goto fail;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (f->type) {
+            BASIC_SIMPLE(STRING, STRING , v->str);
+            BASIC_QUIRKY(BOOL  , BOOLEAN, v->bln, bln);
+            BASIC_QUIRKY(UINT8 , UINT16 , v->u8 , u16);
+            BASIC_QUIRKY(SINT8 ,  INT16 , v->s8 , s16);
+            BASIC_SIMPLE(UINT16, UINT16 , v->u16);
+            BASIC_SIMPLE(SINT16,  INT16 , v->s16);
+            BASIC_SIMPLE(UINT32, UINT32 , v->u32);
+            BASIC_SIMPLE(SINT32,  INT32 , v->s32);
+            BASIC_SIMPLE(UINT64, UINT64 , v->u64);
+            BASIC_SIMPLE(SINT64,  INT64 , v->s64);
+            BASIC_SIMPLE(DOUBLE, DOUBLE , v->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            sig    = get_array_signature(f->type);
+            blblen = mrp_data_get_blob_size(data, descr, i);
+
+            if (blblen == -1)
+                goto fail;
+
+            if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY,
+                                                  sig, &ia) ||
+                !dbus_message_iter_append_fixed_array(&ia, sig[0],
+                                                      &f->blb, blblen) ||
+                !dbus_message_iter_close_container(&im, &ia))
+                goto fail;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = f->type & ~(MRP_MSG_FIELD_ARRAY);
+            n    = mrp_data_get_array_size(data, descr, i);
+            sig  = get_array_signature(base);
+
+            if (!dbus_message_iter_append_basic(&im, DBUS_TYPE_UINT32, &n))
+                goto fail;
+
+            if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY,
+                                                  sig, &ia))
+                goto fail;
+
+            for (j = 0; j < n; j++) {
+                switch (base) {
+                    ARRAY_SIMPLE(STRING, STRING , v->astr[j]);
+                    ARRAY_QUIRKY(BOOL  , BOOLEAN, v->abln[j], bln);
+                    ARRAY_QUIRKY(UINT8 , UINT16 , v->au8[j] , u16);
+                    ARRAY_QUIRKY(SINT8 ,  INT16 , v->as8[j] , s16);
+                    ARRAY_SIMPLE(UINT16, UINT16 , v->au16[j]);
+                    ARRAY_SIMPLE(SINT16,  INT16 , v->as16[j]);
+                    ARRAY_SIMPLE(UINT32, UINT32 , v->au32[j]);
+                    ARRAY_SIMPLE(SINT32,  INT32 , v->as32[j]);
+                    ARRAY_SIMPLE(UINT64, UINT64 , v->au64[j]);
+                    ARRAY_SIMPLE(DOUBLE, DOUBLE , v->adbl[j]);
+
+                case MRP_MSG_FIELD_BLOB:
+                    goto fail;
+
+                default:
+                    goto fail;
+                }
+            }
+
+            if (!dbus_message_iter_close_container(&im, &ia))
+                goto fail;
+        }
+    }
+
+    return m;
+
+ fail:
+    if (m != NULL)
+        dbus_message_unref(m);
+
+    errno = ECOMM;
+
+    return NULL;
+
+#undef BASIC_SIMPLE
+#undef BASIC_QUIRKY
+#undef ARRAY_SIMPLE
+#undef ARRAY_QUIRKY
+}
+
+
+static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield,
+                                      uint16_t tag)
+{
+    mrp_data_member_t *f;
+    int                i;
+
+    for (i = 0, f = fields; i < nfield; i++, f++)
+        if (f->tag == tag)
+            return f;
+
+    return NULL;
+}
+
+
+static void *data_decode(DBusMessage *m, uint16_t *tagp, const char **sender_id)
+{
+#define HANDLE_SIMPLE(_i, _mtype, _dtype, _var)                           \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \
+                goto fail;                                                \
+            dbus_message_iter_get_basic(_i, &(_var));                     \
+            dbus_message_iter_next(_i);                                   \
+            break
+
+#define HANDLE_QUIRKY(_i, _mtype, _dtype, _mvar, _dvar)                   \
+        case MRP_MSG_FIELD_##_mtype:                                      \
+            if (dbus_message_iter_get_arg_type(_i) != DBUS_TYPE_##_dtype) \
+                goto fail;                                                \
+            dbus_message_iter_get_basic(_i, &(_dvar));                    \
+            dbus_message_iter_next(_i);                                   \
+                                                                          \
+            _mvar = _dvar;                                                \
+            break
+
+    void              *data;
+    mrp_data_descr_t  *descr;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           tag, type, base;
+    mrp_msg_value_t   *v;
+    uint32_t           n, j, size;
+    int                i, blblen;
+    DBusMessageIter    im, ia;
+    const char        *sender;
+    uint32_t           u32;
+    uint16_t           u16;
+    int16_t            s16;
+
+    tag  = 0;
+    data = NULL;
+
+    if (!dbus_message_iter_init(m, &im))
+        goto fail;
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_OBJECT_PATH)
+        goto fail;
+
+    dbus_message_iter_get_basic(&im, &sender);
+    dbus_message_iter_next(&im);
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+        goto fail;
+
+    dbus_message_iter_get_basic(&im, &tag);
+    dbus_message_iter_next(&im);
+
+    descr = mrp_msg_find_type(tag);
+
+    if (descr == NULL)
+        goto fail;
+
+    *tagp = tag;
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+        goto fail;
+
+    dbus_message_iter_get_basic(&im, &nfield);
+    dbus_message_iter_next(&im);
+
+    if (nfield != descr->nfield)
+        goto fail;
+
+    fields = descr->fields;
+    data   = mrp_allocz(descr->size);
+
+    if (MRP_UNLIKELY(data == NULL))
+        goto fail;
+
+    for (i = 0; i < nfield; i++) {
+        if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+            goto fail;
+
+        dbus_message_iter_get_basic(&im, &tag);
+        dbus_message_iter_next(&im);
+
+        if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT16)
+            goto fail;
+
+        dbus_message_iter_get_basic(&im, &type);
+        dbus_message_iter_next(&im);
+
+        f = member_type(fields, nfield, tag);
+
+        if (MRP_UNLIKELY(f == NULL))
+            goto fail;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (type) {
+            HANDLE_SIMPLE(&im, STRING, STRING , v->str);
+            HANDLE_QUIRKY(&im, BOOL  , BOOLEAN, v->bln, u32);
+            HANDLE_QUIRKY(&im, UINT8 , UINT16 , v->u8 , u16);
+            HANDLE_QUIRKY(&im, SINT8 ,  INT16 , v->s8 , s16);
+            HANDLE_SIMPLE(&im, UINT16, UINT16 , v->u16);
+            HANDLE_SIMPLE(&im, SINT16,  INT16 , v->s16);
+            HANDLE_SIMPLE(&im, UINT32, UINT32 , v->u32);
+            HANDLE_SIMPLE(&im, SINT32,  INT32 , v->s32);
+            HANDLE_SIMPLE(&im, UINT64, UINT64 , v->u64);
+            HANDLE_SIMPLE(&im, SINT64,  INT64 , v->s64);
+            HANDLE_SIMPLE(&im, DOUBLE, DOUBLE , v->dbl);
+
+        case MRP_MSG_FIELD_BLOB:
+            if (dbus_message_iter_get_element_type(&ia) != DBUS_TYPE_BYTE)
+                goto fail;
+
+            dbus_message_iter_recurse(&im, &ia);
+            dbus_message_iter_get_fixed_array(&ia, &v->blb, &blblen);
+            dbus_message_iter_next(&im);
+            v->blb = mrp_datadup(v->blb, blblen);
+            if (v->blb == NULL)
+                goto fail;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY))
+                goto fail;
+
+            base = type & ~(MRP_MSG_FIELD_ARRAY);
+
+            if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_UINT32)
+                goto fail;
+
+            dbus_message_iter_get_basic(&im, &n);
+            dbus_message_iter_next(&im);
+
+            if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_ARRAY)
+                goto fail;
+
+            dbus_message_iter_recurse(&im, &ia);
+            dbus_message_iter_next(&im);
+
+            size = n;
+
+            switch (base) {
+            case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break;
+            case MRP_MSG_FIELD_BOOL:   size *= sizeof(*v->abln); break;
+            case MRP_MSG_FIELD_UINT8:  size *= sizeof(*v->au8);  break;
+            case MRP_MSG_FIELD_SINT8:  size *= sizeof(*v->as8);  break;
+            case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break;
+            case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break;
+            case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break;
+            case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break;
+            case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break;
+            case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break;
+            case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break;
+            default:
+                goto fail;
+            }
+
+            v->aany = mrp_allocz(size);
+            if (v->aany == NULL)
+                goto fail;
+
+            for (j = 0; j < n; j++) {
+                uint32_t  dbln[n];
+                uint16_t  au16[n];
+                int16_t   as16[n];
+
+                switch (base) {
+                    HANDLE_SIMPLE(&ia, STRING, STRING , v->astr[j]);
+                    HANDLE_QUIRKY(&ia, BOOL  , BOOLEAN, v->abln[j], dbln[j]);
+                    HANDLE_QUIRKY(&ia, UINT8 , UINT16 , v->au8[j] , au16[j]);
+                    HANDLE_QUIRKY(&ia, SINT8 ,  INT16 , v->as8[j] , as16[j]);
+                    HANDLE_SIMPLE(&ia, UINT16, UINT16 , v->au16[j]);
+                    HANDLE_SIMPLE(&ia, SINT16,  INT16 , v->as16[j]);
+                    HANDLE_SIMPLE(&ia, UINT32, UINT32 , v->au32[j]);
+                    HANDLE_SIMPLE(&ia, SINT32,  INT32 , v->as32[j]);
+                    HANDLE_SIMPLE(&ia, UINT64, UINT64 , v->au64[j]);
+                    HANDLE_SIMPLE(&ia, SINT64,  INT64 , v->as64[j]);
+                    HANDLE_SIMPLE(&ia, DOUBLE, DOUBLE , v->adbl[j]);
+                }
+
+                if (base == MRP_MSG_FIELD_STRING) {
+                    v->astr[j] = mrp_strdup(v->astr[j]);
+                    if (v->astr[j] == NULL)
+                        goto fail;
+                }
+            }
+        }
+
+        if (f->type == MRP_MSG_FIELD_STRING) {
+            v->str = mrp_strdup(v->str);
+            if (v->str == NULL)
+                goto fail;
+        }
+    }
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return data;
+
+ fail:
+    mrp_data_free(data, tag);
+    errno = EBADMSG;
+
+    return NULL;
+}
+
+
+static DBusMessage *raw_encode(const char *sender_id, void *data, size_t size)
+{
+    DBusMessage     *m;
+    DBusMessageIter  im, ia;
+    const char      *sig;
+    int              len;
+
+    m = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL);
+
+    if (m != NULL) {
+        dbus_message_iter_init_append(m, &im);
+
+        if (!dbus_message_iter_append_basic(&im,
+                                            DBUS_TYPE_OBJECT_PATH, &sender_id))
+            goto fail;
+
+        sig = DBUS_TYPE_BYTE_AS_STRING;
+        len = (int)size;
+
+        if (!dbus_message_iter_open_container(&im, DBUS_TYPE_ARRAY, sig, &ia) ||
+            !dbus_message_iter_append_fixed_array(&ia, sig[0], &data, len) ||
+            !dbus_message_iter_close_container(&im, &ia))
+            goto fail;
+
+        return m;
+    }
+    else
+        return NULL;
+
+ fail:
+    if (m != NULL)
+        dbus_message_unref(m);
+
+    errno = ECOMM;
+
+    return NULL;
+}
+
+
+static void *raw_decode(DBusMessage *m, size_t *sizep, const char **sender_id)
+{
+    DBusMessageIter  im, ia;
+    const char      *sender;
+    void            *data;
+    int              len;
+
+    data = NULL;
+
+    if (!dbus_message_iter_init(m, &im))
+        goto fail;
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_OBJECT_PATH)
+        goto fail;
+
+    dbus_message_iter_get_basic(&im, &sender);
+    dbus_message_iter_next(&im);
+
+    if (dbus_message_iter_get_element_type(&ia) != DBUS_TYPE_BYTE)
+        goto fail;
+
+    if (dbus_message_iter_get_arg_type(&im) != DBUS_TYPE_ARRAY)
+        goto fail;
+
+    dbus_message_iter_recurse(&im, &ia);
+    dbus_message_iter_get_fixed_array(&ia, &data, &len);
+
+    data = mrp_datadup(data, len);
+
+    if (sizep != NULL)
+        *sizep = (size_t)len;
+
+    if (sender_id != NULL)
+        *sender_id = sender;
+
+    return data;
+
+ fail:
+    errno = EBADMSG;
+
+    return NULL;
+}
+
+
+MRP_REGISTER_TRANSPORT(dbus, DBUS, dbus_t, dbus_resolve,
+                       dbus_open, dbus_createfrom, dbus_close, NULL,
+                       dbus_bind, NULL, NULL,
+                       dbus_connect, dbus_disconnect,
+                       dbus_sendmsg, dbus_sendmsgto,
+                       dbus_sendraw, dbus_sendrawto,
+                       dbus_senddata, dbus_senddatato,
+                       NULL, NULL,
+                       NULL, NULL,
+                       NULL, NULL);
+
diff --git a/src/common/dbus-transport.h b/src/common/dbus-transport.h
new file mode 100644 (file)
index 0000000..77b5335
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DBUS_TRANSPORT_H__
+#define __MURPHY_DBUS_TRANSPORT_H__
+
+#include <murphy/common/transport.h>
+
+#define MRP_AF_DBUS 0xDB
+
+#define MRP_DBUSADDR_BASE                                                 \
+    __SOCKADDR_COMMON(db_);                                               \
+    char *db_bus;                               /* D-BUS bus address */   \
+    char *db_addr;                                /* address on bus */    \
+    char *db_path                                /* instance path */      \
+
+typedef struct {
+    MRP_DBUSADDR_BASE;
+} _mrp_dbusaddr_base_t;
+
+
+typedef struct {
+    MRP_DBUSADDR_BASE;
+    char db_fqa[MRP_SOCKADDR_SIZE - sizeof(_mrp_dbusaddr_base_t)];
+} mrp_dbusaddr_t;
+
+
+
+#endif /* __MURPHY_DBUS_TRANSPORT_H__ */
diff --git a/src/common/debug-auto-register.c b/src/common/debug-auto-register.c
new file mode 100644 (file)
index 0000000..974e343
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+static void __attribute__((constructor)) register_debug_data(void)
+{
+    mrp_debug_file_t *df;
+    int               i;
+
+    for (i = 0; (df = debug_files[i]) != NULL; i++)
+        mrp_debug_register_file(df);
+}
+
+static void __attribute__((destructor)) unregister_debug_data(void)
+{
+    mrp_debug_file_t *df;
+    int               i;
+
+    for (i = 0; (df = debug_files[i]) != NULL; i++)
+        mrp_debug_unregister_file(df);
+}
+
diff --git a/src/common/debug-info.h b/src/common/debug-info.h
new file mode 100644 (file)
index 0000000..970d4c9
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DEBUG_INFO_H__
+#define __MURPHY_DEBUG_INFO_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/list.h>
+
+MRP_CDECL_BEGIN
+
+/*
+ * line number information for a single function
+ */
+
+typedef struct {
+    const char *func;                    /* name of the function */
+    int         line;                    /* start at this line */
+} mrp_debug_info_t;
+
+
+/*
+ * funcion - line number mapping for a single file
+ */
+
+typedef struct {
+    mrp_list_hook_t   hook;              /* hook for startup registration */
+    const char       *file;              /* file name */
+    mrp_debug_info_t *info;              /* function information */
+} mrp_debug_file_t;
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_DEBUG_INFO_H__ */
diff --git a/src/common/debug.c b/src/common/debug.c
new file mode 100644 (file)
index 0000000..9f5e091
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE
+#include <link.h>
+#include <elf.h>
+
+#include <stdarg.h>
+#include <limits.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/debug.h>
+
+#define WILDCARD  "*"
+
+int mrp_debug_stamp = 0;                    /* debug config stamp */
+
+static int         debug_enabled;           /* debug messages enabled */
+static mrp_htbl_t *rules_on;                /* enabling rules */
+static mrp_htbl_t *rules_off;               /* disabling rules */
+
+static void free_rule_cb(void *key, void *entry)
+{
+    MRP_UNUSED(key);
+
+    mrp_free(entry);
+}
+
+
+static int init_rules(void)
+{
+    mrp_htbl_config_t hcfg;
+
+    mrp_clear(&hcfg);
+    hcfg.comp = mrp_string_comp;
+    hcfg.hash = mrp_string_hash;
+    hcfg.free = free_rule_cb;
+
+    rules_on  = mrp_htbl_create(&hcfg);
+    rules_off = mrp_htbl_create(&hcfg);
+
+    if (rules_on == NULL || rules_off == NULL)
+        return FALSE;
+    else
+        return TRUE;
+}
+
+
+static void reset_rules(void)
+{
+    if (rules_on != NULL) {
+        mrp_htbl_destroy(rules_on , TRUE);
+        rules_on = NULL;
+    }
+    if (rules_off != NULL) {
+        mrp_htbl_destroy(rules_off, TRUE);
+        rules_off = NULL;
+    }
+}
+
+
+void mrp_debug_reset(void)
+{
+    debug_enabled = FALSE;
+    reset_rules();
+}
+
+
+int mrp_debug_enable(int enabled)
+{
+    int prev = debug_enabled;
+
+    debug_enabled = !!enabled;
+    mrp_log_enable(MRP_LOG_MASK_DEBUG);
+    mrp_debug_stamp++;
+
+    return prev;
+}
+
+
+static int add_rule(const char *func, const char *file, int line, int off)
+{
+    mrp_htbl_t  *ht;
+    char        *rule, *r, buf[PATH_MAX * 2];
+
+    if (rules_on == NULL)
+        if (!init_rules())
+            return FALSE;
+
+    r = rule = NULL;
+
+    if (!off)
+        ht = rules_on;
+    else
+        ht = rules_off;
+
+    if (func != NULL && file == NULL && line == 0) {
+        r    = mrp_htbl_lookup(ht, (void *)func);
+        rule = (char *)func;
+    }
+    else if (func != NULL && file != NULL && line == 0) {
+        snprintf(buf, sizeof(buf), "%s@%s", func, file);
+        r    = mrp_htbl_lookup(ht, (void *)buf);
+        rule = buf;
+    }
+    else if (func == NULL && file != NULL && line == 0) {
+        snprintf(buf, sizeof(buf), "@%s", file);
+        r    = mrp_htbl_lookup(ht, (void *)buf);
+        rule = buf;
+    }
+    else if (func == NULL && file != NULL && line > 0) {
+        snprintf(buf, sizeof(buf), "%s:%d", file, line);
+        r    = mrp_htbl_lookup(ht, (void *)buf);
+        rule = buf;
+    }
+
+    if (r != NULL)
+        return FALSE;
+
+    rule = mrp_strdup(rule);
+    if (rule == NULL)
+        return FALSE;
+
+    if (mrp_htbl_insert(ht, rule, rule)) {
+        mrp_debug_stamp++;
+
+        return TRUE;
+    }
+    else {
+        mrp_free(rule);
+
+        return FALSE;
+    }
+}
+
+
+static int del_rule(const char *func, const char *file, int line, int off)
+{
+    mrp_htbl_t  *ht;
+    char        *r, buf[PATH_MAX * 2];
+
+    if (rules_on == NULL)
+        if (!init_rules())
+            return FALSE;
+
+    r = NULL;
+
+    if (!off)
+        ht = rules_on;
+    else
+        ht = rules_off;
+
+    if (func != NULL && file == NULL && line == 0) {
+        r = mrp_htbl_remove(ht, (void *)func, TRUE);
+    }
+    else if (func != NULL && file != NULL && line == 0) {
+        snprintf(buf, sizeof(buf), "%s@%s", func, file);
+        r = mrp_htbl_remove(ht, (void *)buf, TRUE);
+    }
+    else if (func == NULL && file != NULL && line == 0) {
+        snprintf(buf, sizeof(buf), "@%s", file);
+        r = mrp_htbl_remove(ht, (void *)buf, TRUE);
+    }
+    else if (func == NULL && file != NULL && line > 0) {
+        snprintf(buf, sizeof(buf), "%s:%d", file, line);
+        r = mrp_htbl_remove(ht, (void *)buf, TRUE);
+    }
+
+    if (r != NULL) {
+        mrp_debug_stamp++;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_debug_set_config(const char *cmd)
+{
+    char    buf[2 * PATH_MAX + 1], *colon, *at, *eq;
+    char   *func, *file, *end;
+    size_t  len;
+    int     del, off, line;
+
+    if (*cmd == '+' || *cmd == '-')
+        del = (*cmd++ == '-');
+    else
+        del = FALSE;
+
+    eq = strchr(cmd, '=');
+
+    if (eq == NULL) {
+        strncpy(buf, cmd, sizeof(buf) - 1);
+        buf[sizeof(buf) - 1] = '\0';
+        off = FALSE;
+    }
+    else {
+        if (!strcmp(eq + 1, "on"))
+            off = FALSE;
+        else if (!strcmp(eq + 1, "off"))
+            off = TRUE;
+        else
+            return FALSE;
+
+        len = eq - cmd;
+        if (len >= sizeof(buf))
+            len = sizeof(buf) - 1;
+
+        strncpy(buf, cmd, len);
+        buf[len] = '\0';
+    }
+
+    colon = strchr(buf, ':');
+
+    if (colon != NULL) {
+        if (strchr(buf, '@') != NULL)
+            return FALSE;
+
+        *colon = '\0';
+        func   = NULL;
+        file   = buf;
+        line   = strtoul(colon + 1, &end, 10);
+
+        if (end && *end)
+            return FALSE;
+
+        mrp_log_info("%s file='%s', line=%d, %s", del ? "del" : "add",
+                     file, line, off ? "off" : "on");
+    }
+    else {
+        at = strchr(buf, '@');
+
+        if (at != NULL) {
+            *at = '\0';
+            func = (at == buf ? NULL : buf);
+            file = at + 1;
+            line = 0;
+
+            mrp_log_info("%s func='%s', file='%s', %s", del ? "del" : "add",
+                         func ? func : "", file, off ? "off" : "on");
+        }
+        else {
+            func = buf;
+            file = NULL;
+            line = 0;
+
+            mrp_log_info("%s func='%s' %s", del ? "del" : "add",
+                         func, off ? "off" : "on");
+        }
+    }
+
+    if (!del)
+        return add_rule(func, file, line, off);
+    else
+        return del_rule(func, file, line, off);
+
+    return TRUE;
+}
+
+
+typedef struct {
+    FILE *fp;
+    int   on;
+} dump_t;
+
+
+static int dump_rule_cb(void *key, void *object, void *user_data)
+{
+    dump_t     *d     = (dump_t *)user_data;
+    FILE       *fp    = d->fp;
+    const char *state = d->on ? "on" : "off";
+
+    MRP_UNUSED(key);
+
+    fprintf(fp, "    %s %s\n", (char *)object, state);
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+int mrp_debug_dump_config(FILE *fp)
+{
+    dump_t d;
+
+    fprintf(fp, "Debugging is %sabled\n", debug_enabled ? "en" : "dis");
+
+    if (rules_on != NULL) {
+        fprintf(fp, "Debugging rules:\n");
+
+        d.fp = fp;
+        d.on = TRUE;
+        mrp_htbl_foreach(rules_on , dump_rule_cb, &d);
+        d.on = FALSE;
+        mrp_htbl_foreach(rules_off, dump_rule_cb, &d);
+    }
+    else
+        fprintf(fp, "No debugging rules defined.\n");
+
+    return TRUE;
+}
+
+
+void mrp_debug_msg(const char *file, int line, const char *func,
+                   const char *format, ...)
+{
+    va_list ap;
+
+    va_start(ap, format);
+    mrp_log_msgv(MRP_LOG_DEBUG, file, line, func, format, ap);
+    va_end(ap);
+}
+
+
+int mrp_debug_check(const char *func, const char *file, int line)
+{
+    char  buf[2 * PATH_MAX], *base;
+    void *key;
+
+    if (!debug_enabled || rules_on == NULL)
+        return FALSE;
+
+    base = NULL;
+    key  = (void *)func;
+    if (mrp_htbl_lookup(rules_on, key) != NULL)
+        goto check_suppress;
+
+    base = strrchr(file, '/');
+    if (base != NULL)
+        base++;
+
+    key = buf;
+
+    snprintf(buf, sizeof(buf), "@%s", file);
+    if (mrp_htbl_lookup(rules_on, key) != NULL)
+        goto check_suppress;
+
+    if (base != NULL) {
+        snprintf(buf, sizeof(buf), "@%s", base);
+        if (mrp_htbl_lookup(rules_on, key) != NULL)
+            goto check_suppress;
+    }
+
+    snprintf(buf, sizeof(buf), "%s@%s", func, file);
+    if (mrp_htbl_lookup(rules_on, key) != NULL)
+        goto check_suppress;
+
+    snprintf(buf, sizeof(buf), "%s:%d", file, line);
+    if (mrp_htbl_lookup(rules_on, key) != NULL)
+        goto check_suppress;
+
+    if (mrp_htbl_lookup(rules_on, (void *)WILDCARD) == NULL)
+        return FALSE;
+
+
+ check_suppress:
+    if (rules_off == NULL)
+        return TRUE;
+
+    key = (void *)func;
+    if (mrp_htbl_lookup(rules_off, key) != NULL)
+        return FALSE;
+
+    key = buf;
+
+    snprintf(buf, sizeof(buf), "@%s", file);
+    if (mrp_htbl_lookup(rules_off, key) != NULL)
+        return FALSE;
+
+    if (base != NULL) {
+        snprintf(buf, sizeof(buf), "@%s", base);
+        if (mrp_htbl_lookup(rules_off, key) != NULL)
+            return FALSE;
+    }
+
+    snprintf(buf, sizeof(buf), "%s@%s", func, file);
+    if (mrp_htbl_lookup(rules_off, key) != NULL)
+        return FALSE;
+
+    snprintf(buf, sizeof(buf), "%s:%d", file, line);
+    if (mrp_htbl_lookup(rules_off, key) != NULL)
+        return FALSE;
+
+    return TRUE;
+}
+
+
diff --git a/src/common/debug.h b/src/common/debug.h
new file mode 100644 (file)
index 0000000..3828402
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DEBUG_H__
+#define __MURPHY_DEBUG_H__
+
+#include <stdio.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug-info.h>
+
+MRP_CDECL_BEGIN
+
+/** Log a debug message if the invoking debug site is enabled. */
+#define mrp_debug(fmt, args...)        do {                               \
+        static int __site_stamp = -1;                                     \
+        static int __site_enabled;                                        \
+                                                                          \
+        if (MRP_UNLIKELY(__site_stamp != mrp_debug_stamp)) {              \
+            __site_enabled = mrp_debug_check(__FUNCTION__,                \
+                                             __FILE__, __LINE__);         \
+            __site_stamp   = mrp_debug_stamp;                             \
+        }                                                                 \
+                                                                          \
+        if (MRP_UNLIKELY(__site_enabled))                                 \
+            mrp_debug_msg(__LOC__, fmt, ## args);                         \
+    } while (0)
+
+
+/** mrp_debug variant with explicitly passed site info. */
+#define mrp_debug_at(_file, _line, _func, fmt, args...)        do {       \
+        static int __site_stamp = -1;                                     \
+        static int __site_enabled;                                        \
+                                                                          \
+        if (MRP_UNLIKELY(__site_stamp != mrp_debug_stamp)) {              \
+            __site_enabled = mrp_debug_check(_func, _file, _line);        \
+            __site_stamp   = mrp_debug_stamp;                             \
+        }                                                                 \
+                                                                          \
+        if (MRP_UNLIKELY(__site_enabled))                                 \
+            mrp_debug_msg(_file, _line, _func, fmt, ## args);             \
+    } while (0)
+
+
+/** Run a block of code if the invoking debug site is enabled. */
+#define mrp_debug_code(...)         do {                                  \
+        static int __site_stamp = -1;                                     \
+        static int __site_enabled;                                        \
+                                                                          \
+        if (MRP_UNLIKELY(__site_stamp != mrp_debug_stamp)) {              \
+            __site_enabled = mrp_debug_check(__FUNCTION__,                \
+                                             __FILE__, __LINE__);         \
+            __site_stamp   = mrp_debug_stamp;                             \
+        }                                                                 \
+                                                                          \
+        if (MRP_UNLIKELY(__site_enabled)) {                               \
+            __VA_ARGS__;                                                  \
+        }                                                                 \
+    } while (0)
+
+/** Global debug configuration stamp, exported for minimum-overhead checking. */
+extern int mrp_debug_stamp;
+
+/** Enable/disable debug messages globally. */
+int mrp_debug_enable(int enabled);
+
+/** Reset all debug configuration to the defaults. */
+void mrp_debug_reset(void);
+
+/** Apply the debug configuration settings given in cmd. */
+int mrp_debug_set_config(const char *cmd);
+
+/** Dump the active debug configuration. */
+int mrp_debug_dump_config(FILE *fp);
+
+/** Low-level log wrapper for debug messages. */
+void mrp_debug_msg(const char *file, int line, const char *func,
+                   const char *format, ...) MRP_PRINTF_LIKE(4, 5);
+
+/** Check if the given debug site is enabled. */
+int mrp_debug_check(const char *func, const char *file, int line);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_DEBUG_H__ */
diff --git a/src/common/dgram-transport.c b/src/common/dgram-transport.c
new file mode 100644 (file)
index 0000000..b54ed62
--- /dev/null
@@ -0,0 +1,866 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+
+#ifndef UNIX_PATH_MAX
+#    define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)NULL)->sun_path)
+#endif
+
+#define UDP4  "udp4"
+#define UDP4L 4
+#define UDP6  "udp6"
+#define UDP6L 4
+#define UNXD  "unxd"
+#define UNXDL 4
+
+
+#define DEFAULT_SIZE 1024                /* default input buffer size */
+
+typedef struct {
+    MRP_TRANSPORT_PUBLIC_FIELDS;         /* common transport fields */
+    int             sock;                /* UDP socket */
+    int             family;              /* socket family */
+    mrp_io_watch_t *iow;                 /* socket I/O watch */
+    void           *ibuf;                /* input buffer */
+    size_t          isize;               /* input buffer size */
+    size_t          idata;               /* amount of input data */
+} dgrm_t;
+
+
+static void dgrm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                         void *user_data);
+static int dgrm_disconnect(mrp_transport_t *mu);
+static int open_socket(dgrm_t *u, int family);
+
+
+/*
+ * XXX TODO:
+ *
+ *     There is an almost verbatim copy of this in stream-transport.c
+ *     The only differences are the actual address type specifier
+ *     prefixes... Combine these and separate the result out to a
+ *     new transport-priv.[hc].
+ */
+
+
+static int parse_address(const char *str, int *familyp, char *nodep,
+                         size_t nsize, char **servicep, const char **typep)
+{
+    char       *node, *service;
+    const char *type;
+    int         family;
+    size_t      l, nl;
+
+    node = (char *)str;
+
+    if (!strncmp(node, UDP4":", l=UDP4L+1)) {
+        family = AF_INET;
+        type   = UDP4;
+        node  += l;
+    }
+    else if (!strncmp(node, UDP6":", l=UDP6L+1)) {
+        family = AF_INET6;
+        type   = UDP6;
+        node  += l;
+    }
+    else if (!strncmp(node, UNXD":", l=UNXDL+1)) {
+        family = AF_UNIX;
+        type   = UNXD;
+        node  += l;
+    }
+    else {
+        if      (node[0] == '[') family = AF_INET6;
+        else if (node[0] == '/') family = AF_UNIX;
+        else if (node[0] == '@') family = AF_UNIX;
+        else                     family = AF_UNSPEC;
+
+        type = NULL;
+    }
+
+    switch (family) {
+    case AF_INET:
+        service = strrchr(node, ':');
+        if (service == NULL) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        nl = service - node;
+        service++;
+
+    case AF_INET6:
+        service = strrchr(node, ':');
+
+        if (service == NULL || service == node) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        if (node[0] == '[') {
+            node++;
+
+            if (service[-1] != ']') {
+                errno = EINVAL;
+                return -1;
+            }
+
+            nl = service - node - 1;
+        }
+        else
+            nl = service - node;
+        service++;
+        break;
+
+    case AF_UNSPEC:
+        if (!strncmp(node, "tcp:", l=4))
+            node += l;
+        service = strrchr(node, ':');
+
+        if (service == NULL || service == node) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        if (node[0] == '[') {
+            node++;
+            family = AF_INET6;
+
+            if (service[-1] != ']') {
+                errno = EINVAL;
+                return -1;
+            }
+
+            nl = service - node - 1;
+        }
+        else {
+            family = AF_INET;
+            nl = service - node;
+        }
+        service++;
+        break;
+
+    case AF_UNIX:
+        service = NULL;
+        nl      = strlen(node);
+    }
+
+    if (nl >= nsize) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    strncpy(nodep, node, nl);
+    nodep[nl] = '\0';
+    *servicep = service;
+    *familyp  = family;
+    if (typep != NULL)
+        *typep = type;
+
+    return 0;
+}
+
+
+static socklen_t dgrm_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    struct addrinfo    *ai, hints;
+    struct sockaddr_un *un;
+    char                node[UNIX_PATH_MAX], *port;
+    socklen_t           len;
+
+    mrp_clear(&hints);
+
+    if (parse_address(str, &hints.ai_family, node, sizeof(node),
+                      &port, typep) < 0)
+        return 0;
+
+    switch (hints.ai_family) {
+    case AF_UNIX:
+        un  = &addr->unx;
+        len = MRP_OFFSET(typeof(*un), sun_path) + strlen(node) + 1;
+
+        if (size < len)
+            errno = ENOMEM;
+        else {
+            un->sun_family = AF_UNIX;
+            strncpy(un->sun_path, node, UNIX_PATH_MAX-1);
+            if (un->sun_path[0] == '@')
+                un->sun_path[0] = '\0';
+        }
+
+        /* When binding the socket, we don't need the null at the end */
+        len--;
+
+        break;
+
+    case AF_INET:
+    case AF_INET6:
+    default:
+        if (getaddrinfo(node, port, &hints, &ai) == 0) {
+            if (ai->ai_addrlen <= size) {
+                memcpy(addr, ai->ai_addr, ai->ai_addrlen);
+                len = ai->ai_addrlen;
+            }
+            else
+                len = 0;
+
+            freeaddrinfo(ai);
+        }
+        else
+            len = 0;
+    }
+
+    return len;
+}
+
+
+static int dgrm_open(mrp_transport_t *mu)
+{
+    dgrm_t *u = (dgrm_t *)mu;
+
+    u->sock   = -1;
+    u->family = -1;
+
+    return TRUE;
+}
+
+
+static int dgrm_createfrom(mrp_transport_t *mu, void *conn)
+{
+    dgrm_t         *u = (dgrm_t *)mu;
+    int             on;
+    mrp_io_event_t  events;
+
+    u->sock = *(int *)conn;
+
+    if (u->sock >= 0) {
+        if (mu->flags & MRP_TRANSPORT_REUSEADDR) {
+            on = 1;
+            setsockopt(u->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+        }
+        if (mu->flags & MRP_TRANSPORT_NONBLOCK) {
+            on = 1;
+            fcntl(u->sock, F_SETFL, O_NONBLOCK, on);
+        }
+
+        events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+        u->iow = mrp_add_io_watch(u->ml, u->sock, events, dgrm_recv_cb, u);
+
+        if (u->iow != NULL)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_bind(mrp_transport_t *mu, mrp_sockaddr_t *addr,
+                     socklen_t addrlen)
+{
+    dgrm_t *u = (dgrm_t *)mu;
+
+    if (u->sock != -1 || !u->connected) {
+        if (open_socket(u, addr->any.sa_family))
+            if (bind(u->sock, &addr->any, addrlen) == 0)
+                return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_listen(mrp_transport_t *mt, int backlog)
+{
+    MRP_UNUSED(mt);
+    MRP_UNUSED(backlog);
+
+    return TRUE;            /* can be connected to without listening */
+}
+
+
+static void dgrm_close(mrp_transport_t *mu)
+{
+    dgrm_t *u = (dgrm_t *)mu;
+
+    mrp_del_io_watch(u->iow);
+    u->iow = NULL;
+
+    mrp_free(u->ibuf);
+    u->ibuf  = NULL;
+    u->isize = 0;
+    u->idata = 0;
+
+    if (u->sock >= 0){
+        close(u->sock);
+        u->sock = -1;
+    }
+}
+
+
+static void dgrm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                         void *user_data)
+{
+    dgrm_t          *u  = (dgrm_t *)user_data;
+    mrp_transport_t *mu = (mrp_transport_t *)u;
+    mrp_sockaddr_t   addr;
+    socklen_t        addrlen;
+    uint32_t         size;
+    ssize_t          n;
+    void            *data;
+    int              old, error;
+
+    MRP_UNUSED(w);
+
+    if (events & MRP_IO_EVENT_IN) {
+        if (u->idata == u->isize) {
+            if (u->isize != 0) {
+                old      = u->isize;
+                u->isize *= 2;
+            }
+            else {
+                old      = 0;
+                u->isize = DEFAULT_SIZE;
+            }
+            if (!mrp_reallocz(u->ibuf, old, u->isize)) {
+                error = ENOMEM;
+            fatal_error:
+            closed:
+                dgrm_disconnect(mu);
+
+                if (u->evt.closed != NULL)
+                    MRP_TRANSPORT_BUSY(mu, {
+                            mu->evt.closed(mu, error, mu->user_data);
+                        });
+
+                u->check_destroy(mu);
+                return;
+            }
+        }
+
+        if (recv(fd, &size, sizeof(size), MSG_PEEK) != sizeof(size)) {
+            error = EIO;
+            goto fatal_error;
+        }
+
+        size = ntohl(size);
+
+        if (u->isize < size + sizeof(size)) {
+            old      = u->isize;
+            u->isize = size + sizeof(size);
+
+            if (!mrp_reallocz(u->ibuf, old, u->isize)) {
+                error = ENOMEM;
+                goto fatal_error;
+            }
+        }
+
+        addrlen = sizeof(addr);
+        n = recvfrom(fd, u->ibuf, size + sizeof(size), 0, &addr.any, &addrlen);
+
+        if (n != (ssize_t)(size + sizeof(size))) {
+            error = n < 0 ? EIO : EPROTO;
+            goto fatal_error;
+        }
+
+        data  = u->ibuf + sizeof(size);
+        error = mu->recv_data(mu, data, size, &addr, addrlen);
+
+        if (error)
+            goto fatal_error;
+
+        if (u->check_destroy(mu))
+            return;
+    }
+
+    if (events & MRP_IO_EVENT_HUP) {
+        error = 0;
+        goto closed;
+    }
+}
+
+
+static int open_socket(dgrm_t *u, int family)
+{
+    mrp_io_event_t events;
+    int            on;
+    long           nb;
+
+    u->sock = socket(family, SOCK_DGRAM, 0);
+
+    if (u->sock != -1) {
+        if (u->flags & MRP_TRANSPORT_REUSEADDR) {
+            on = 1;
+            setsockopt(u->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+        }
+        if (u->flags & MRP_TRANSPORT_NONBLOCK) {
+            nb = 1;
+            fcntl(u->sock, F_SETFL, O_NONBLOCK, nb);
+        }
+        if (u->flags & MRP_TRANSPORT_CLOEXEC) {
+            on = 1;
+            fcntl(u->sock, F_SETFL, O_CLOEXEC, on);
+        }
+
+        events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+        u->iow = mrp_add_io_watch(u->ml, u->sock, events, dgrm_recv_cb, u);
+
+        if (u->iow != NULL)
+            return TRUE;
+        else {
+            close(u->sock);
+            u->sock = -1;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_connect(mrp_transport_t *mu, mrp_sockaddr_t *addr,
+                        socklen_t addrlen)
+{
+    dgrm_t *u = (dgrm_t *)mu;
+    int     on;
+    long    nb;
+
+    if (MRP_UNLIKELY(u->family != -1 && u->family != addr->any.sa_family))
+        return FALSE;
+
+    if (MRP_UNLIKELY(u->sock == -1)) {
+        if (!open_socket(u, addr->any.sa_family))
+            return FALSE;
+    }
+
+    if (connect(u->sock, &addr->any, addrlen) == 0) {
+        on = 1;
+        setsockopt(u->sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+        nb = 1;
+        fcntl(u->sock, F_SETFL, O_NONBLOCK, nb);
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_disconnect(mrp_transport_t *mu)
+{
+    dgrm_t          *u    = (dgrm_t *)mu;
+    struct sockaddr  none = { .sa_family = AF_UNSPEC, };
+
+
+    if (u->connected) {
+        connect(u->sock, &none, sizeof(none));
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int dgrm_send(mrp_transport_t *mu, mrp_msg_t *msg)
+{
+    dgrm_t       *u = (dgrm_t *)mu;
+    struct iovec  iov[2];
+    void         *buf;
+    ssize_t       size, n;
+    uint32_t      len;
+
+    if (u->connected) {
+        size = mrp_msg_default_encode(msg, &buf);
+
+        if (size >= 0) {
+            len = htonl(size);
+            iov[0].iov_base = &len;
+            iov[0].iov_len  = sizeof(len);
+            iov[1].iov_base = buf;
+            iov[1].iov_len  = size;
+
+            n = writev(u->sock, iov, 2);
+            mrp_free(buf);
+
+            if (n == (ssize_t)(size + sizeof(len)))
+                return TRUE;
+            else {
+                if (n == -1 && errno == EAGAIN) {
+                    mrp_log_error("%s(): XXX TODO: this sucks, need to add "
+                                  "output queuing for dgrm-transport.",
+                                  __FUNCTION__);
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_sendto(mrp_transport_t *mu, mrp_msg_t *msg,
+                       mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    dgrm_t          *u = (dgrm_t *)mu;
+    struct iovec     iov[2];
+    void            *buf;
+    ssize_t          size, n;
+    uint32_t         len;
+    struct msghdr    hdr;
+
+    if (MRP_UNLIKELY(u->sock == -1)) {
+        if (!open_socket(u, ((struct sockaddr *)addr)->sa_family))
+            return FALSE;
+    }
+
+    size = mrp_msg_default_encode(msg, &buf);
+
+    if (size >= 0) {
+        len = htonl(size);
+        iov[0].iov_base = &len;
+        iov[0].iov_len  = sizeof(len);
+        iov[1].iov_base = buf;
+        iov[1].iov_len  = size;
+
+        hdr.msg_name    = addr;
+        hdr.msg_namelen = addrlen;
+        hdr.msg_iov     = iov;
+        hdr.msg_iovlen  = MRP_ARRAY_SIZE(iov);
+
+        hdr.msg_control    = NULL;
+        hdr.msg_controllen = 0;
+        hdr.msg_flags      = 0;
+
+        n = sendmsg(u->sock, &hdr, 0);
+        mrp_free(buf);
+
+        if (n == (ssize_t)(size + sizeof(len)))
+            return TRUE;
+        else {
+            if (n == -1 && errno == EAGAIN) {
+                mrp_log_error("%s(): XXX TODO: dgrm-transport send failed",
+                              __FUNCTION__);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_sendraw(mrp_transport_t *mu, void *data, size_t size)
+{
+    dgrm_t  *u = (dgrm_t *)mu;
+    ssize_t  n;
+
+    if (u->connected) {
+        n = write(u->sock, data, size);
+
+        if (n == (ssize_t)size)
+            return TRUE;
+        else {
+            if (n == -1 && errno == EAGAIN) {
+                mrp_log_error("%s(): XXX TODO: this sucks, need to add "
+                              "output queuing for dgrm-transport.",
+                              __FUNCTION__);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_sendrawto(mrp_transport_t *mu, void *data, size_t size,
+                          mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    dgrm_t  *u = (dgrm_t *)mu;
+    ssize_t  n;
+
+    if (MRP_UNLIKELY(u->sock == -1)) {
+        if (!open_socket(u, ((struct sockaddr *)addr)->sa_family))
+            return FALSE;
+    }
+
+    n = sendto(u->sock, data, size, 0, &addr->any, addrlen);
+
+    if (n == (ssize_t)size)
+        return TRUE;
+    else {
+        if (n == -1 && errno == EAGAIN) {
+            mrp_log_error("%s(): XXX TODO: dgrm-transport send failed",
+                          __FUNCTION__);
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int senddatato(mrp_transport_t *mu, void *data, uint16_t tag,
+                      mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    dgrm_t           *u = (dgrm_t *)mu;
+    mrp_data_descr_t *type;
+    ssize_t           n;
+    void             *buf;
+    size_t            size, reserve, len;
+    uint32_t         *lenp;
+    uint16_t         *tagp;
+
+    if (MRP_UNLIKELY(u->sock == -1)) {
+        if (!open_socket(u, ((struct sockaddr *)addr)->sa_family))
+            return FALSE;
+    }
+
+    type = mrp_msg_find_type(tag);
+
+    if (type != NULL) {
+        reserve = sizeof(*lenp) + sizeof(*tagp);
+        size    = mrp_data_encode(&buf, data, type, reserve);
+
+        if (size > 0) {
+            lenp  = buf;
+            len   = size - sizeof(*lenp);
+            tagp  = buf + sizeof(*lenp);
+            *lenp = htobe32(len);
+            *tagp = htobe16(tag);
+
+            if (u->connected)
+                n = send(u->sock, buf, len + sizeof(*lenp), 0);
+            else
+                n = sendto(u->sock, buf, len + sizeof(*lenp), 0, &addr->any,
+                           addrlen);
+
+            mrp_free(buf);
+
+            if (n == (ssize_t)(len + sizeof(*lenp)))
+                return TRUE;
+            else {
+                if (n == -1 && errno == EAGAIN) {
+                    mrp_log_error("%s(): XXX TODO: dgrm-transport send"
+                                  " needs queuing", __FUNCTION__);
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_senddata(mrp_transport_t *mu, void *data, uint16_t tag)
+{
+    if (mu->connected)
+        return senddatato(mu, data, tag, NULL, 0);
+    else
+        return FALSE;
+}
+
+
+static int dgrm_senddatato(mrp_transport_t *mu, void *data, uint16_t tag,
+                           mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    return senddatato(mu, data, tag, addr, addrlen);
+}
+
+
+static int sendnativeto(mrp_transport_t *mu, void *data, uint32_t type_id,
+                        mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    dgrm_t        *u   = (dgrm_t *)mu;
+    mrp_typemap_t *map = u->map;
+    void          *buf;
+    size_t         size, reserve;
+    uint32_t      *lenp;
+    ssize_t        n;
+
+    if (MRP_UNLIKELY(u->sock == -1)) {
+        if (!open_socket(u, ((struct sockaddr *)addr)->sa_family))
+            return FALSE;
+    }
+
+    reserve = sizeof(*lenp);
+
+    if (mrp_encode_native(data, type_id, reserve, &buf, &size, map) > 0) {
+        lenp  = buf;
+        *lenp = htobe32(size - sizeof(*lenp));
+
+        if (u->connected)
+            n = send(u->sock, buf, size, 0);
+        else
+            n = sendto(u->sock, buf, size, 0, &addr->any, addrlen);
+
+        mrp_free(buf);
+
+        if (n == (ssize_t)size)
+            return TRUE;
+        else {
+            if (n == -1 && errno == EAGAIN) {
+                mrp_log_error("%s(): XXX TODO: dgrm-transport send"
+                              " needs queuing", __FUNCTION__);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_sendnative(mrp_transport_t *mu, void *data, uint32_t type_id)
+{
+    if (mu->connected)
+        return sendnativeto(mu, data, type_id, NULL, 0);
+    else
+        return FALSE;
+}
+
+
+static int dgrm_sendnativeto(mrp_transport_t *mu, void *data, uint32_t type_id,
+                             mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    return sendnativeto(mu, data, type_id, addr, addrlen);
+}
+
+
+static int sendjsonto(mrp_transport_t *mu, mrp_json_t *msg,
+                      mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    dgrm_t       *u = (dgrm_t *)mu;
+    struct iovec  iov[2];
+    const char   *s;
+    ssize_t       size, n;
+    uint32_t      len;
+
+    if (MRP_UNLIKELY(u->sock == -1)) {
+        if (!open_socket(u, ((struct sockaddr *)addr)->sa_family))
+            return FALSE;
+    }
+
+    if (u->connected && (s = mrp_json_object_to_string(msg)) != NULL) {
+        size = strlen(s);
+        len  = htobe32(size);
+
+        iov[0].iov_base = &len;
+        iov[0].iov_len  = sizeof(len);
+        iov[1].iov_base = (char *)s;
+        iov[1].iov_len  = size;
+
+        if (u->connected)
+            n = writev(u->sock, iov, 2);
+        else {
+            struct msghdr mh;
+
+            mh.msg_name       = &addr->any;
+            mh.msg_namelen    = addrlen;
+            mh.msg_iov        = iov;
+            mh.msg_iovlen     = MRP_ARRAY_SIZE(iov);
+            mh.msg_control    = NULL;
+            mh.msg_controllen = 0;
+            mh.msg_flags      = 0;
+
+            n = sendmsg(u->sock, &mh, 0);
+        }
+
+        if (n == (ssize_t)(size + sizeof(len)))
+            return TRUE;
+        else {
+            if (n == -1 && errno == EAGAIN) {
+                mrp_log_error("%s(): XXX TODO: this sucks, need to add "
+                              "output queuing for dgrm-transport.",
+                              __FUNCTION__);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int dgrm_sendjson(mrp_transport_t *mu, mrp_json_t *msg)
+{
+    if (mu->connected)
+        return sendjsonto(mu, msg, NULL, 0);
+    else
+        return FALSE;
+}
+
+
+static int dgrm_sendjsonto(mrp_transport_t *mu, mrp_json_t *msg,
+                           mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    return sendjsonto(mu, msg, addr, addrlen);
+}
+
+
+MRP_REGISTER_TRANSPORT(udp4, UDP4, dgrm_t, dgrm_resolve,
+                       dgrm_open, dgrm_createfrom, dgrm_close, NULL,
+                       dgrm_bind, dgrm_listen, NULL,
+                       dgrm_connect, dgrm_disconnect,
+                       dgrm_send, dgrm_sendto,
+                       dgrm_sendraw, dgrm_sendrawto,
+                       dgrm_senddata, dgrm_senddatato,
+                       NULL, NULL,
+                       dgrm_sendnative, dgrm_sendnativeto,
+                       dgrm_sendjson, dgrm_sendjsonto);
+
+MRP_REGISTER_TRANSPORT(udp6, UDP6, dgrm_t, dgrm_resolve,
+                       dgrm_open, dgrm_createfrom, dgrm_close, NULL,
+                       dgrm_bind, dgrm_listen, NULL,
+                       dgrm_connect, dgrm_disconnect,
+                       dgrm_send, dgrm_sendto,
+                       dgrm_sendraw, dgrm_sendrawto,
+                       dgrm_senddata, dgrm_senddatato,
+                       NULL, NULL,
+                       dgrm_sendnative, dgrm_sendnativeto,
+                       dgrm_sendjson, dgrm_sendjsonto);
+
+MRP_REGISTER_TRANSPORT(unxdgrm, UNXD, dgrm_t, dgrm_resolve,
+                       dgrm_open, dgrm_createfrom, dgrm_close, NULL,
+                       dgrm_bind, dgrm_listen, NULL,
+                       dgrm_connect, dgrm_disconnect,
+                       dgrm_send, dgrm_sendto,
+                       dgrm_sendraw, dgrm_sendrawto,
+                       dgrm_senddata, dgrm_senddatato,
+                       NULL, NULL,
+                       dgrm_sendnative, dgrm_sendnativeto,
+                       dgrm_sendjson, dgrm_sendjsonto);
diff --git a/src/common/ecore-glue.c b/src/common/ecore-glue.c
new file mode 100644 (file)
index 0000000..8ad6238
--- /dev/null
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+#include <Ecore.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+
+
+typedef struct {
+    int ecore;
+} ecore_glue_t;
+
+
+typedef struct {
+    Ecore_Fd_Handler *ec_io;
+    void            (*cb)(void *glue_data,
+                          void *id, int fd, mrp_io_event_t events,
+                          void *user_data);
+    mrp_io_event_t    mask;
+    void             *user_data;
+    void             *glue_data;
+} io_t;
+
+
+typedef struct {
+    Ecore_Timer *ec_t;
+    void       (*cb)(void *glue_data, void *id, void *user_data);
+    void        *user_data;
+    void        *glue_data;
+} tmr_t;
+
+
+typedef struct {
+    Ecore_Timer *ec_t;
+    void       (*cb)(void *glue_data, void *id, void *user_data);
+    void        *user_data;
+    void        *glue_data;
+} dfr_t;
+
+
+#define D(fmt, args...) do {                                     \
+        printf("* [%s]: "fmt"\n", __FUNCTION__ , ## args);       \
+    } while (0)
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data);
+static void  del_io(void *glue_data, void *id);
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+static void  del_timer(void *glue_data, void *id);
+static void  mod_timer(void *glue_data, void *id, unsigned int msecs);
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+static void  del_defer(void *glue_data, void *id);
+static void  mod_defer(void *glue_data, void *id, int enabled);
+
+
+static int io_check_hup(int fd)
+{
+    char buf[1];
+    int  saved_errno, n;
+
+    saved_errno = errno;
+    n = recv(fd, buf, 1, MSG_PEEK);
+    errno = saved_errno;
+
+    return (n == 0);
+}
+
+
+static Eina_Bool io_cb(void *user_data, Ecore_Fd_Handler *ec_io)
+{
+    io_t           *io     = (io_t *)user_data;
+    mrp_io_event_t  events = MRP_IO_EVENT_NONE;
+    int             fd     = ecore_main_fd_handler_fd_get(ec_io);
+
+    if (ecore_main_fd_handler_active_get(ec_io, ECORE_FD_READ))
+        events |= MRP_IO_EVENT_IN;
+    if (ecore_main_fd_handler_active_get(ec_io, ECORE_FD_WRITE))
+        events |= MRP_IO_EVENT_OUT;
+    if (ecore_main_fd_handler_active_get(ec_io, ECORE_FD_ERROR))
+        events |= MRP_IO_EVENT_ERR;
+
+#if 0 /* Pfoof... ecore cannot monitor for HUP. */
+    if (ecore_main_fd_handler_active_get(ec_io, NO_SUCH_ECORE_EVENT))
+        events |= MRP_IO_EVENT_HUP;
+#else
+    if ((io->mask & MRP_IO_EVENT_HUP) && (events & MRP_IO_EVENT_IN))
+        if (io_check_hup(fd))
+            events |= MRP_IO_EVENT_HUP;
+#endif
+
+    io->cb(io->glue_data, io, fd, events, io->user_data);
+
+    return ECORE_CALLBACK_RENEW;
+}
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data)
+{
+    int   mask = 0;
+    io_t *io;
+
+    io = mrp_allocz(sizeof(*io));
+
+    if (io != NULL) {
+        if (events & MRP_IO_EVENT_IN)  mask |= ECORE_FD_READ;
+        if (events & MRP_IO_EVENT_OUT) mask |= ECORE_FD_WRITE;
+#if 0 /* Pfoof... ecore cannot monitor for HUP. */
+        if (events & MRP_IO_EVENT_HUP) mask |= NO_SUCH_ECORE_EVENT;
+#endif
+        if (events & MRP_IO_EVENT_ERR) mask |= ECORE_FD_ERROR;
+
+        io->mask  = events;
+        io->ec_io = ecore_main_fd_handler_add(fd, mask, io_cb, io, NULL, NULL);
+
+        if (io->ec_io != NULL) {
+            io->cb        = cb;
+            io->user_data = user_data;
+            io->glue_data = glue_data;
+
+            return io;
+        }
+        else
+            mrp_free(io);
+    }
+
+    return NULL;
+}
+
+
+static void del_io(void *glue_data, void *id)
+{
+    io_t *io   = (io_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    ecore_main_fd_handler_del(io->ec_io);
+    mrp_free(io);
+}
+
+
+static Eina_Bool timer_cb(void *user_data)
+{
+    tmr_t *t = (tmr_t *)user_data;
+
+    t->cb(t->glue_data, t, t->user_data);
+
+    return ECORE_CALLBACK_RENEW;
+}
+
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    double  interval = (1.0 * msecs) / 1000.0;
+    tmr_t  *t;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+        t->ec_t = ecore_timer_add(interval, timer_cb, t);
+
+        if (t->ec_t != NULL) {
+            t->cb        = cb;
+            t->user_data = user_data;
+            t->glue_data = glue_data;
+
+            return t;
+        }
+        else
+            mrp_free(t);
+    }
+
+    return NULL;
+}
+
+
+static void del_timer(void *glue_data, void *id)
+{
+    tmr_t *t = (tmr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    ecore_timer_del(t->ec_t);
+    mrp_free(t);
+}
+
+
+static void mod_timer(void *glue_data, void *id, unsigned int msecs)
+{
+    tmr_t  *t        = (tmr_t *)id;
+    double  interval = (1.0 * msecs) / 1000.0;
+
+    MRP_UNUSED(glue_data);
+
+    if (t != NULL) {
+        /*
+         * Notes:
+         *     ecore_timer_reset needs to be called after updating the
+         *     interval. Otherwise the change will not be effective.
+         *
+         *     In practice, since we use mod_timer to update our super-
+         *     loop briding timer to latch at the next mrp_mainloop_t
+         *     timer moment, this could cause our mainloop to hang
+         *     right in the beginning, since the bridging timer has an
+         *     initial interval of (uint32_t)-1 (no next event). If we
+         *     have no other event sources than timers this would cause
+         *     our mainloop to hang indefinitely. If we have other event
+         *     sources (I/O or signals), the mainloop would hang till a
+         *     non-timer event comes in.
+         */
+        ecore_timer_interval_set(t->ec_t, interval);
+        ecore_timer_reset(t->ec_t);
+    }
+}
+
+
+static Eina_Bool defer_cb(void *user_data)
+{
+    dfr_t *d = (dfr_t *)user_data;
+
+    d->cb(d->glue_data, d, d->user_data);
+
+    return ECORE_CALLBACK_RENEW;
+}
+
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    dfr_t *d;
+
+    d = mrp_allocz(sizeof(*d));
+
+    if (d != NULL) {
+        d->ec_t = ecore_timer_add(0.0, defer_cb, d);
+
+        if (d->ec_t != NULL) {
+            d->cb        = cb;
+            d->user_data = user_data;
+            d->glue_data = glue_data;
+
+            return d;
+        }
+        else
+            mrp_free(d);
+    }
+
+    return NULL;
+}
+
+
+static void del_defer(void *glue_data, void *id)
+{
+    dfr_t *d = (dfr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    ecore_timer_del(d->ec_t);
+    mrp_free(d);
+}
+
+
+static void mod_defer(void *glue_data, void *id, int enabled)
+{
+    dfr_t *d = (dfr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    if (enabled)
+        ecore_timer_thaw(d->ec_t);
+    else
+        ecore_timer_freeze(d->ec_t);
+}
+
+
+static void unregister(void *data)
+{
+    MRP_UNUSED(data);
+}
+
+
+static mrp_superloop_ops_t ecore_ops = {
+    .add_io     = add_io,
+    .del_io     = del_io,
+    .add_timer  = add_timer,
+    .del_timer  = del_timer,
+    .mod_timer  = mod_timer,
+    .add_defer  = add_defer,
+    .del_defer  = del_defer,
+    .mod_defer  = mod_defer,
+    .unregister = unregister,
+};
+
+
+static const char     *ecore_glue = "murphy-ecore-glue";
+static mrp_mainloop_t *ecore_ml;
+
+
+int mrp_mainloop_register_with_ecore(mrp_mainloop_t *ml)
+{
+    return mrp_set_superloop(ml, &ecore_ops, (void *)ecore_glue);
+}
+
+
+int mrp_mainloop_unregister_from_ecore(mrp_mainloop_t *ml)
+{
+    return mrp_mainloop_unregister(ml);
+}
+
+
+mrp_mainloop_t *mrp_mainloop_ecore_get(void)
+{
+    if (ecore_ml == NULL) {
+        ecore_init();
+
+        ecore_ml = mrp_mainloop_create();
+
+        if (ecore_ml != NULL) {
+            if (!mrp_mainloop_register_with_ecore(ecore_ml)) {
+                mrp_mainloop_destroy(ecore_ml);
+                ecore_ml = NULL;
+            }
+        }
+    }
+
+    return ecore_ml;
+}
diff --git a/src/common/ecore-glue.h b/src/common/ecore-glue.h
new file mode 100644 (file)
index 0000000..ebf53a6
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_ECORE_H__
+#define __MURPHY_ECORE_H__
+
+MRP_CDECL_BEGIN
+
+/** Register the given murphy mainloop with the ecore mainloop. */
+int mrp_mainloop_register_with_ecore(mrp_mainloop_t *ml);
+
+/** Unrgister the given murphy mainloop from the ecore mainloop. */
+int mrp_mainloop_unregister_from_ecore(mrp_mainloop_t *ml);
+
+/** Create a murphy mainloop and set it up with the ecore mainloop. */
+mrp_mainloop_t *mrp_mainloop_ecore_get(void);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_ECORE_H__ */
diff --git a/src/common/env.c b/src/common/env.c
new file mode 100644 (file)
index 0000000..4ea8bad
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2012 - 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+const char *mrp_env_config_key(const char *config, const char *key)
+{
+    const char *beg;
+    int         len;
+
+    if (config != NULL) {
+        len = strlen(key);
+
+        beg = config;
+        while (beg != NULL) {
+            beg = strstr(beg, key);
+
+            if (beg != NULL) {
+                if ((beg == config || beg[-1] == ':') &&
+                    (beg[len] == '=' || beg[len] == ':' || beg[len] == '\0'))
+                    return (beg[len] == '=' ? beg + len + 1 : "");
+                else
+                    beg++;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+int32_t mrp_env_config_int32(const char *cfg, const char *key, int32_t defval)
+{
+    const char *v;
+    char       *end;
+    int         i;
+
+    v = mrp_env_config_key(cfg, key);
+
+    if (v != NULL) {
+        if (*v) {
+            i = strtol(v, &end, 10);
+
+            if (end && (!*end || *end == ':'))
+                return i;
+        }
+    }
+
+    return defval;
+}
+
+
+uint32_t mrp_env_config_uint32(const char *cfg, const char *key,
+                               uint32_t defval)
+{
+    const char *v;
+    char       *end;
+    int         i;
+
+    v = mrp_env_config_key(cfg, key);
+
+    if (v != NULL) {
+        if (*v) {
+            i = strtol(v, &end, 10);
+
+            if (end && (!*end || *end == ':'))
+                return i;
+        }
+    }
+
+    return defval;
+}
+
+
+int mrp_env_config_bool(const char *config, const char *key, bool defval)
+{
+    const char *v;
+
+    v = mrp_env_config_key(config, key);
+
+    if (v != NULL) {
+        if (*v) {
+            if ((!strncasecmp(v, "false", 5) && (v[5] == ':' || !v[5])) ||
+                (!strncasecmp(v, "true" , 4) && (v[4] == ':' || !v[4])))
+                return (v[0] == 't' || v[0] == 'T');
+            if ((!strncasecmp(v, "no" , 2) && (v[2] == ':' || !v[2])) ||
+                (!strncasecmp(v, "yes", 3) && (v[3] == ':' || !v[3])))
+                return (v[0] == 'y' || v[0] == 'Y');
+            if ((!strncasecmp(v, "on" , 2) && (v[2] == ':' || !v[2])) ||
+                (!strncasecmp(v, "off", 3) && (v[3] == ':' || !v[3])))
+                return (v[1] == 'n' || v[1] == 'N');
+        }
+        else if (*v == '\0')
+            return !defval;              /* hmm... is this right */
+    }
+
+    return defval;
+}
+
+
+int mrp_env_config_string(const char *cfg, const char *key,
+                          const char *defval, char *buf, size_t size)
+{
+    const char *v;
+    char       *end;
+    int         len;
+
+    v = mrp_env_config_key(cfg, key);
+
+    if (v == NULL)
+        v = defval;
+
+    end = strchr(v, ':');
+
+    if (end != NULL)
+        len = end - v;
+    else
+        len = strlen(v);
+
+    len = snprintf(buf, size, "%*.*s", len, len, v);
+
+    if (len >= (int)size - 1)
+        buf[size - 1] = '\0';
+
+    return len;
+}
diff --git a/src/common/env.h b/src/common/env.h
new file mode 100644 (file)
index 0000000..1a739e8
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2012 - 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_ENV_H__
+#define __MURPHY_ENV_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/** Extract the value for the given key from the config string. */
+const char *mrp_env_config_key(const char *config, const char *key);
+
+/** Extract an int32 value for key, returning defval if not found. */
+int32_t mrp_env_config_int32(const char *cfg, const char *key, int32_t defval);
+
+/** Extract an uint32 value for key, returning defval if not found. */
+uint32_t mrp_env_config_uint32(const char *cfg, const char *key,
+                               uint32_t defval);
+
+/** Extract a boolean value for key, returning defval if not found. */
+bool mrp_env_config_bool(const char *config, const char *key, bool defval);
+
+/** Extract a string value for key to buf (of size), defaulting to defval. */
+int mrp_env_config_string(const char *cfg, const char *key,
+                          const char *defval, char *buf, size_t size);
+
+#endif /* __MURPHY_ENV_H__ */
diff --git a/src/common/file-utils.c b/src/common/file-utils.c
new file mode 100644 (file)
index 0000000..ce78c80
--- /dev/null
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdbool.h>
+#include <dirent.h>
+#include <regex.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/file-utils.h>
+
+
+static const char *translate_glob(const char *pattern, char *glob, size_t size)
+{
+    MRP_UNUSED(glob);
+    MRP_UNUSED(size);
+
+    /* XXX FIXME: translate pattern to glob-like */
+
+    return pattern;
+}
+
+
+static inline mrp_dirent_type_t dirent_type(mode_t mode)
+{
+#define MAP_TYPE(x, y) if (S_IS##x(mode)) return MRP_DIRENT_##y
+
+    MAP_TYPE(REG, REG);
+    MAP_TYPE(DIR, DIR);
+    MAP_TYPE(LNK, LNK);
+    MAP_TYPE(CHR, CHR);
+    MAP_TYPE(BLK, BLK);
+    MAP_TYPE(FIFO, FIFO);
+    MAP_TYPE(SOCK, SOCK);
+
+    return MRP_DIRENT_UNKNOWN;
+
+#undef MAP_TYPE
+}
+
+
+int mrp_scan_dir(const char *path, const char *pattern, mrp_dirent_type_t mask,
+                 mrp_scan_dir_cb_t cb, void *user_data)
+{
+    DIR               *dp;
+    struct dirent     *de;
+    struct stat        st;
+    regex_t            regexp;
+    const char        *prefix;
+    char               glob[1024], file[PATH_MAX];
+    size_t             size;
+    int                stop;
+    mrp_dirent_type_t  type;
+
+    if ((dp = opendir(path)) == NULL)
+        return FALSE;
+
+    if (pattern != NULL) {
+        prefix = MRP_PATTERN_GLOB;
+        size   = sizeof(MRP_PATTERN_GLOB) - 1;
+
+        if (!strncmp(pattern, prefix, size)) {
+            pattern = translate_glob(pattern + size, glob, sizeof(glob));
+
+            if (pattern == NULL) {
+                closedir(dp);
+                return FALSE;
+            }
+        }
+        else {
+            prefix = MRP_PATTERN_REGEX;
+            size   = sizeof(MRP_PATTERN_REGEX) - 1;
+
+            if (!strncmp(pattern, prefix, size))
+                pattern += size;
+        }
+
+        if (regcomp(&regexp, pattern, REG_EXTENDED | REG_NOSUB) != 0) {
+            closedir(dp);
+            return FALSE;
+        }
+    }
+
+    stop = FALSE;
+    while ((de = readdir(dp)) != NULL && !stop) {
+        if (pattern != NULL && regexec(&regexp, de->d_name, 0, NULL, 0) != 0)
+            continue;
+
+        snprintf(file, sizeof(file), "%s/%s", path, de->d_name);
+
+        if (((mask & MRP_DIRENT_LNK ? lstat : stat))(file, &st) != 0)
+            continue;
+
+        type = dirent_type(st.st_mode);
+        if (!(type & mask))
+            continue;
+
+        stop = !cb(de->d_name, type, user_data);
+    }
+
+
+    closedir(dp);
+    if (pattern != NULL)
+        regfree(&regexp);
+
+    return TRUE;
+}
+
+
+int mrp_find_file(const char *file, const char **dirs, int mode, char *buf,
+                  size_t size)
+{
+    const char *dir;
+    char        path[PATH_MAX];
+    int         i;
+
+    if (file[0] != '/') {
+        if (dirs != NULL) {
+            for (dir = dirs[i=0]; dir != NULL; dir = dirs[++i]) {
+                if (snprintf(path, sizeof(path), "%s/%s",
+                             dir, file) >= (ssize_t)sizeof(path))
+                    continue;
+
+                if (access(path, mode) == 0) {
+                    file = path;
+                    goto found;
+                }
+            }
+        }
+
+        if (snprintf(path, sizeof(path), "./%s", file) < (ssize_t)sizeof(path)) {
+            if (access(path, mode) == 0) {
+                file = path;
+                goto found;
+            }
+        }
+    }
+    else {
+        if (access(file, mode) == 0)
+            goto found;
+    }
+
+    errno = ENOENT;
+    return -1;
+
+ found:
+    if (buf != NULL && size > 0)
+        snprintf(buf, size, "%s", file);
+
+    return 0;
+}
+
+
+int mrp_mkdir(const char *path, mode_t mode)
+{
+    const char *p;
+    char       *q, buf[PATH_MAX];
+    int         n, undo[PATH_MAX / 2];
+    struct stat st;
+
+    if (path == NULL || path[0] == '\0') {
+        errno = path ? EINVAL : EFAULT;
+        return -1;
+    }
+
+    /*
+     * Notes:
+     *     Our directory creation algorithm logic closely resembles what
+     *     'mkdir -p' does. We simply walk the given path component by
+     *     component, testing if each one exist. If an existing one is
+     *     not a directory we bail out. Missing ones we try to create with
+     *     the given mode, bailing out if we fail.
+     *
+     *     Unlike 'mkdir -p' whenever we fail we clean up by removing
+     *     all directories we have created (or at least we try).
+     *
+     *     Similarly to 'mkdir -p' we don't try to be overly 'smart' about
+     *     the path we're handling. Especially we never try to treat '..'
+     *     in any special way. This is very much intentional and the idea
+     *     is to let the caller try to create a full directory hierarchy
+     *     atomically, either succeeeding creating the full hierarchy, or
+     *     none of it. To see the consequences of these design choices,
+     *     consider what are the possible outcomes of a call like
+     *
+     *       mrp_mkdir("/home/kli/bin/../sbin/../scripts/../etc/../doc", 0755);
+     */
+
+    p = path;
+    q = buf;
+    n = 0;
+    while (1) {
+        if (q - buf >= (ptrdiff_t)sizeof(buf) - 1) {
+            errno = ENAMETOOLONG;
+            goto cleanup;
+        }
+
+        if (*p && *p != '/') {
+            *q++ = *p++;
+            continue;
+        }
+
+        *q = '\0';
+
+        mrp_debug("checking/creating '%s'...", buf);
+
+        if (q != buf) {
+            if (stat(buf, &st) < 0) {
+                if (errno != ENOENT)
+                    goto cleanup;
+
+                if (mkdir(buf, mode) < 0)
+                    goto cleanup;
+
+                undo[n++] = q - buf;
+            }
+            else {
+                if (!S_ISDIR(st.st_mode)) {
+                    errno = ENOTDIR;
+                    goto cleanup;
+                }
+            }
+        }
+
+        while (*p == '/')
+            p++;
+
+        if (!*p)
+            break;
+
+        *q++ = '/';
+    }
+
+    return 0;
+
+ cleanup:
+    while (--n >= 0) {
+        buf[undo[n]] = '\0';
+        mrp_debug("cleaning up '%s'...", buf);
+        rmdir(buf);
+    }
+
+    return -1;
+}
+
+
+char *mrp_normalize_path(char *buf, size_t size, const char *path)
+{
+    const char *p;
+    char       *q;
+    int         n, back[PATH_MAX / 2];
+
+    if (path == NULL)
+        return NULL;
+
+    if (*path == '\0') {
+        if (size > 0) {
+            *buf = '\0';
+            return buf;
+        }
+        else {
+        overflow:
+            errno = ENAMETOOLONG;
+            return NULL;
+        }
+    }
+
+    p   = path;
+    q   = buf;
+    n   = 0;
+
+    while (*p) {
+        if (q - buf + 1 >= (ptrdiff_t)size)
+            goto overflow;
+
+        if (*p == '/') {
+            back[n++] = q - buf;
+            *q++ = *p++;
+
+        skip_slashes:
+            while (*p == '/')
+                p++;
+
+            /*
+             * '.'
+             *
+             * We skip './' including all trailing slashes. Note that
+             * the code is arranged so that whenever we skip trailing
+             * slashes, we automatically check and skip also trailing
+             * './'s too...
+             */
+
+            if (p[0] == '.' && (p[1] == '/' || p[1] == '\0')) {
+                p++;
+                goto skip_slashes;
+            }
+
+            /*
+             * '..'
+             *
+             * We throw away the last incorrectly saved backtracking
+             * point (we saved it for this '../'). Then if we can still
+             * backtrack, we do so. Otherwise (we're at the beginning
+             * already), if the path is absolute, we just ignore the
+             * current '../' (can't go above '/'), otherwise we keep it
+             * for relative pathes.
+             */
+
+            if (p[0] == '.' && p[1] == '.' && (p[2] == '/' || p[2] == '\0')) {
+                n--;                                /* throw away */
+                if (n > 0) {                        /* can still backtrack */
+                    if (back[n - 1] >= 0)           /* previous not a '..' */
+                        q = buf + back[n - 1] + 1;
+                }
+                else {
+                    if (q > buf && buf[0] == '/')   /* for absolute pathes */
+                        q = buf + 1;                /*     reset to start */
+                    else {                          /* for relative pathes */
+                        if (q - buf + 4 >= (ptrdiff_t)size)
+                            goto overflow;
+
+                        q[0] = '.';                 /*     append this '..' */
+                        q[1] = '.';
+                        q[2] = '/';
+                        q += 3;
+                        back[n] = -1;               /*     block backtracking */
+                    }
+                }
+
+                p += 2;
+                goto skip_slashes;
+            }
+        }
+        else
+            *q++ = *p++;
+    }
+
+    /*
+     * finally for other than '/' align trailing slashes
+     */
+
+    if (p > path + 1 && p[-1] != '/')
+        if (q > buf + 1 && q[-1] == '/')
+            q--;
+
+    *q = '\0';
+
+    return buf;
+}
diff --git a/src/common/file-utils.h b/src/common/file-utils.h
new file mode 100644 (file)
index 0000000..dd7266c
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_FILEUTILS_H__
+#define __MURPHY_FILEUTILS_H__
+
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+/*
+ * Routines for scanning a directory for matching entries.
+ */
+
+/** Directory entry types. */
+typedef enum {
+    MRP_DIRENT_UNKNOWN = 0,                      /* unknown */
+    MRP_DIRENT_NONE = 0x00,                      /* unknown */
+    MRP_DIRENT_FIFO = 0x01,                      /* FIFO */
+    MRP_DIRENT_CHR  = 0x02,                      /* character device */
+    MRP_DIRENT_DIR  = 0x04,                      /* directory */
+    MRP_DIRENT_BLK  = 0x08,                      /* block device */
+    MRP_DIRENT_REG  = 0x10,                      /* regular file */
+    MRP_DIRENT_LNK  = 0x20,                      /* symbolic link */
+    MRP_DIRENT_SOCK = 0x40,                      /* socket */
+    MRP_DIRENT_ANY  = 0xff,                      /* mask of all types */
+} mrp_dirent_type_t;
+
+
+#define MRP_PATTERN_GLOB  "glob:"                /* a globbing pattern */
+#define MRP_PATTERN_REGEX "regex:"               /* a regexp pattern */
+
+
+/** Directory scanning callback type. */
+typedef int (*mrp_scan_dir_cb_t)(const char *entry, mrp_dirent_type_t type,
+                                 void *user_data);
+
+/** Scan a directory, calling cb with all matching entries. */
+int mrp_scan_dir(const char *path, const char *pattern, mrp_dirent_type_t mask,
+                 mrp_scan_dir_cb_t cb, void *user_data);
+
+/** Do an #include-like search for the given file among the given dirs. */
+int mrp_find_file(const char *file, const char **dirs, int mode, char *buf,
+                  size_t size);
+
+/** Create a directory, creating leading path as necessary. */
+int mrp_mkdir(const char *path, mode_t mode);
+
+
+/** Parse a path into a normalized form, removing ../'s and ./'s. */
+char *mrp_normalize_path(char *buf, size_t size, const char *path);
+
+#endif /* __MURPHY_FILEUTILS_H__ */
diff --git a/src/common/fragbuf.c b/src/common/fragbuf.c
new file mode 100644 (file)
index 0000000..740e138
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <endian.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/fragbuf.h>
+
+struct mrp_fragbuf_s {
+    void *data;                          /* actual data buffer */
+    int   size;                          /* size of the buffer */
+    int   used;                          /* amount of data in the bufer */
+    int   framed : 1;                    /* whether data is framed */
+};
+
+
+static void *fragbuf_ensure(mrp_fragbuf_t *buf, size_t size)
+{
+    int more;
+
+    if (buf->size - buf->used < (int)size) {
+        more = size - (buf->size - buf->used);
+
+        if (mrp_reallocz(buf->data, buf->size, buf->size + more) == NULL)
+            return NULL;
+        else
+            buf->size += more;
+    }
+
+    return buf->data + buf->used;
+}
+
+
+size_t mrp_fragbuf_used(mrp_fragbuf_t *buf)
+{
+    return buf->used;
+}
+
+
+size_t mrp_fragbuf_missing(mrp_fragbuf_t *buf)
+{
+    void     *ptr;
+    int       offs;
+    uint32_t  size;
+
+    if (!buf->framed || !buf->used)
+        return 0;
+
+    /* find the last frame */
+    ptr  = buf->data;
+    offs = 0;
+    while (offs < buf->used) {
+        size  = be32toh(*(uint32_t *)ptr);
+        offs += sizeof(size) + size;
+    }
+
+    /* get the amount of data missing */
+    return offs - buf->used;
+}
+
+
+int fragbuf_init(mrp_fragbuf_t *buf, int framed, int pre_alloc)
+{
+    buf->data   = NULL;
+    buf->size   = 0;
+    buf->used   = 0;
+    buf->framed = framed;
+
+    if (pre_alloc <= 0 || fragbuf_ensure(buf, pre_alloc))
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+mrp_fragbuf_t *mrp_fragbuf_create(int framed, size_t pre_alloc)
+{
+    mrp_fragbuf_t *buf;
+
+    buf = mrp_allocz(sizeof(*buf));
+
+    if (buf != NULL) {
+        if (fragbuf_init(buf, framed, pre_alloc))
+            return buf;
+
+        mrp_free(buf);
+    }
+
+    return NULL;
+}
+
+
+void mrp_fragbuf_reset(mrp_fragbuf_t *buf)
+{
+    if (buf != NULL) {
+        mrp_free(buf->data);
+        buf->data = NULL;
+        buf->size = 0;
+        buf->used = 0;
+    }
+}
+
+void mrp_fragbuf_destroy(mrp_fragbuf_t *buf)
+{
+    if (buf != NULL) {
+        mrp_free(buf->data);
+        mrp_free(buf);
+    }
+}
+
+
+void *mrp_fragbuf_alloc(mrp_fragbuf_t *buf, size_t size)
+{
+    void *ptr;
+
+    ptr = fragbuf_ensure(buf, size);
+
+    if (ptr != NULL)
+        buf->used += size;
+
+    return ptr;
+}
+
+
+int mrp_fragbuf_trim(mrp_fragbuf_t *buf, void *ptr, size_t osize, size_t nsize)
+{
+    size_t diff;
+
+    if (ptr + osize == buf->data + buf->used) { /* looks like the last alloc */
+        if (nsize <= osize) {
+            diff = osize - nsize;
+            buf->used -= diff;
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_fragbuf_push(mrp_fragbuf_t *buf, void *data, size_t size)
+{
+    void *ptr;
+
+    ptr = fragbuf_ensure(buf, size);
+
+    if (ptr != NULL) {
+        memcpy(ptr, data, size);
+        buf->used += size;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_fragbuf_pull(mrp_fragbuf_t *buf, void **datap, size_t *sizep)
+{
+    void     *data;
+    uint32_t  size;
+
+    if (buf == NULL || buf->used <= 0)
+        return FALSE;
+
+    if (MRP_UNLIKELY(*datap &&
+                     (*datap < buf->data || *datap > buf->data + buf->used))) {
+        mrp_log_warning("%s(): *** looks like we're called with an unreset "
+                        "datap pointer... ***", __FUNCTION__);
+    }
+
+    /* start of iteration */
+    if (*datap == NULL) {
+        if (!buf->framed) {
+            *datap = buf->data;
+            *sizep = buf->used;
+
+            return TRUE;
+        }
+        else {
+            if (buf->used < (int)sizeof(size))
+                return FALSE;
+
+            size = be32toh(*(uint32_t *)buf->data);
+
+            if (buf->used >= (int)(sizeof(size) + size)) {
+                *datap = buf->data + sizeof(size);
+                *sizep = size;
+
+                return TRUE;
+            }
+            else
+                return FALSE;
+        }
+    }
+    /* continue iteration */
+    else {
+        if (!buf->framed) {
+            data = *datap + *sizep;
+
+            if (buf->data <= data && data < buf->data + buf->used) {
+                memmove(buf->data, data, buf->used - (data - buf->data));
+                buf->used -= (data - buf->data);
+
+                *datap = buf->data;
+                *sizep = buf->used;
+
+                return TRUE;
+            }
+            else {
+                if (data == buf->data + buf->used)
+                    buf->used = 0;
+
+                return FALSE;
+            }
+        }
+        else {
+            if (*datap != buf->data + sizeof(size))
+                return FALSE;
+
+            size = be32toh(*(uint32_t *)buf->data);
+
+            if ((int)(size + sizeof(size)) <= buf->used) {
+                memmove(buf->data, buf->data + size + sizeof(size),
+                        buf->used - (size + sizeof(size)));
+                buf->used -= size + sizeof(size);
+            }
+            else
+                return FALSE;
+
+            if (buf->used <= (int)sizeof(size))
+                return FALSE;
+
+            size = be32toh(*(uint32_t *)buf->data);
+            data = buf->data + sizeof(size);
+
+            if (buf->used >= (int)(size + sizeof(size))) {
+                *datap = data;
+                *sizep = size;
+
+                return TRUE;
+            }
+
+            return FALSE;
+        }
+    }
+}
diff --git a/src/common/fragbuf.h b/src/common/fragbuf.h
new file mode 100644 (file)
index 0000000..6e2ecc6
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_FRAGBUF_H__
+#define __MURPHY_FRAGBUF_H__
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+/*
+ * Fragment collector buffers.
+ *
+ * As the name implies, a fragment collector buffer can be used
+ * to collect message fragments and reassemble messages that were
+ * transmitted in arbitrary pieces.
+ *
+ * Messages are expected to be transmitted in frames where each
+ * frame simply consist of a 32-bit message size followed by
+ * the actual message data. On the sending side you can simply
+ * send each message prefixed with its size. On the receiving side
+ * you keep feeding the received chunks of data to a fragment
+ * collector buffer (using mrp_fragbuf_push). After each chunk you
+ * can iterate through the fully reassembled messages (by calling
+ * mrp_fragbuf_pull until it returns FALSE). Messages are removed
+ * automatically from the collector buffer as you iterate through
+ * them.
+ *
+ * You can also create a collector buffer in frameless mode. Such a
+ * buffer will always return immediately all available data as you
+ * iterate through it.
+ */
+
+/** Buffer for collecting fragments of (framed or unframed) message data. */
+typedef struct mrp_fragbuf_s mrp_fragbuf_t;
+
+/** Initialize the given fragment collector buffer. */
+mrp_fragbuf_t *mrp_fragbuf_create(int framed, size_t pre_alloc);
+
+/** Initialize the given data collector buffer. */
+int mrp_fragbuf_init(mrp_fragbuf_t *buf, int framed, size_t pre_alloc);
+
+/** Reset the given data collector buffer. */
+void mrp_fragbuf_reset(mrp_fragbuf_t *buf);
+
+/** Destroy the given data collector buffer, freeing all associated memory. */
+void mrp_fragbuf_destroy(mrp_fragbuf_t *buf);
+
+/** Return the amount of buffer space currently in used in th buffer. */
+size_t mrp_fragbuf_used(mrp_fragbuf_t *buf);
+
+/** Return the amount of bytes missing from the last message. */
+size_t mrp_fragbuf_missing(mrp_fragbuf_t *buf);
+
+/** Allocate a buffer of the given size from the buffer. */
+void *mrp_fragbuf_alloc(mrp_fragbuf_t *buf, size_t size);
+
+/** Trim the last allocation to nsize bytes. */
+int mrp_fragbuf_trim(mrp_fragbuf_t *buf, void *ptr, size_t osize, size_t nsize);
+
+/** Append the given data to the buffer. */
+int mrp_fragbuf_push(mrp_fragbuf_t *buf, void *data, size_t size);
+
+/** Iterate through the given buffer, pulling and freeing assembled messages. */
+int mrp_fragbuf_pull(mrp_fragbuf_t *buf, void **data, size_t *size);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_FRAGBUF_H__ */
diff --git a/src/common/glib-glue.c b/src/common/glib-glue.c
new file mode 100644 (file)
index 0000000..2df2ea7
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <glib.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+
+
+typedef struct {
+    GMainLoop *gml;
+} glib_glue_t;
+
+
+typedef struct {
+    GIOChannel        *gl_ioc;
+    guint              gl_iow;
+    void             (*cb)(void *glue_data,
+                           void *id, int fd, mrp_io_event_t events,
+                           void *user_data);
+    mrp_io_event_t     mask;
+    void              *user_data;
+    void              *glue_data;
+} io_t;
+
+
+typedef struct {
+    guint        gl_t;
+    void       (*cb)(void *glue_data, void *id, void *user_data);
+    void        *user_data;
+    void        *glue_data;
+} tmr_t;
+
+
+typedef struct {
+    guint        gl_t;
+    void       (*cb)(void *glue_data, void *id, void *user_data);
+    void        *user_data;
+    void        *glue_data;
+} dfr_t;
+
+
+#define D(fmt, args...) do {                                     \
+        printf("* [%s]: "fmt"\n", __FUNCTION__ , ## args);       \
+    } while (0)
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data);
+static void  del_io(void *glue_data, void *id);
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+static void  del_timer(void *glue_data, void *id);
+static void  mod_timer(void *glue_data, void *id, unsigned int msecs);
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+static void  del_defer(void *glue_data, void *id);
+static void  mod_defer(void *glue_data, void *id, int enabled);
+
+
+static gboolean io_cb(GIOChannel *ioc, GIOCondition cond, gpointer user_data)
+{
+    io_t           *io     = (io_t *)user_data;
+    mrp_io_event_t  events = MRP_IO_EVENT_NONE;
+    int             fd     = g_io_channel_unix_get_fd(ioc);
+
+    if (cond & G_IO_IN)
+        events |= MRP_IO_EVENT_IN;
+    if (cond & G_IO_OUT)
+        events |= MRP_IO_EVENT_OUT;
+    if (cond & G_IO_ERR)
+        events |= MRP_IO_EVENT_ERR;
+    if (cond & G_IO_HUP)
+        events |= MRP_IO_EVENT_HUP;
+
+    io->cb(io->glue_data, io, fd, events, io->user_data);
+
+    return TRUE;
+}
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data)
+{
+    GIOCondition  mask = 0;
+    GIOChannel   *ioc;
+    io_t         *io;
+
+    ioc = g_io_channel_unix_new(fd);
+
+    if (ioc == NULL)
+        return NULL;
+
+    io = mrp_allocz(sizeof(*io));
+
+    if (io != NULL) {
+        if (events & MRP_IO_EVENT_IN ) mask |= G_IO_IN;
+        if (events & MRP_IO_EVENT_OUT) mask |= G_IO_OUT;
+        if (events & MRP_IO_EVENT_HUP) mask |= G_IO_HUP;
+        if (events & MRP_IO_EVENT_ERR) mask |= G_IO_ERR;
+
+        io->mask   = events;
+        io->gl_ioc = ioc;
+        io->gl_iow = g_io_add_watch(ioc, mask, io_cb, io);
+
+        if (io->gl_iow != 0) {
+            io->cb        = cb;
+            io->user_data = user_data;
+            io->glue_data = glue_data;
+
+            return io;
+        }
+        else {
+            g_io_channel_unref(ioc);
+            mrp_free(io);
+        }
+    }
+
+    return NULL;
+}
+
+
+static void del_io(void *glue_data, void *id)
+{
+    io_t *io = (io_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    g_source_remove(io->gl_iow);
+    g_io_channel_unref(io->gl_ioc);
+    mrp_free(io);
+}
+
+
+static gboolean timer_cb(gpointer user_data)
+{
+    tmr_t *t = (tmr_t *)user_data;
+
+    t->cb(t->glue_data, t, t->user_data);
+
+    return TRUE;
+}
+
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    tmr_t *t;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+        t->gl_t = g_timeout_add(msecs, timer_cb, t);
+
+        if (t->gl_t != 0) {
+            t->cb        = cb;
+            t->user_data = user_data;
+            t->glue_data = glue_data;
+
+            return t;
+        }
+        else
+            mrp_free(t);
+    }
+
+    return NULL;
+}
+
+
+static void del_timer(void *glue_data, void *id)
+{
+    tmr_t *t = (tmr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    g_source_remove(t->gl_t);
+    mrp_free(t);
+}
+
+
+static void mod_timer(void *glue_data, void *id, unsigned int msecs)
+{
+    tmr_t  *t = (tmr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    if (t != NULL) {
+        g_source_remove(t->gl_t);
+        t->gl_t = g_timeout_add(msecs, timer_cb, t);
+    }
+}
+
+
+static gboolean defer_cb(void *user_data)
+{
+    dfr_t *d = (dfr_t *)user_data;
+
+    d->cb(d->glue_data, d, d->user_data);
+
+    return TRUE;
+}
+
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    dfr_t *d;
+
+    d = mrp_allocz(sizeof(*d));
+
+    if (d != NULL) {
+        d->gl_t = g_timeout_add(0, defer_cb, d);
+
+        if (d->gl_t != 0) {
+            d->cb        = cb;
+            d->user_data = user_data;
+            d->glue_data = glue_data;
+
+            return d;
+        }
+        else
+            mrp_free(d);
+    }
+
+    return NULL;
+}
+
+
+static void del_defer(void *glue_data, void *id)
+{
+    dfr_t *d = (dfr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    if (d->gl_t != 0)
+        g_source_remove(d->gl_t);
+
+    mrp_free(d);
+}
+
+
+static void mod_defer(void *glue_data, void *id, int enabled)
+{
+    dfr_t *d = (dfr_t *)id;
+
+    MRP_UNUSED(glue_data);
+
+    if (enabled && !d->gl_t)
+        d->gl_t = g_timeout_add(0, defer_cb, d);
+    else if (!enabled && d->gl_t) {
+        g_source_remove(d->gl_t);
+        d->gl_t = 0;
+    }
+}
+
+
+static void unregister(void *data)
+{
+    glib_glue_t *glue = (glib_glue_t *)data;
+
+    g_main_loop_unref(glue->gml);
+
+    mrp_free(glue);
+}
+
+
+static mrp_superloop_ops_t glib_ops = {
+    .add_io     = add_io,
+    .del_io     = del_io,
+    .add_timer  = add_timer,
+    .del_timer  = del_timer,
+    .mod_timer  = mod_timer,
+    .add_defer  = add_defer,
+    .del_defer  = del_defer,
+    .mod_defer  = mod_defer,
+    .unregister = unregister,
+};
+
+
+int mrp_mainloop_register_with_glib(mrp_mainloop_t *ml, GMainLoop *gml)
+{
+    glib_glue_t *glue;
+
+    glue = mrp_allocz(sizeof(*glue));
+
+    if (glue != NULL) {
+        glue->gml = g_main_loop_ref(gml);
+
+        if (mrp_set_superloop(ml, &glib_ops, glue))
+            return TRUE;
+        else {
+            g_main_loop_unref(gml);
+            mrp_free(glue);
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_mainloop_unregister_from_glib(mrp_mainloop_t *ml)
+{
+    return mrp_mainloop_unregister(ml);
+}
+
+
+mrp_mainloop_t *mrp_mainloop_glib_get(GMainLoop *gml)
+{
+    mrp_mainloop_t *ml;
+
+    if (gml != NULL) {
+        ml = mrp_mainloop_create();
+
+        if (ml != NULL) {
+            if (mrp_mainloop_register_with_glib(ml, gml))
+                return ml;
+            else
+                mrp_mainloop_destroy(ml);
+        }
+    }
+
+    return NULL;
+}
diff --git a/src/common/glib-glue.h b/src/common/glib-glue.h
new file mode 100644 (file)
index 0000000..88fef45
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_GLIB_H__
+#define __MURPHY_GLIB_H__
+
+#include <murphy/common/mainloop.h>
+#include <glib.h>
+
+MRP_CDECL_BEGIN
+
+/** Register the given murphy mainloop with the glib mainloop. */
+int mrp_mainloop_register_with_glib(mrp_mainloop_t *ml, GMainLoop *gml);
+
+/** Unrgister the given murphy mainloop from the glib mainloop. */
+int mrp_mainloop_unregister_from_glib(mrp_mainloop_t *ml);
+
+/** Create a murphy mainloop and set it up with the glib mainloop. */
+mrp_mainloop_t *mrp_mainloop_glib_get(GMainLoop *gml);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_GLIB_H__ */
diff --git a/src/common/hashtbl.c b/src/common/hashtbl.c
new file mode 100644 (file)
index 0000000..3f49d83
--- /dev/null
@@ -0,0 +1,377 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+
+#include "murphy/common/mm.h"
+#include "murphy/common/list.h"
+#include "murphy/common/hashtbl.h"
+
+#define MIN_NBUCKET   8
+#define MAX_NBUCKET 128
+
+typedef struct {                        /* a hash bucket */
+    mrp_list_hook_t entries;            /* hook to hash table entries */
+    mrp_list_hook_t used;               /* hook to list of buckets in use */
+} bucket_t;
+
+typedef struct {                        /* a hash table entry */
+    mrp_list_hook_t  hook;              /* hook to bucket chain */
+    void            *key;               /* key for this entry */
+    void            *obj;               /* object for this entry */
+} entry_t;
+
+typedef struct {                        /* iterator state */
+    mrp_list_hook_t *bp, *bn;           /* current bucket hook pointers */
+    mrp_list_hook_t *ep, *en;           /* current entry hook pointers */
+    entry_t         *entry;             /* current entry */
+    int              verdict;           /* remove-from-cb verdict */
+} iter_t;
+
+struct mrp_htbl_s {
+    bucket_t           *buckets;        /* hash table buckets */
+    size_t              nbucket;        /* this many of them */
+    mrp_list_hook_t     used;           /* buckets in use */
+    mrp_htbl_comp_fn_t  comp;           /* key comparison function */
+    mrp_htbl_hash_fn_t  hash;           /* key hash function */
+    mrp_htbl_free_fn_t  free;           /* function to free an entry */
+    iter_t             *iter;           /* active iterator state */
+};
+
+
+static size_t calc_buckets(size_t nbucket)
+{
+    size_t n;
+
+    if (nbucket < MIN_NBUCKET)
+        nbucket = MIN_NBUCKET;
+    if (nbucket > MAX_NBUCKET)
+        nbucket = MAX_NBUCKET;
+
+    for (n = MIN_NBUCKET; n < nbucket; n <<= 1)
+        ;
+
+    return n;
+}
+
+
+mrp_htbl_t *mrp_htbl_create(mrp_htbl_config_t *cfg)
+{
+    mrp_htbl_t *ht;
+    size_t     i, nbucket;
+
+    if (cfg->comp && cfg->hash) {
+        if ((ht = mrp_allocz(sizeof(*ht))) != NULL) {
+            if (cfg->nbucket != 0)
+                nbucket = cfg->nbucket;
+            else {
+                if (cfg->nentry != 0)
+                    nbucket = cfg->nentry / 4;
+                else
+                    nbucket = 4 * MIN_NBUCKET;
+            }
+
+            ht->nbucket = calc_buckets(nbucket);
+            ht->comp    = cfg->comp;
+            ht->hash    = cfg->hash;
+            ht->free    = cfg->free;
+
+            mrp_list_init(&ht->used);
+
+            ht->buckets = mrp_allocz(sizeof(*ht->buckets) * ht->nbucket);
+            if (ht->buckets != NULL) {
+                for (i = 0; i < ht->nbucket; i++) {
+                    mrp_list_init(&ht->buckets[i].entries);
+                    mrp_list_init(&ht->buckets[i].used);
+                }
+
+                return ht;
+            }
+            else {
+                mrp_free(ht);
+                ht = NULL;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+void mrp_htbl_destroy(mrp_htbl_t *ht, int free)
+{
+    if (ht != NULL) {
+        if (free)
+            mrp_htbl_reset(ht, free);
+
+        mrp_free(ht->buckets);
+        mrp_free(ht);
+    }
+}
+
+
+static inline void free_entry(mrp_htbl_t *ht, entry_t *entry, int free)
+{
+    if (free && ht->free)
+        ht->free(entry->key, entry->obj);
+    mrp_free(entry);
+}
+
+
+void mrp_htbl_reset(mrp_htbl_t *ht, int free)
+{
+    mrp_list_hook_t *bp, *bn, *ep, *en;
+    bucket_t        *bucket;
+    entry_t         *entry;
+
+    mrp_list_foreach(&ht->used, bp, bn) {
+        bucket = mrp_list_entry(bp, bucket_t, used);
+
+        mrp_list_foreach(&bucket->entries, ep, en) {
+            entry = mrp_list_entry(ep, entry_t, hook);
+            mrp_list_delete(ep);
+            free_entry(ht, entry, free);
+        }
+
+        mrp_list_delete(&bucket->used);
+    }
+}
+
+
+int mrp_htbl_insert(mrp_htbl_t *ht, void *key, void *object)
+{
+    uint32_t  idx    = ht->hash(key) & (ht->nbucket - 1);
+    bucket_t *bucket = ht->buckets + idx;
+    int       first  = mrp_list_empty(&bucket->entries);
+    entry_t  *entry;
+
+    if ((entry = mrp_allocz(sizeof(*entry))) != NULL) {
+        entry->key = key;
+        entry->obj = object;
+        mrp_list_append(&bucket->entries, &entry->hook);
+        if (first)
+            mrp_list_append(&ht->used, &bucket->used);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static inline entry_t *lookup(mrp_htbl_t *ht, void *key, bucket_t **bucketp)
+{
+    uint32_t        idx    = ht->hash(key) & (ht->nbucket - 1);
+    bucket_t       *bucket = ht->buckets + idx;
+    mrp_list_hook_t *p, *n;
+    entry_t        *entry;
+
+    mrp_list_foreach(&bucket->entries, p, n) {
+        entry = mrp_list_entry(p, entry_t, hook);
+
+        if (!ht->comp(entry->key, key)) {
+            if (bucketp != NULL)
+                *bucketp = bucket;
+            return entry;
+        }
+    }
+
+    return NULL;
+}
+
+
+void *mrp_htbl_lookup(mrp_htbl_t *ht, void *key)
+{
+    entry_t *entry;
+
+    entry = lookup(ht, key, NULL);
+    if (entry != NULL)
+        return entry->obj;
+    else
+        return NULL;
+}
+
+
+static void delete_from_bucket(mrp_htbl_t *ht, bucket_t *bucket, entry_t *entry)
+{
+    mrp_list_hook_t *eh = &entry->hook;
+
+
+    /*
+     * If there is an iterator active and this entry would
+     * have been the next one to iterate over, we need to
+     * update the iterator to skip to the next entry instead
+     * as this one will be removed. Failing to update the
+     * iterator could crash mrp_htbl_foreach or drive it into
+     * an infinite loop.
+     */
+
+    if (ht->iter != NULL && ht->iter->en == eh)
+        ht->iter->en = eh->next;
+
+    mrp_list_delete(eh);
+
+
+    /*
+     * If the bucket became empty, unlink it from the used list.
+     * If also there is an iterator active and this bucket would
+     * have been the next one to iterate over, we need to
+     * update the iterator to skip to the next bucket instead
+     * as this one just became empty and will be removed from
+     * the used bucket list. Failing to update the iterator
+     * could drive mrp_htbl_foreach into an infinite loop
+     * because of the unexpected hop from the used bucket list
+     * (to a single empty bucket).
+     */
+
+    if (mrp_list_empty(&bucket->entries)) {
+        if (ht->iter != NULL && ht->iter->bn == &bucket->used)
+                ht->iter->bn = bucket->used.next;
+
+        mrp_list_delete(&bucket->used);
+    }
+}
+
+
+void *mrp_htbl_remove(mrp_htbl_t *ht, void *key, int free)
+{
+    bucket_t *bucket;
+    entry_t  *entry;
+    void     *object;
+
+    /*
+     * We need to check the found entry and its hash-bucket
+     * against any potentially active iterator. Special care
+     * needs to be taken if the entry is being iterated over
+     * or if the bucket becomes empty and it would be the next
+     * bucket to iterate over. The former is taken care of
+     * here while the latter is handled in delete_from_bucket.
+     */
+    if ((entry = lookup(ht, key, &bucket)) != NULL) {
+        delete_from_bucket(ht, bucket, entry);
+        object = entry->obj;
+
+        if (ht->iter != NULL && entry == ht->iter->entry) /* being iterated */
+            ht->iter->verdict = free ? MRP_HTBL_ITER_DELETE : 0;
+        else {
+            free_entry(ht, entry, free);
+        }
+    }
+    else
+        object = NULL;
+
+    return object;
+}
+
+
+int mrp_htbl_foreach(mrp_htbl_t *ht, mrp_htbl_iter_cb_t cb, void *user_data)
+{
+    iter_t    iter;
+    bucket_t *bucket;
+    entry_t  *entry;
+    int       cb_verdict, ht_verdict;
+
+    /*
+     * Now we can only handle a single callback-based iterator.
+     * If there is already one we're busy so just bail out.
+     */
+    if (ht->iter != NULL)
+        return FALSE;
+
+    mrp_clear(&iter);
+    ht->iter = &iter;
+
+    mrp_list_foreach(&ht->used, iter.bp, iter.bn) {
+        bucket = mrp_list_entry(iter.bp, bucket_t, used);
+
+        mrp_list_foreach(&bucket->entries, iter.ep, iter.en) {
+            iter.entry = entry = mrp_list_entry(iter.ep, entry_t, hook);
+            cb_verdict = cb(entry->key, entry->obj, user_data);
+            ht_verdict = iter.verdict;
+
+            /* delete was called from cb (unhashed entry and marked it) */
+            if (ht_verdict & MRP_HTBL_ITER_DELETE) {
+                free_entry(ht, entry, TRUE);
+            }
+            else {
+                /* cb wants us to unhash (safe even if unhashed in remove) */
+                if (cb_verdict & MRP_HTBL_ITER_UNHASH)
+                    mrp_list_delete(iter.ep);
+                /* cb want us to free entry (and remove was not called) */
+                if (cb_verdict & MRP_HTBL_ITER_DELETE)
+                    free_entry(ht, entry, TRUE);
+
+                /* cb wants to stop iterating */
+                if (!(cb_verdict & MRP_HTBL_ITER_MORE))
+                    goto out;
+            }
+        }
+    }
+
+ out:
+    ht->iter = NULL;
+
+    return TRUE;
+}
+
+
+void *mrp_htbl_find(mrp_htbl_t *ht, mrp_htbl_find_cb_t cb, void *user_data)
+{
+    iter_t    iter;
+    bucket_t *bucket;
+    entry_t  *entry, *found;
+
+    /*
+     * Bail out if there is also an iterator active...
+     */
+    if (ht->iter != NULL)
+        return FALSE;
+
+    mrp_clear(&iter);
+    ht->iter = &iter;
+    found    = NULL;
+
+    mrp_list_foreach(&ht->used, iter.bp, iter.bn) {
+        bucket = mrp_list_entry(iter.bp, bucket_t, used);
+
+        mrp_list_foreach(&bucket->entries, iter.ep, iter.en) {
+            entry = mrp_list_entry(iter.ep, entry_t, hook);
+
+            if (cb(entry->key, entry->obj, user_data)) {
+                found = entry->obj;
+                goto out;
+            }
+        }
+    }
+
+ out:
+    ht->iter = NULL;
+
+    return found;
+}
diff --git a/src/common/hashtbl.h b/src/common/hashtbl.h
new file mode 100644 (file)
index 0000000..6dd7c05
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_HASHTBL_H__
+#define __MURPHY_HASHTBL_H__
+
+#include <stdint.h>
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+typedef struct mrp_htbl_s mrp_htbl_t;
+
+/** Prototype for key comparison functions. */
+typedef int (*mrp_htbl_comp_fn_t)(const void *key1, const void *key2);
+
+/** Prototoype for key hash functions. */
+typedef uint32_t (*mrp_htbl_hash_fn_t)(const void *key);
+
+/** Prototype for functions used to free entries. */
+typedef void (*mrp_htbl_free_fn_t)(void *key, void *object);
+
+
+/*
+ * hash table configuration
+ */
+typedef struct {
+    size_t             nentry;                   /* estimated entries */
+    mrp_htbl_comp_fn_t comp;                     /* comparison function */
+    mrp_htbl_hash_fn_t hash;                     /* hash function */
+    mrp_htbl_free_fn_t free;                     /* freeing function */
+    size_t             nbucket;                  /* number of buckets, or 0 */
+} mrp_htbl_config_t;
+
+
+/** Create a new hash table with the given configuration. */
+mrp_htbl_t *mrp_htbl_create(mrp_htbl_config_t *cfg);
+
+/** Destroy a hash table, free all entries unless @free is FALSE. */
+void mrp_htbl_destroy(mrp_htbl_t *ht, int free);
+
+/** Reset a hash table, also free all entries unless @free is FALSE. */
+void mrp_htbl_reset(mrp_htbl_t *ht, int free);
+
+/** Insert the given @key/@object pair to the hash table. */
+int mrp_htbl_insert(mrp_htbl_t *ht, void *key, void *object);
+
+/** Remove and return the object for @key, also free unless @free is FALSE. */
+void *mrp_htbl_remove(mrp_htbl_t *ht, void *key, int free);
+
+/** Look up the object corresponding to @key. */
+void *mrp_htbl_lookup(mrp_htbl_t *ht, void *key);
+
+/** Find the first matching entry in a hash table. */
+typedef int (*mrp_htbl_find_cb_t)(void *key, void *object, void *user_data);
+void *mrp_htbl_find(mrp_htbl_t *ht, mrp_htbl_find_cb_t cb, void *user_data);
+
+
+/*
+ * hash table iterators
+ */
+
+enum {
+    MRP_HTBL_ITER_STOP   = 0x0,                  /* stop iterating */
+    MRP_HTBL_ITER_MORE   = 0x1,                  /* keep iterating */
+    MRP_HTBL_ITER_UNHASH = 0x2,                  /* unhash without freeing */
+    MRP_HTBL_ITER_DELETE = 0x6,                  /* unhash and free */
+};
+
+typedef int (*mrp_htbl_iter_cb_t)(void *key, void *object, void *user_data);
+int mrp_htbl_foreach(mrp_htbl_t *ht, mrp_htbl_iter_cb_t cb, void *user_data);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_HASHTBL_H__ */
diff --git a/src/common/internal-transport.c b/src/common/internal-transport.c
new file mode 100644 (file)
index 0000000..7ffae37
--- /dev/null
@@ -0,0 +1,585 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include <murphy/common.h>
+
+#define INTERNAL "internal"
+
+typedef struct internal_s internal_t;
+
+struct internal_s {
+    MRP_TRANSPORT_PUBLIC_FIELDS; /* common transport fields */
+    char name[MRP_SOCKADDR_SIZE]; /* bound connection name */
+    mrp_sockaddr_t address; /* internal connection name*/
+    bool active;
+    bool bound;
+    bool listening;
+
+    internal_t *endpoint; /* that we are connected to */
+};
+
+typedef struct {
+    void *data;
+    size_t size;
+    internal_t *u;
+    mrp_sockaddr_t *addr;
+    socklen_t addrlen;
+    bool free_data;
+    int offset;
+    bool custom;
+    uint16_t tag;
+
+    mrp_list_hook_t hook;
+} internal_message_t;
+
+
+/* storage for the global data, TODO: refactor away */
+static mrp_htbl_t *servers = NULL;
+static mrp_htbl_t *connections = NULL;
+static mrp_list_hook_t msg_queue;
+static mrp_deferred_t *d;
+static uint32_t cid;
+
+
+static void process_queue(mrp_deferred_t *d, void *user_data)
+{
+    internal_message_t *msg;
+    internal_t *endpoint;
+    mrp_list_hook_t *p, *n;
+
+    MRP_UNUSED(user_data);
+
+    mrp_disable_deferred(d);
+
+    mrp_list_foreach(&msg_queue, p, n) {
+
+        msg = mrp_list_entry(p, typeof(*msg), hook);
+
+        if (!msg) {
+            mrp_log_error("no message!");
+            goto end;
+        }
+
+        if (!msg->u->connected) {
+            if (!msg->addr) {
+                mrp_log_error("connected transport without address!");
+                goto end;
+            }
+
+            /* Find the recipient. Look first from the server table.*/
+            endpoint = mrp_htbl_lookup(servers, msg->addr->data);
+
+            if (!endpoint) {
+
+                /* Look next from the general connections table. */
+                endpoint = mrp_htbl_lookup(connections, msg->addr->data);
+            }
+        }
+        else {
+            endpoint = msg->u->endpoint;
+        }
+
+        if (!endpoint || !endpoint->recv_data) {
+            mrp_log_error("no endpoint matching the address");
+            goto end;
+        }
+
+        /* skip the length word when sending */
+        endpoint->recv_data(
+                (mrp_transport_t *) endpoint, msg->data + msg->offset,
+                msg->size, &msg->u->address, MRP_SOCKADDR_SIZE);
+
+end:
+        if (msg) {
+
+            if (msg->free_data) {
+                if (msg->custom) {
+                    /* FIXME: should be mrp_data_free(msg->data, msg->tag); */
+                    mrp_free(msg->data);
+                }
+                else
+                    mrp_msg_unref(msg->data);
+            }
+
+            mrp_list_delete(&msg->hook);
+            mrp_free(msg);
+        }
+    }
+}
+
+
+static int internal_initialize_table(internal_t *u)
+{
+    mrp_htbl_config_t servers_conf;
+    mrp_htbl_config_t connections_conf;
+
+    MRP_UNUSED(u);
+
+    if (servers && connections && d)
+        return 0; /* already initialized */
+
+    servers_conf.comp = mrp_string_comp;
+    servers_conf.hash = mrp_string_hash;
+    servers_conf.free = NULL;
+    servers_conf.nbucket = 0;
+    servers_conf.nentry = 10;
+
+    servers = mrp_htbl_create(&servers_conf);
+
+    if (!servers)
+        goto error;
+
+    connections_conf.comp = mrp_string_comp;
+    connections_conf.hash = mrp_string_hash;
+    connections_conf.free = NULL;
+    connections_conf.nbucket = 0;
+    connections_conf.nentry = 10;
+
+    connections = mrp_htbl_create(&connections_conf);
+
+    if (!connections)
+        goto error;
+
+    mrp_list_init(&msg_queue);
+
+    cid = 0;
+
+    d = mrp_add_deferred(u->ml, process_queue, NULL);
+
+    if (!d)
+        goto error;
+
+    mrp_disable_deferred(d);
+
+    return 0;
+
+error:
+
+    if (servers)
+        mrp_htbl_destroy(servers, FALSE);
+
+    servers = NULL;
+
+    if (connections)
+        mrp_htbl_destroy(connections, FALSE);
+
+    connections = NULL;
+
+    return -1;
+}
+
+
+static socklen_t internal_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    int len;
+
+    MRP_UNUSED(size);
+
+    if (!str)
+        return 0;
+
+    len = strlen(str);
+
+    if (len <= 9 || len >= MRP_SOCKADDR_SIZE)
+        return 0;
+
+    if (strncmp("internal:", str, 9))
+        return 0;
+
+    if (typep)
+        *typep = INTERNAL;
+
+    memcpy(addr->data, str+9, len-9+1);
+
+    return len-9;
+}
+
+
+static int internal_open(mrp_transport_t *mu)
+{
+    internal_t *u = (internal_t *)mu;
+
+    if (internal_initialize_table(u) < 0)
+        return FALSE;
+
+    memset(u->name, 0, MRP_SOCKADDR_SIZE);
+    memset(u->address.data, 0, MRP_SOCKADDR_SIZE);
+
+    u->active = FALSE;
+
+    snprintf(u->address.data, MRP_SOCKADDR_SIZE, INTERNAL"_%d", cid++);
+
+    mrp_htbl_insert(connections, u->address.data, mu);
+
+    return TRUE;
+}
+
+
+static int internal_bind(mrp_transport_t *mu, mrp_sockaddr_t *addr,
+                     socklen_t addrlen)
+{
+    internal_t *u = (internal_t *)mu;
+
+    if (internal_initialize_table(u) < 0)
+        return FALSE;
+
+    memcpy(u->name, addr->data, addrlen+1);
+
+    mrp_htbl_insert(servers, u->name, u);
+
+    u->active = TRUE;
+    u->bound = TRUE;
+
+    return TRUE;
+}
+
+
+static int internal_listen(mrp_transport_t *mu, int backlog)
+{
+    internal_t *u = (internal_t *)mu;
+
+    MRP_UNUSED(backlog);
+
+    if (!u->bound)
+        return FALSE;
+
+    u->listening = TRUE;
+
+    return TRUE;
+}
+
+
+static int internal_accept(mrp_transport_t *mt, mrp_transport_t *mlt)
+{
+    internal_t *t = (internal_t *) mt;
+    internal_t *lt = (internal_t *) mlt;
+    internal_t *client = lt->endpoint;
+
+    t->endpoint = client;
+    client->endpoint = t;
+
+    lt->endpoint = NULL; /* connection process is now over */
+
+    return TRUE;
+}
+
+
+static void remove_messages(internal_t *u)
+{
+    internal_message_t *msg;
+    mrp_list_hook_t *p, *n;
+
+    mrp_list_foreach(&msg_queue, p, n) {
+        msg = mrp_list_entry(p, typeof(*msg), hook);
+
+        if (strcmp(msg->addr->data, u->name) == 0
+            || strcmp(msg->addr->data, u->address.data) == 0) {
+
+            if (msg->free_data) {
+                if (msg->custom) {
+                    /* FIXME: should be mrp_data_free(msg->data, msg->tag); */
+                    mrp_free(msg->data);
+                }
+                else
+                    mrp_msg_unref(msg->data);
+            }
+
+            mrp_list_delete(&msg->hook);
+            mrp_free(msg);
+        }
+    }
+}
+
+
+static void internal_close(mrp_transport_t *mu)
+{
+    internal_t *u = (internal_t *)mu;
+
+    /* Is this client or server? If server, go remove the connection from
+     * servers table. */
+
+    if (u->bound) {
+        /* server listening socket */
+        mrp_htbl_remove(servers, u->name, FALSE);
+        u->bound = FALSE;
+    }
+
+    mrp_htbl_remove(connections, u->address.data, FALSE);
+
+    u->active = FALSE;
+
+    remove_messages(u);
+}
+
+
+static int internal_connect(mrp_transport_t *mu, mrp_sockaddr_t *addr,
+                        socklen_t addrlen)
+{
+    internal_t *u = (internal_t *)mu;
+    internal_t *host;
+    mrp_transport_t *mt;
+
+    MRP_UNUSED(addrlen);
+
+    /* client connecting */
+
+    if (!servers) {
+        mrp_log_error("no servers available for connecting");
+        return FALSE;
+    }
+
+    host = mrp_htbl_lookup(servers, addr->data);
+
+    if (!host) {
+        mrp_log_error("server '%s' wasn't found", addr->data);
+        return FALSE;
+    }
+
+    mt = (mrp_transport_t *) host;
+
+    host->endpoint = u; /* temporary connection data */
+
+    host->evt.connection(mt, mt->user_data);
+
+    return TRUE;
+}
+
+
+static int internal_disconnect(mrp_transport_t *mu)
+{
+    internal_t *u = (internal_t *)mu;
+
+    if (u->connected) {
+        internal_t *endpoint = u->endpoint;
+
+        if (endpoint) {
+            endpoint->endpoint = NULL;
+            mrp_transport_disconnect((mrp_transport_t *) endpoint);
+        }
+        u->endpoint = NULL;
+    }
+
+    return TRUE;
+}
+
+
+static int internal_sendto(mrp_transport_t *mu, mrp_msg_t *data,
+                       mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    internal_t *u = (internal_t *)mu;
+    void *buf;
+    size_t size;
+    internal_message_t *msg;
+
+    size = mrp_msg_default_encode(data, &buf);
+
+    if (size == 0 || buf == NULL) {
+        return FALSE;
+    }
+
+    msg = mrp_allocz(sizeof(internal_message_t));
+
+    if (!msg)
+        return FALSE;
+
+    msg->addr = addr;
+    msg->addrlen = addrlen;
+    msg->data = buf;
+    msg->free_data = FALSE;
+    msg->offset = 0;
+    msg->size = size;
+    msg->u = u;
+    msg->custom = FALSE;
+
+    mrp_list_init(&msg->hook);
+    mrp_list_append(&msg_queue, &msg->hook);
+
+    mrp_enable_deferred(d);
+
+    return TRUE;
+}
+
+
+static int internal_send(mrp_transport_t *mu, mrp_msg_t *msg)
+{
+    if (!mu->connected) {
+        return FALSE;
+    }
+
+    return internal_sendto(mu, msg, NULL, 0);
+}
+
+
+static int internal_sendrawto(mrp_transport_t *mu, void *data, size_t size,
+                          mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    internal_t *u = (internal_t *)mu;
+    internal_message_t *msg;
+
+    msg = mrp_allocz(sizeof(internal_message_t));
+
+    if (!msg)
+        return FALSE;
+
+    msg->addr = addr;
+    msg->addrlen = addrlen;
+    msg->data = data;
+    msg->free_data = FALSE;
+    msg->offset = 0;
+    msg->size = size;
+    msg->u = u;
+    msg->custom = FALSE;
+
+    mrp_list_init(&msg->hook);
+    mrp_list_append(&msg_queue, &msg->hook);
+
+    mrp_enable_deferred(d);
+
+    return TRUE;
+}
+
+
+static int internal_sendraw(mrp_transport_t *mu, void *data, size_t size)
+{
+    if (!mu->connected) {
+        return FALSE;
+    }
+
+    return internal_sendrawto(mu, data, size, NULL, 0);
+}
+
+
+static size_t encode_custom_data(void *data, void **newdata, uint16_t tag)
+{
+    mrp_data_descr_t *type = mrp_msg_find_type(tag);
+    uint32_t *lenp;
+    uint16_t *tagp;
+    size_t reserve, size;
+    int len;
+    void *buf;
+
+    if (type == NULL) {
+        mrp_log_error("type not found!");
+        return 0;
+    }
+
+    reserve = sizeof(*lenp) + sizeof(*tagp);
+    size = mrp_data_encode(&buf, data, type, reserve);
+
+    if (size == 0) {
+        mrp_log_error("data encoding failed");
+        return 0;
+    }
+
+    /* some format conversion */
+
+    lenp = buf;
+    len = size - sizeof(*lenp);
+    tagp = buf + sizeof(*lenp);
+
+    *tagp = htobe16(tag);
+
+    *newdata = buf;
+
+    return len;
+}
+
+
+static int internal_senddatato(mrp_transport_t *mu, void *data, uint16_t tag,
+                           mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    internal_t *u = (internal_t *)mu;
+    mrp_data_descr_t *type = mrp_msg_find_type(tag);
+    void *newdata = NULL;
+    size_t size;
+    internal_message_t *msg;
+
+    if (type == NULL)
+        return FALSE;
+
+    msg = mrp_allocz(sizeof(internal_message_t));
+
+    if (!msg)
+        return FALSE;
+
+    size = encode_custom_data(data, &newdata, tag);
+
+    if (!newdata) {
+        mrp_log_error("custom data encoding failed");
+        mrp_free(msg);
+        return FALSE;
+    }
+
+    msg->addr = addr;
+    msg->addrlen = addrlen;
+    msg->data = newdata;
+    msg->free_data = TRUE;
+    msg->offset = 4;
+    msg->size = size;
+    msg->u = u;
+    msg->custom = TRUE;
+    msg->tag = tag;
+
+    mrp_list_init(&msg->hook);
+    mrp_list_append(&msg_queue, &msg->hook);
+
+    mrp_enable_deferred(d);
+
+    return TRUE;
+}
+
+
+static int internal_senddata(mrp_transport_t *mu, void *data, uint16_t tag)
+{
+    if (!mu->connected) {
+        return FALSE;
+    }
+
+    return internal_senddatato(mu, data, tag, NULL, 0);
+}
+
+
+
+
+MRP_REGISTER_TRANSPORT(internal, INTERNAL, internal_t, internal_resolve,
+                       internal_open, NULL, internal_close, NULL,
+                       internal_bind, internal_listen, internal_accept,
+                       internal_connect, internal_disconnect,
+                       internal_send, internal_sendto,
+                       internal_sendraw, internal_sendrawto,
+                       internal_senddata, internal_senddatato,
+                       NULL, NULL,
+                       NULL, NULL,
+                       NULL, NULL);
diff --git a/src/common/json.c b/src/common/json.c
new file mode 100644 (file)
index 0000000..c275330
--- /dev/null
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "murphy/config.h"
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/json.h>
+
+/** Type for a JSON parser. */
+typedef struct json_tokener mrp_json_parser_t;
+
+static mrp_json_parser_t *parser;
+
+mrp_json_t *mrp_json_create(mrp_json_type_t type, ...)
+{
+    mrp_json_t *o;
+    const char *s;
+    bool        b;
+    int         i, l;
+    double      d;
+    va_list     ap;
+
+    va_start(ap, type);
+    switch (type) {
+    case MRP_JSON_STRING:
+        s = va_arg(ap, const char *);
+        l = va_arg(ap, int);
+        if (l < 0)
+            o = json_object_new_string(s);
+        else
+            o = json_object_new_string_len(s, l);
+        break;
+    case MRP_JSON_BOOLEAN:
+        b = va_arg(ap, int);
+        o = json_object_new_boolean(b);
+        break;
+    case MRP_JSON_INTEGER:
+        i = va_arg(ap, int);
+        o = json_object_new_int(i);
+        break;
+    case MRP_JSON_DOUBLE:
+        d = va_arg(ap, double);
+        o = json_object_new_double(d);
+        break;
+    case MRP_JSON_OBJECT:
+        o = json_object_new_object();
+        break;
+    case MRP_JSON_ARRAY:
+        o = json_object_new_array();
+        break;
+    default:
+        o = NULL;
+    }
+    va_end(ap);
+
+    return o;
+}
+
+
+mrp_json_t *mrp_json_clone(mrp_json_t *o)
+{
+    if (o != NULL)
+        return mrp_json_string_to_object(mrp_json_object_to_string(o), -1);
+    else
+        return NULL;
+}
+
+
+mrp_json_t *mrp_json_string_to_object(const char *s, int len)
+{
+    if (parser == NULL) {
+        parser = json_tokener_new();
+
+        if (parser == NULL)
+            return NULL;
+    }
+    else
+        json_tokener_reset(parser);
+
+    if (len < 0)
+        len = strlen(s);
+
+    return json_tokener_parse_ex(parser, s, len);
+}
+
+
+const char *mrp_json_object_to_string(mrp_json_t *o)
+{
+    if (o != NULL)
+        return json_object_to_json_string(o);
+    else
+        return "{}";
+}
+
+
+mrp_json_t *mrp_json_ref(mrp_json_t *o)
+{
+    return json_object_get(o);
+}
+
+
+void mrp_json_unref(mrp_json_t *o)
+{
+    json_object_put(o);
+}
+
+
+mrp_json_type_t mrp_json_get_type(mrp_json_t *o)
+{
+    return json_object_get_type(o);
+}
+
+
+int mrp_json_is_type(mrp_json_t *o, mrp_json_type_t type)
+{
+    return json_object_is_type(o, type);
+}
+
+
+void mrp_json_add(mrp_json_t *o, const char *key, mrp_json_t *m)
+{
+    json_object_object_add(o, key, m);
+}
+
+
+mrp_json_t *mrp_json_add_member(mrp_json_t *o, const char *key,
+                                mrp_json_type_t type, ...)
+{
+    mrp_json_t *m;
+    const char *s;
+    bool        b;
+    int         i, l;
+    double      d;
+    va_list     ap;
+
+    va_start(ap, type);
+    switch (type) {
+    case MRP_JSON_STRING:
+        s = va_arg(ap, const char *);
+        l = va_arg(ap, int);
+        if (l < 0)
+            m = json_object_new_string(s);
+        else
+            m = json_object_new_string_len(s, l);
+        break;
+    case MRP_JSON_BOOLEAN:
+        b = va_arg(ap, int);
+        m = json_object_new_boolean(b);
+        break;
+    case MRP_JSON_INTEGER:
+        i = va_arg(ap, int);
+        m = json_object_new_int(i);
+        break;
+    case MRP_JSON_DOUBLE:
+        d = va_arg(ap, double);
+        m = json_object_new_double(d);
+        break;
+    case MRP_JSON_OBJECT:
+        m = json_object_new_object();
+        break;
+    case MRP_JSON_ARRAY:
+        m = json_object_new_array();
+        break;
+    default:
+        m = NULL;
+        errno = EINVAL;
+    }
+    va_end(ap);
+
+    if (m != NULL)
+        json_object_object_add(o, key, m);
+
+    return m;
+}
+
+
+mrp_json_t *mrp_json_add_array(mrp_json_t *o, const char *key,
+                               mrp_json_type_t type, ...)
+{
+    va_list      ap;
+    void        *arr;
+    size_t       cnt, i;
+    mrp_json_t  *a;
+
+    va_start(ap, type);
+    arr = va_arg(ap, void *);
+    cnt = va_arg(ap, size_t);
+    a   = mrp_json_create(MRP_JSON_ARRAY);
+
+    if (a == NULL)
+        goto fail;
+
+    switch (type) {
+    case MRP_JSON_STRING:
+        for (i = 0; i < cnt; i++) {
+            if (!mrp_json_array_append_string(a, ((char **)arr)[i]))
+                goto fail;
+        }
+        break;
+
+    case MRP_JSON_INTEGER:
+        for (i = 0; i < cnt; i++) {
+            if (!mrp_json_array_append_integer(a, ((int *)arr)[i]))
+                goto fail;
+        }
+        break;
+
+    case MRP_JSON_DOUBLE:
+        for (i = 0; i < cnt; i++) {
+            if (!mrp_json_array_append_double(a, ((double *)arr)[i]))
+                goto fail;
+        }
+        break;
+
+    case MRP_JSON_BOOLEAN:
+        for (i = 0; i < cnt; i++) {
+            if (!mrp_json_array_append_boolean(a, ((bool *)arr)[i]))
+                goto fail;
+        }
+        break;
+
+    default:
+        goto fail;
+
+    }
+
+    va_end(ap);
+
+    mrp_json_add(o, key, a);
+    return a;
+
+ fail:
+    va_end(ap);
+    mrp_json_unref(a);
+
+    return NULL;
+}
+
+
+mrp_json_t *mrp_json_get(mrp_json_t *o, const char *key)
+{
+    mrp_json_iter_t  it;
+    const char      *k;
+    mrp_json_t      *v;
+
+    mrp_json_foreach_member(o, k, v, it) {
+        if (!strcmp(k, key))
+            return v;
+    }
+
+    return NULL;
+}
+
+
+int mrp_json_get_member(mrp_json_t *o, const char *key,
+                        mrp_json_type_t type, ...)
+{
+    const char **s;
+    bool        *b;
+    int         *i;
+    double      *d;
+    mrp_json_t  *m, **mp;
+    int          success;
+    va_list      ap;
+
+    success = FALSE;
+    va_start(ap, type);
+
+    m = mrp_json_get(o, key);
+
+    if (m != NULL) {
+        if (json_object_is_type(m, type)) {
+            success = TRUE;
+            switch (type) {
+            case MRP_JSON_STRING:
+                s  = va_arg(ap, const char **);
+                *s = json_object_get_string(m);
+                break;
+            case MRP_JSON_BOOLEAN:
+                b  = va_arg(ap, bool *);
+                *b = json_object_get_boolean(m);
+                break;
+            case MRP_JSON_INTEGER:
+                i  = va_arg(ap, int *);
+                *i = json_object_get_int(m);
+                break;
+            case MRP_JSON_DOUBLE:
+                d  = va_arg(ap, double *);
+                *d = json_object_get_double(m);
+                break;
+            case MRP_JSON_OBJECT:
+                mp  = va_arg(ap, mrp_json_t **);
+                *mp = m;
+                break;
+            case MRP_JSON_ARRAY:
+                mp  = va_arg(ap, mrp_json_t **);
+                *mp = m;
+                break;
+            default:
+                success = FALSE;
+            }
+        }
+        else
+            errno = EINVAL;
+    }
+    else {
+        errno = ENOENT;
+        success = FALSE;
+    }
+
+    va_end(ap);
+
+    return success;
+}
+
+
+void mrp_json_del_member(mrp_json_t *o, const char *key)
+{
+    json_object_object_del(o, key);
+}
+
+
+int mrp_json_array_length(mrp_json_t *a)
+{
+    return json_object_array_length(a);
+}
+
+
+int mrp_json_array_append(mrp_json_t *a, mrp_json_t *v)
+{
+    return json_object_array_add(a, v) == 0;
+}
+
+
+mrp_json_t *mrp_json_array_append_item(mrp_json_t *a, mrp_json_type_t type, ...)
+{
+    mrp_json_t *v;
+    const char *s;
+    bool        b;
+    int         i, l;
+    double      d;
+    va_list     ap;
+
+    va_start(ap, type);
+    switch (type) {
+    case MRP_JSON_STRING:
+        s = va_arg(ap, const char *);
+        l = va_arg(ap, int);
+        if (l < 0)
+            v = json_object_new_string(s);
+        else
+            v = json_object_new_string_len(s, l);
+        break;
+    case MRP_JSON_BOOLEAN:
+        b = va_arg(ap, int);
+        v = json_object_new_boolean(b);
+        break;
+    case MRP_JSON_INTEGER:
+        i = va_arg(ap, int);
+        v = json_object_new_int(i);
+        break;
+    case MRP_JSON_DOUBLE:
+        d = va_arg(ap, double);
+        v = json_object_new_double(d);
+        break;
+    case MRP_JSON_OBJECT:
+        v = va_arg(ap, mrp_json_t *);
+        break;
+    case MRP_JSON_ARRAY:
+        v = va_arg(ap, mrp_json_t *);
+        break;
+    default:
+        v = NULL;
+        errno = EINVAL;
+    }
+    va_end(ap);
+
+    if (v != NULL) {
+        if (json_object_array_add(a, v) == 0)
+            return v;
+        else {
+            mrp_json_unref(v);
+            errno = ENOMEM;
+        }
+    }
+
+    return NULL;
+}
+
+
+int mrp_json_array_set(mrp_json_t *a, int idx, mrp_json_t *v)
+{
+    return json_object_array_put_idx(a, idx, v);
+}
+
+
+int mrp_json_array_set_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...)
+{
+    mrp_json_t *v;
+    const char *s;
+    bool        b;
+    int         i, l;
+    double      d;
+    va_list     ap;
+
+    va_start(ap, type);
+    switch (type) {
+    case MRP_JSON_STRING:
+        s = va_arg(ap, const char *);
+        l = va_arg(ap, int);
+        if (l < 0)
+            v = json_object_new_string(s);
+        else
+            v = json_object_new_string_len(s, l);
+        break;
+    case MRP_JSON_BOOLEAN:
+        b = va_arg(ap, int);
+        v = json_object_new_boolean(b);
+        break;
+    case MRP_JSON_INTEGER:
+        i = va_arg(ap, int);
+        v = json_object_new_int(i);
+        break;
+    case MRP_JSON_DOUBLE:
+        d = va_arg(ap, double);
+        v = json_object_new_double(d);
+        break;
+    case MRP_JSON_OBJECT:
+        v = va_arg(ap, mrp_json_t *);
+        break;
+    case MRP_JSON_ARRAY:
+        v = va_arg(ap, mrp_json_t *);
+        break;
+    default:
+        v = NULL;
+        errno = EINVAL;
+    }
+    va_end(ap);
+
+    if (v != NULL)
+        return json_object_array_put_idx(a, idx, v);
+    else {
+        errno = ENOMEM;
+        return FALSE;
+    }
+}
+
+
+mrp_json_t *mrp_json_array_get(mrp_json_t *a, int idx)
+{
+    return json_object_array_get_idx(a, idx);
+}
+
+
+int mrp_json_array_get_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...)
+{
+    const char **s;
+    bool        *b;
+    int         *i;
+    double      *d;
+    mrp_json_t  *v, **vp;
+    int          success;
+    va_list      ap;
+
+    success = FALSE;
+    va_start(ap, type);
+
+    v = json_object_array_get_idx(a, idx);
+
+    if (v != NULL) {
+        if (json_object_is_type(v, type)) {
+            success = TRUE;
+            switch (type) {
+            case MRP_JSON_STRING:
+                s  = va_arg(ap, const char **);
+                *s = json_object_get_string(v);
+                break;
+            case MRP_JSON_BOOLEAN:
+                b  = va_arg(ap, bool *);
+                *b = json_object_get_boolean(v);
+                break;
+            case MRP_JSON_INTEGER:
+                i  = va_arg(ap, int *);
+                *i = json_object_get_int(v);
+                break;
+            case MRP_JSON_DOUBLE:
+                d  = va_arg(ap, double *);
+                *d = json_object_get_double(v);
+                break;
+            case MRP_JSON_OBJECT:
+                vp  = va_arg(ap, mrp_json_t **);
+                *vp = v;
+                break;
+            case MRP_JSON_ARRAY:
+                vp  = va_arg(ap, mrp_json_t **);
+                *vp = v;
+                break;
+            default:
+                success = FALSE;
+                errno = EINVAL;
+            }
+        }
+        else
+            errno = EINVAL;
+    }
+    else
+        errno = ENOENT;
+
+    va_end(ap);
+
+    return success;
+}
+
+
+int mrp_json_parse_object(char **strp, int *lenp, mrp_json_t **op)
+{
+    char         *str;
+    int           len;
+    mrp_json_t   *o   = NULL;
+    json_tokener *tok = NULL;
+    int           res = -1;
+
+    if (strp == NULL || *strp == NULL) {
+        *op = NULL;
+        if (lenp != NULL)
+            *lenp = 0;
+
+        return 0;
+    }
+
+    str = *strp;
+    len = lenp ? *lenp : 0;
+
+    if (len <= 0)
+        len = strlen(str);
+
+    tok = json_tokener_new();
+
+    if (tok != NULL) {
+        o = json_tokener_parse_ex(tok, str, len);
+
+        if (o != NULL) {
+            *strp += tok->char_offset;
+            if (lenp != NULL)
+                *lenp -= tok->char_offset;
+
+            res = 0;
+        }
+        else {
+#ifdef HAVE_JSON_TOKENER_GET_ERROR
+            if (json_tokener_get_error(tok) != json_tokener_success)
+                errno = EINVAL;
+#else
+            if (tok->err != json_tokener_success)
+                errno = EINVAL;
+#endif
+            else
+                res = 0;
+        }
+
+        json_tokener_free(tok);
+    }
+    else
+        errno = ENOMEM;
+
+    *op = o;
+    return res;
+}
diff --git a/src/common/json.h b/src/common/json.h
new file mode 100644 (file)
index 0000000..746a6ef
--- /dev/null
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_JSON_H__
+#define __MURPHY_JSON_H__
+
+#include <stdarg.h>
+#include <stdbool.h>
+
+#include "murphy/config.h"
+
+#ifndef JSON_INCLUDE_PATH_JSONC
+#    include <json/json.h>
+#    include <json/linkhash.h>
+/* workaround for older broken json-c not exposing json_object_iter */
+#    include <json/json_object_private.h>
+#else
+#    include <json-c/json.h>
+#    include <json-c/linkhash.h>
+/* workaround for older broken json-c not exposing json_object_iter */
+#    include <json-c/json_object_private.h>
+#endif
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+/*
+ * We use json-c as the underlying json implementation, However, we do
+ * not want direct json-c dependencies to spread all over the code base
+ * (at least not yet). So we try to define here an envelop layer that
+ * hides json-c underneath.
+ */
+
+/** Type of a JSON object. */
+typedef json_object mrp_json_t;
+
+/** JSON object/member types. */
+typedef enum {
+    MRP_JSON_STRING  = json_type_string,
+    MRP_JSON_BOOLEAN = json_type_boolean,
+    MRP_JSON_INTEGER = json_type_int,
+    MRP_JSON_DOUBLE  = json_type_double,
+    MRP_JSON_OBJECT  = json_type_object,
+    MRP_JSON_ARRAY   = json_type_array
+} mrp_json_type_t;
+
+/** Type for a JSON member iterator. */
+typedef json_object_iter mrp_json_iter_t;
+
+/** Create a new JSON object of the given type. */
+mrp_json_t *mrp_json_create(mrp_json_type_t type, ...);
+
+/** Clone the given JSON object, creating a new private copy of it. */
+mrp_json_t *mrp_json_clone(mrp_json_t *o);
+
+/** Deserialize a string to a JSON object. */
+mrp_json_t *mrp_json_string_to_object(const char *str, int len);
+
+/** Serialize a JSON object to a string. */
+const char *mrp_json_object_to_string(mrp_json_t *o);
+
+/** Add a reference to the given JSON object. */
+mrp_json_t *mrp_json_ref(mrp_json_t *o);
+
+/** Remove a reference from the given JSON object, freeing if it was last. */
+void mrp_json_unref(mrp_json_t *o);
+
+/** Get the type of a JSON object. */
+mrp_json_type_t mrp_json_get_type(mrp_json_t *o);
+
+/** Check if a JSON object has the given type. */
+int mrp_json_is_type(mrp_json_t *o, mrp_json_type_t type);
+
+/** Convenience macros to get values of JSON objects of basic types. */
+#define mrp_json_string_value(o)  json_object_get_string(o)
+#define mrp_json_integer_value(o) json_object_get_int(o)
+#define mrp_json_double_value(o)  json_object_get_double(o)
+#define mrp_json_boolean_value(o) json_object_get_boolean(o)
+
+/** Set a member of a JSON object. */
+void mrp_json_add(mrp_json_t *o, const char *key, mrp_json_t *m);
+
+/** Create a new JSON object and set it as a member of another object. */
+mrp_json_t *mrp_json_add_member(mrp_json_t *o, const char *key,
+                                mrp_json_type_t type, ...);
+
+/** Convenience macros to add members of various basic types. */
+#define mrp_json_add_string(o, key, s) \
+    mrp_json_add_member(o, key, MRP_JSON_STRING, s, -1)
+
+#define mrp_json_add_string_slice(o, key, s, l)         \
+    mrp_json_add_member(o, key, MRP_JSON_STRING, s, l)
+
+#define mrp_json_add_integer(o, key, i) \
+    mrp_json_add_member(o, key, MRP_JSON_INTEGER, i)
+
+#define mrp_json_add_double(o, key, d) \
+    mrp_json_add_member(o, key, MRP_JSON_DOUBLE, d)
+
+#define mrp_json_add_boolean(o, key, b) \
+    mrp_json_add_member(o, key, MRP_JSON_BOOLEAN, (int)b)
+
+/** Add an array member from a native C array of the given type. */
+mrp_json_t *mrp_json_add_array(mrp_json_t *o, const char *key,
+                               mrp_json_type_t type, ...);
+
+/** Convenience macros for adding arrays of various basic types. */
+#define mrp_json_add_string_array(o, key, arr, size) \
+    mrp_json_add_array(o, key, MRP_JSON_STRING, arr, size)
+
+#define mrp_json_add_int_array(o, key, arr, size) \
+    mrp_json_add_array(o, key, MRP_JSON_INTEGER, arr, size)
+
+#define mrp_json_add_double_array(o, key, arr, size) \
+    mrp_json_add_array(o, key, MRP_JSON_DOUBLE, arr, size)
+
+#define mrp_json_add_boolean_array(o, key, arr, size) \
+    mrp_json_add_array(o, key, MRP_JSON_BOOLEAN, arr, size)
+
+/** Get the member of a JSON object as a json object. */
+mrp_json_t *mrp_json_get(mrp_json_t *o, const char *key);
+
+/** Get the member of a JSON object in a type specific format. */
+int mrp_json_get_member(mrp_json_t *o, const char *key,
+                        mrp_json_type_t type, ...);
+
+/** Convenience macros to get members of various types. */
+#define mrp_json_get_string(o, key, sptr)               \
+    mrp_json_get_member(o, key, MRP_JSON_STRING, sptr)
+
+#define mrp_json_get_integer(o, key, iptr)              \
+    mrp_json_get_member(o, key, MRP_JSON_INTEGER, iptr)
+
+#define mrp_json_get_double(o, key, dptr)               \
+    mrp_json_get_member(o, key, MRP_JSON_DOUBLE, dptr)
+
+#define mrp_json_get_boolean(o, key, bptr)              \
+    mrp_json_get_member(o, key, MRP_JSON_BOOLEAN, bptr)
+
+#define mrp_json_get_array(o, key, aptr)                \
+    mrp_json_get_member(o, key, MRP_JSON_ARRAY, aptr)
+
+#define mrp_json_get_object(o, key, optr)               \
+    mrp_json_get_member(o, key, MRP_JSON_OBJECT, optr)
+
+/** Delete a member of a JSON object. */
+void mrp_json_del_member(mrp_json_t *o, const char *key);
+
+/** Get the length of a JSON array object. */
+int mrp_json_array_length(mrp_json_t *a);
+
+/** Append a JSON object to an array object. */
+int mrp_json_array_append(mrp_json_t *a, mrp_json_t *v);
+
+/** Create and append a new item to a JSON array object. */
+mrp_json_t *mrp_json_array_append_item(mrp_json_t *a, mrp_json_type_t type,
+                                       ...);
+
+/** Convenience macros for appending array items of basic types. */
+#define mrp_json_array_append_string(a, s) \
+    mrp_json_array_append_item(a, MRP_JSON_STRING, s, -1)
+
+#define mrp_json_array_append_string_slice(a, s, l)       \
+    mrp_json_array_append_item(a, MRP_JSON_STRING, s, l)
+
+
+#define mrp_json_array_append_integer(a, i) \
+    mrp_json_array_append_item(a, MRP_JSON_INTEGER, (int)i)
+
+#define mrp_json_array_append_double(a, d) \
+    mrp_json_array_append_item(a, MRP_JSON_DOUBLE, 1.0*d)
+
+#define mrp_json_array_append_boolean(a, b) \
+    mrp_json_array_append_item(a, MRP_JSON_BOOLEAN, (int)b)
+
+/** Add a JSON object to a given index of an array object. */
+int mrp_json_array_set(mrp_json_t *a, int idx, mrp_json_t *v);
+
+/** Add a JSON object to a given index of an array object. */
+int mrp_json_array_set_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...);
+
+/** Get the object at a given index of a JSON array object. */
+mrp_json_t *mrp_json_array_get(mrp_json_t *a, int idx);
+
+/** Get the element of a JSON array object at an index. */
+int mrp_json_array_get_item(mrp_json_t *a, int idx, mrp_json_type_t type, ...);
+
+/** Convenience macros to get items of certain types from an array. */
+#define mrp_json_array_get_string(a, idx, sptr) \
+    mrp_json_array_get_item(a, idx, MRP_JSON_STRING, sptr)
+
+#define mrp_json_array_get_integer(a, idx, iptr) \
+    mrp_json_array_get_item(a, idx, MRP_JSON_INTEGER, iptr)
+
+#define mrp_json_array_get_double(a, idx, dptr) \
+    mrp_json_array_get_item(a, idx, MRP_JSON_DOUBLE, dptr)
+
+#define mrp_json_array_get_boolean(a, idx, bptr) \
+    mrp_json_array_get_item(a, idx, MRP_JSON_BOOLEAN, bptr)
+
+#define mrp_json_array_get_array(a, idx, aptr) \
+    mrp_json_array_get_item(a, idx, MRP_JSON_ARRAY, aptr)
+
+#define mrp_json_array_get_object(a, idx, optr) \
+    mrp_json_array_get_item(a, idx, MRP_JSON_OBJECT, optr)
+
+/** Iterate through the members of an object. */
+#define mrp_json_foreach_member(o, _k, _v, it)                  \
+    for (it.entry = json_object_get_object((o))->head;          \
+         (it.entry ?                                            \
+          (_k = it.key = it.entry->k,                           \
+           _v = it.val = (mrp_json_t *)it.entry->v,             \
+           it.entry) : 0);                                      \
+         it.entry = it.entry->next)
+
+/** Parse a JSON object from the given string. */
+int mrp_json_parse_object(char **str, int *len, mrp_json_t **op);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_JSON_H__ */
diff --git a/src/common/libdbus-glue.c b/src/common/libdbus-glue.c
new file mode 100644 (file)
index 0000000..d1bb093
--- /dev/null
@@ -0,0 +1,374 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <dbus/dbus.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+
+typedef struct dbus_glue_s dbus_glue_t;
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_io_watch_t  *mw;
+    DBusWatch       *dw;
+    mrp_list_hook_t  hook;
+} watch_t;
+
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_timer_t     *mt;
+    DBusTimeout     *dt;
+    mrp_list_hook_t  hook;
+} timeout_t;
+
+
+struct dbus_glue_s {
+    DBusConnection  *conn;
+    mrp_mainloop_t  *ml;
+    mrp_list_hook_t  watches;
+    mrp_list_hook_t  timers;
+    mrp_deferred_t  *pump;
+};
+
+
+static dbus_int32_t data_slot = -1;
+
+static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events,
+                           void *user_data)
+{
+    watch_t        *watch = (watch_t *)user_data;
+    DBusConnection *conn  = watch->glue->conn;
+    unsigned int    mask  = 0;
+
+    MRP_UNUSED(mw);
+    MRP_UNUSED(fd);
+
+    if (events & MRP_IO_EVENT_IN)
+        mask |= DBUS_WATCH_READABLE;
+    if (events & MRP_IO_EVENT_OUT)
+        mask |= DBUS_WATCH_WRITABLE;
+    if (events & MRP_IO_EVENT_HUP)
+        mask |= DBUS_WATCH_HANGUP;
+    if (events & MRP_IO_EVENT_ERR)
+        mask |= DBUS_WATCH_ERROR;
+
+    dbus_connection_ref(conn);
+    dbus_watch_handle(watch->dw, mask);
+    dbus_connection_unref(conn);
+}
+
+
+static void watch_freed_cb(void *data)
+{
+    watch_t *watch = (watch_t *)data;
+
+    if (watch != NULL) {
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+        mrp_free(watch);
+    }
+}
+
+
+static dbus_bool_t add_watch(DBusWatch *dw, void *data)
+{
+    dbus_glue_t    *glue = (dbus_glue_t *)data;
+    watch_t        *watch;
+    mrp_io_watch_t *mw;
+    mrp_io_event_t  mask;
+    int             fd;
+    unsigned int    flags;
+
+    mrp_debug("adding D-BUS watch %p (%s)", dw,
+              dbus_watch_get_enabled(dw) ? "enabled" : "disabled");
+
+    if (!dbus_watch_get_enabled(dw))
+        return TRUE;
+
+    fd    = dbus_watch_get_unix_fd(dw);
+    flags = dbus_watch_get_flags(dw);
+    mask  = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR;
+
+    if (flags & DBUS_WATCH_READABLE)
+        mask |= MRP_IO_EVENT_IN;
+    if (flags & DBUS_WATCH_WRITABLE)
+        mask |= MRP_IO_EVENT_OUT;
+
+    mrp_debug("event mask for fd %d: %s%s", fd,
+              mask & MRP_IO_EVENT_IN  ? "read"  : "",
+              mask & MRP_IO_EVENT_OUT ? "write" : "");
+
+    if ((watch = mrp_allocz(sizeof(*watch))) != NULL) {
+        mrp_list_init(&watch->hook);
+        mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch);
+
+        if (mw != NULL) {
+            watch->glue = glue;
+            watch->mw   = mw;
+            watch->dw   = dw;
+            dbus_watch_set_data(dw, watch, watch_freed_cb);
+            mrp_list_append(&glue->watches, &watch->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(watch);
+    }
+
+    return FALSE;
+}
+
+
+static void del_watch(DBusWatch *dw, void *data)
+{
+    watch_t *watch = (watch_t *)dbus_watch_get_data(dw);
+
+    MRP_UNUSED(data);
+
+    mrp_debug("deleting D-BUS watch %p...", dw);
+
+    if (watch != NULL) {
+        mrp_del_io_watch(watch->mw);
+        watch->mw = NULL;
+    }
+}
+
+
+static void toggle_watch(DBusWatch *dw, void *data)
+{
+    mrp_debug("Toggling D-BUS watch %p...", dw);
+
+    if (dbus_watch_get_enabled(dw))
+        add_watch(dw, data);
+    else
+        del_watch(dw, data);
+}
+
+
+static void dispatch_timeout(mrp_timer_t *mt, void *user_data)
+{
+    timeout_t *timer = (timeout_t *)user_data;
+
+    MRP_UNUSED(mt);
+
+    mrp_debug("dispatching D-BUS timeout %p...", timer->dt);
+
+    dbus_timeout_handle(timer->dt);
+}
+
+
+static void timeout_freed_cb(void *data)
+{
+    timeout_t *timer = (timeout_t *)data;
+
+    if (timer != NULL) {
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+}
+
+
+static dbus_bool_t add_timeout(DBusTimeout *dt, void *data)
+{
+    dbus_glue_t  *glue = (dbus_glue_t *)data;
+    timeout_t    *timer;
+    mrp_timer_t  *mt;
+    unsigned int  msecs;
+
+    mrp_debug("adding D-BUS timeout %p...", dt);
+
+    if ((timer = mrp_allocz(sizeof(*timer))) != NULL) {
+        mrp_list_init(&timer->hook);
+        msecs = dbus_timeout_get_interval(dt);
+        mt    = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer);
+
+        if (mt != NULL) {
+            timer->glue = glue;
+            timer->mt   = mt;
+            timer->dt   = dt;
+            dbus_timeout_set_data(dt, timer, timeout_freed_cb);
+            mrp_list_append(&glue->timers, &timer->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(timer);
+    }
+
+    return FALSE;
+}
+
+
+static void del_timeout(DBusTimeout *dt, void *data)
+{
+    timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt);
+
+    MRP_UNUSED(data);
+
+    mrp_debug("deleting D-BUS timeout %p...", dt);
+
+    if (timer != NULL) {
+        mrp_del_timer(timer->mt);
+        timer->mt = NULL;
+    }
+}
+
+
+static void toggle_timeout(DBusTimeout *dt, void *data)
+{
+    mrp_debug("toggling D-BUS timeout %p...", dt);
+
+    if (dbus_timeout_get_enabled(dt))
+        add_timeout(dt, data);
+    else
+        del_timeout(dt, data);
+}
+
+
+static void wakeup_mainloop(void *data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)data;
+
+    mrp_debug("waking up mainloop...");
+
+    mrp_enable_deferred(glue->pump);
+}
+
+
+static void glue_free_cb(void *data)
+{
+    dbus_glue_t     *glue = (dbus_glue_t *)data;
+    mrp_list_hook_t *p, *n;
+    watch_t         *watch;
+    timeout_t       *timer;
+
+    mrp_list_foreach(&glue->watches, p, n) {
+        watch = mrp_list_entry(p, typeof(*watch), hook);
+
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+
+        mrp_free(watch);
+    }
+
+    mrp_list_foreach(&glue->timers, p, n) {
+        timer = mrp_list_entry(p, typeof(*timer), hook);
+
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+
+    mrp_free(glue);
+}
+
+
+static void pump_cb(mrp_deferred_t *d, void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    mrp_debug("dispatching dbus connection %p...", glue->conn);
+
+    if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE)
+        mrp_disable_deferred(d);
+}
+
+
+static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status,
+                               void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    MRP_UNUSED(conn);
+
+    switch (status) {
+    case DBUS_DISPATCH_COMPLETE:
+        mrp_debug("dispatching status for %p: complete", conn);
+        mrp_disable_deferred(glue->pump);
+        break;
+
+    case DBUS_DISPATCH_DATA_REMAINS:
+    case DBUS_DISPATCH_NEED_MEMORY:
+    default:
+        mrp_debug("dispatching status for %p: not complete yet", conn);
+        mrp_enable_deferred(glue->pump);
+        break;
+    }
+}
+
+
+int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn)
+{
+    dbus_glue_t *glue;
+
+    if (!dbus_connection_allocate_data_slot(&data_slot))
+        return FALSE;
+
+    if (dbus_connection_get_data(conn, data_slot) != NULL)
+        return FALSE;
+
+    if ((glue = mrp_allocz(sizeof(*glue))) != NULL) {
+        mrp_list_init(&glue->watches);
+        mrp_list_init(&glue->timers);
+        glue->pump = mrp_add_deferred(ml, pump_cb, glue);
+
+        if (glue->pump == NULL) {
+            mrp_free(glue);
+            return FALSE;
+        }
+
+        glue->ml   = ml;
+        glue->conn = conn;
+    }
+    else
+        return FALSE;
+
+    if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb))
+        return FALSE;
+
+    dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb,
+                                                 glue, NULL);
+
+    dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop,
+                                             glue, NULL);
+
+    return
+        dbus_connection_set_watch_functions(conn, add_watch, del_watch,
+                                            toggle_watch, glue, NULL) &&
+            dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout,
+                                              toggle_timeout, glue, NULL);
+}
diff --git a/src/common/libdbus.c b/src/common/libdbus.c
new file mode 100644 (file)
index 0000000..a4c7a24
--- /dev/null
@@ -0,0 +1,1471 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/refcnt.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/libdbus.h>
+
+
+#define DBUS_ADMIN_SERVICE   "org.freedesktop.DBus"
+#define DBUS_ADMIN_INTERFACE "org.freedesktop.DBus"
+#define DBUS_ADMIN_PATH      "/org/freedesktop/DBus"
+#define DBUS_NAME_CHANGED    "NameOwnerChanged"
+
+
+struct mrp_dbus_s {
+    char            *address;            /* bus address */
+    DBusConnection  *conn;               /* actual D-BUS connection */
+    mrp_mainloop_t  *ml;                 /* murphy mainloop */
+    mrp_htbl_t      *methods;            /* method handler table */
+    mrp_htbl_t      *signals;            /* signal handler table */
+    mrp_list_hook_t  name_trackers;      /* peer (name) watchers */
+    mrp_list_hook_t  calls;              /* pending calls */
+    uint32_t         call_id;            /* next call id */
+    const char      *unique_name;        /* our unique D-BUS address */
+    int              priv;               /* whether a private connection */
+    int              signal_filter;      /* if signal dispatching is set up */
+    int              register_fallback;  /* if the fallback object is set up */
+    mrp_refcnt_t     refcnt;             /* reference count */
+};
+
+
+/*
+ * Notes:
+ *
+ * At the moment we administer DBUS method and signal handlers
+ * in a very primitive way (subject to be changed later). For
+ * every bus instance we maintain two hash tables, one for methods
+ * and another for signals. Each method and signal handler is
+ * hashed in only by it's method/signal name to a linked list of
+ * method or signal handlers.
+ *
+ * When dispatching a method, we look up the chain with a matching
+ * method name, or the chain for "" in case a matching chain is
+ * not found, and invoke the handler which best matches the
+ * received message (by looking at the path, interface and name).
+ * Only one such handler is invoked at most.
+ *
+ * For signals we look up both the chain with a matching name and
+ * the chain for "" and invoke all signal handlers that match the
+ * received message (regardless of their return value).
+ */
+
+
+typedef struct {
+    char            *member;            /* signal/method name */
+    mrp_list_hook_t  handlers;          /* handlers with matching member */
+} handler_list_t;
+
+typedef struct {
+    mrp_list_hook_t     hook;
+    char               *sender;
+    char               *path;
+    char               *interface;
+    char               *member;
+    mrp_dbus_handler_t  handler;
+    void               *user_data;
+} handler_t;
+
+#define method_t handler_t
+#define signal_t handler_t
+
+
+typedef struct {
+    mrp_list_hook_t     hook;           /* hook to name tracker list */
+    char               *name;           /* name to track */
+    mrp_dbus_name_cb_t  cb;             /* status change callback */
+    void               *user_data;      /* opaque callback user data */
+    int32_t             qid;            /* initial query ID */
+} name_tracker_t;
+
+
+typedef struct {
+    mrp_dbus_t          *dbus;           /* DBUS connection */
+    int32_t              id;             /* call id */
+    mrp_dbus_reply_cb_t  cb;             /* completion notification callback */
+    void                *user_data;      /* opaque callback data */
+    DBusPendingCall     *pend;           /* pending DBUS call */
+    mrp_list_hook_t      hook;           /* hook to list of pending calls */
+} call_t;
+
+
+typedef struct {
+    mrp_mainloop_t *ml;                  /* mainloop for bus connection */
+    const char     *address;             /* address of bus */
+} bus_spec_t;
+
+static mrp_htbl_t *buses;
+
+
+
+static DBusHandlerResult dispatch_signal(DBusConnection *c,
+                                         DBusMessage *msg, void *data);
+static DBusHandlerResult dispatch_method(DBusConnection *c,
+                                         DBusMessage *msg, void *data);
+static void purge_name_trackers(mrp_dbus_t *dbus);
+static void purge_calls(mrp_dbus_t *dbus);
+static void handler_list_free_cb(void *key, void *entry);
+static void handler_free(handler_t *h);
+static int name_owner_change_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *data);
+static void call_free(call_t *call);
+
+
+
+
+static int purge_filters(void *key, void *entry, void *user_data)
+{
+    mrp_dbus_t      *dbus = (mrp_dbus_t *)user_data;
+    handler_list_t  *l    = (handler_list_t *)entry;
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    MRP_UNUSED(key);
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+        mrp_dbus_remove_filter(dbus,
+                               h->sender, h->path, h->interface,
+                               h->member, NULL);
+    }
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+void dbus_disconnect(mrp_dbus_t *dbus)
+{
+    if (dbus) {
+        mrp_htbl_remove(buses, dbus->conn, FALSE);
+
+        if (dbus->signals) {
+            mrp_htbl_foreach(dbus->signals, purge_filters, dbus);
+            mrp_htbl_destroy(dbus->signals, TRUE);
+        }
+        if (dbus->methods)
+            mrp_htbl_destroy(dbus->methods, TRUE);
+
+        if (dbus->conn != NULL) {
+            if (dbus->signal_filter)
+                dbus_connection_remove_filter(dbus->conn, dispatch_signal,
+                        dbus);
+            if (dbus->register_fallback)
+                dbus_connection_unregister_object_path(dbus->conn, "/");
+            if (dbus->priv)
+                dbus_connection_close(dbus->conn);
+            dbus_connection_unref(dbus->conn);
+        }
+
+        purge_name_trackers(dbus);
+        purge_calls(dbus);
+
+        mrp_free(dbus->address);
+        dbus->conn = NULL;
+        dbus->ml   = NULL;
+
+        mrp_free(dbus);
+    }
+}
+
+
+static int bus_cmp(const void *key1, const void *key2)
+{
+    return key2 - key1;
+}
+
+
+static uint32_t bus_hash(const void *key)
+{
+    uint32_t h;
+
+    h   = (ptrdiff_t)key;
+    h >>= 2 * sizeof(key);
+
+    return h;
+}
+
+
+static int find_bus_by_spec(void *key, void *object, void *user_data)
+{
+    mrp_dbus_t *dbus = (mrp_dbus_t *)object;
+    bus_spec_t *spec = (bus_spec_t *)user_data;
+
+    MRP_UNUSED(key);
+
+    if (dbus->ml == spec->ml && !strcmp(dbus->address, spec->address))
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static mrp_dbus_t *dbus_get(mrp_mainloop_t *ml, const char *address)
+{
+    mrp_htbl_config_t hcfg;
+    bus_spec_t        spec;
+
+    if (buses == NULL) {
+        mrp_clear(&hcfg);
+
+        hcfg.comp = bus_cmp;
+        hcfg.hash = bus_hash;
+        hcfg.free = NULL;
+
+        buses = mrp_htbl_create(&hcfg);
+
+        return NULL;
+    }
+    else {
+        spec.ml      = ml;
+        spec.address = address;
+
+        return mrp_htbl_find(buses, find_bus_by_spec, &spec);
+    }
+}
+
+
+mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address,
+                             DBusError *errp)
+{
+    static struct DBusObjectPathVTable vtable = {
+        .message_function = dispatch_method
+    };
+
+    mrp_htbl_config_t  hcfg;
+    mrp_dbus_t        *dbus;
+
+    if ((dbus = dbus_get(ml, address)) != NULL)
+        return mrp_dbus_ref(dbus);
+
+    if ((dbus = mrp_allocz(sizeof(*dbus))) == NULL)
+        return NULL;
+
+    mrp_list_init(&dbus->calls);
+    mrp_list_init(&dbus->name_trackers);
+    mrp_refcnt_init(&dbus->refcnt);
+
+    dbus->ml = ml;
+
+
+    mrp_dbus_error_init(errp);
+
+    /*
+     * connect to the bus
+     */
+
+    if (!strcmp(address, "system"))
+        dbus->conn = dbus_bus_get(DBUS_BUS_SYSTEM, errp);
+    else if (!strcmp(address, "session"))
+        dbus->conn = dbus_bus_get(DBUS_BUS_SESSION, errp);
+    else {
+        dbus->conn = dbus_connection_open_private(address, errp);
+        dbus->priv = TRUE;
+
+        if (dbus->conn == NULL || !dbus_bus_register(dbus->conn, errp))
+            goto fail;
+    }
+
+    if (dbus->conn == NULL)
+        goto fail;
+
+    dbus->address     = mrp_strdup(address);
+    dbus->unique_name = dbus_bus_get_unique_name(dbus->conn);
+
+    /*
+     * set up with mainloop
+     */
+
+    if (!mrp_dbus_setup_connection(ml, dbus->conn))
+        goto fail;
+
+    /*
+     * set up our message dispatchers and take our name on the bus
+     */
+
+    if (!dbus_connection_add_filter(dbus->conn, dispatch_signal, dbus, NULL)) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to set up signal dispatching.");
+        goto fail;
+    }
+    dbus->signal_filter = TRUE;
+
+    if (!dbus_connection_register_fallback(dbus->conn, "/", &vtable, dbus)) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to set up method dispatching.");
+        goto fail;
+    }
+    dbus->register_fallback = TRUE;
+
+    mrp_clear(&hcfg);
+    hcfg.comp = mrp_string_comp;
+    hcfg.hash = mrp_string_hash;
+    hcfg.free = handler_list_free_cb;
+
+    if ((dbus->methods = mrp_htbl_create(&hcfg)) == NULL) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to create DBUS method table.");
+        goto fail;
+    }
+
+    if ((dbus->signals = mrp_htbl_create(&hcfg)) == NULL) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to create DBUS signal table.");
+        goto fail;
+    }
+
+
+    /*
+     * install handler for NameOwnerChanged for tracking clients/peers
+     */
+
+    if (!mrp_dbus_add_signal_handler(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                                     DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                                     name_owner_change_cb, NULL)) {
+        dbus_set_error(errp, DBUS_ERROR_FAILED,
+                       "Failed to install NameOwnerChanged handler.");
+        goto fail;
+    }
+
+    /* install a 'safe' filter to avoid receiving all name change signals */
+    mrp_dbus_install_filter(dbus,
+                            DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                            DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                            DBUS_ADMIN_SERVICE, NULL);
+
+    mrp_list_init(&dbus->name_trackers);
+    dbus->call_id = 1;
+
+    if (mrp_htbl_insert(buses, dbus->conn, dbus))
+        return dbus;
+
+ fail:
+    dbus_disconnect(dbus);
+    return NULL;
+}
+
+
+mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus)
+{
+    return mrp_ref_obj(dbus, refcnt);
+}
+
+
+int mrp_dbus_unref(mrp_dbus_t *dbus)
+{
+    if (mrp_unref_obj(dbus, refcnt)) {
+        dbus_disconnect(dbus);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, DBusError *error)
+{
+    int flags, status;
+
+    mrp_dbus_error_init(error);
+
+    flags  = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE;
+    status = dbus_bus_request_name(dbus->conn, name, flags, error);
+
+    if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
+        return TRUE;
+    else {
+        if (status == DBUS_REQUEST_NAME_REPLY_EXISTS) {
+            if (error)
+                dbus_error_free(error);
+            dbus_set_error(error, DBUS_ERROR_FAILED, "name already taken");
+        }
+        return FALSE;
+    }
+}
+
+
+int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, DBusError *error)
+{
+    mrp_dbus_error_init(error);
+
+    if (dbus_bus_release_name(dbus->conn, name, error) != -1)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus)
+{
+    return dbus->unique_name;
+}
+
+static void name_owner_query_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *data)
+{
+    name_tracker_t *t = (name_tracker_t *)data;
+    const char     *owner;
+    int             state;
+
+    if (t->cb != NULL) {                /* tracker still active */
+        t->qid = 0;
+        state  = dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN;
+
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &owner,
+                                   DBUS_TYPE_INVALID))
+            owner = "<unknown>";
+
+        t->cb(dbus, t->name, state, owner, t->user_data);
+    }
+    else                                /* already requested to delete */
+        mrp_free(t);
+}
+
+
+static int name_owner_change_cb(mrp_dbus_t *dbus, DBusMessage *msg, void *data)
+{
+    const char      *name, *prev, *next;
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    MRP_UNUSED(data);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL)
+        return FALSE;
+
+    if (!dbus_message_get_args(msg, NULL,
+                               DBUS_TYPE_STRING, &name,
+                               DBUS_TYPE_STRING, &prev,
+                               DBUS_TYPE_STRING, &next,
+                               DBUS_TYPE_INVALID))
+        return FALSE;
+
+    /*
+     * Notes: XXX TODO
+     *    In principle t->cb could call mrp_dbus_forget for some other D-BUS
+     *    address than name. If that happened to be n (== p->hook.next) this
+     *    would result in a crash or memory corruption in the next iteration
+     *    of this loop (when handling n). We can easily get around this
+     *    problem by
+     *
+     *     1) adminstering in mrp_dbus_t that we're handing a NameOwnerChange
+     *     2) checking for this in mrp_dbus_forget_name and if it is the case
+     *        only marking the affected entry for deletion
+     *     3) removing entries marked for deletion in this loop (or just
+     *        ignoring them and making another pass in the end removing any
+     *        such entry).
+     */
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        if (!strcmp(name, t->name))
+            t->cb(dbus, name, next && *next, next, t->user_data);
+    }
+
+    return TRUE;
+}
+
+
+int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data)
+{
+    name_tracker_t *t;
+
+    if ((t = mrp_allocz(sizeof(*t))) != NULL) {
+        if ((t->name = mrp_strdup(name)) != NULL) {
+            t->cb        = cb;
+            t->user_data = user_data;
+
+            if (mrp_dbus_install_filter(dbus,
+                                        DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                                        DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                                        name, NULL)) {
+                mrp_list_append(&dbus->name_trackers, &t->hook);
+
+                t->qid = mrp_dbus_call(dbus,
+                                       DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                                       DBUS_ADMIN_SERVICE, "GetNameOwner", 5000,
+                                       name_owner_query_cb, t,
+                                       DBUS_TYPE_STRING, &t->name,
+                                       DBUS_TYPE_INVALID);
+                return TRUE;
+            }
+            else {
+                mrp_free(t->name);
+                mrp_free(t);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    mrp_dbus_remove_filter(dbus,
+                           DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                           DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                           name, NULL);
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        if (t->cb == cb && t->user_data == user_data && !strcmp(t->name,name)) {
+            mrp_list_delete(&t->hook);
+            mrp_free(t->name);
+
+            if (!t->qid)
+                mrp_free(t);
+            else {
+                t->cb        = NULL;
+                t->user_data = NULL;
+                t->name      = NULL;
+            }
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void purge_name_trackers(mrp_dbus_t *dbus)
+{
+    mrp_list_hook_t *p, *n;
+    name_tracker_t  *t;
+
+    mrp_list_foreach(&dbus->name_trackers, p, n) {
+        t = mrp_list_entry(p, name_tracker_t, hook);
+
+        mrp_list_delete(p);
+        mrp_dbus_remove_filter(dbus, DBUS_ADMIN_SERVICE, DBUS_ADMIN_PATH,
+                               DBUS_ADMIN_SERVICE, DBUS_NAME_CHANGED,
+                               t->name, NULL);
+        mrp_free(t->name);
+        mrp_free(t);
+    }
+}
+
+
+static handler_t *handler_alloc(const char *sender, const char *path,
+                                const char *interface, const char *member,
+                                mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_t *h;
+
+    if ((h = mrp_allocz(sizeof(*h))) != NULL) {
+        h->sender    = mrp_strdup(sender);
+        h->path      = mrp_strdup(path);
+        h->interface = mrp_strdup(interface);
+        h->member    = mrp_strdup(member);
+
+        if ((path && !h->path) || !h->interface || !h->member) {
+            handler_free(h);
+            return NULL;
+        }
+
+        h->handler   = handler;
+        h->user_data = user_data;
+
+        return h;
+    }
+
+    return NULL;
+}
+
+
+static void handler_free(handler_t *h)
+{
+    if (h != NULL) {
+        mrp_free(h->sender);
+        mrp_free(h->path);
+        mrp_free(h->interface);
+        mrp_free(h->member);
+
+        mrp_free(h);
+    }
+}
+
+
+static handler_list_t *handler_list_alloc(const char *member)
+{
+    handler_list_t *l;
+
+    if ((l = mrp_allocz(sizeof(*l))) != NULL) {
+        if ((l->member = mrp_strdup(member)) != NULL)
+            mrp_list_init(&l->handlers);
+        else {
+            mrp_free(l);
+            l = NULL;
+        }
+    }
+
+    return l;
+}
+
+
+static inline void handler_list_free(handler_list_t *l)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+        mrp_list_delete(p);
+        handler_free(h);
+    }
+
+    mrp_free(l->member);
+    mrp_free(l);
+}
+
+
+static void handler_list_free_cb(void *key, void *entry)
+{
+    MRP_UNUSED(key);
+
+    handler_list_free((handler_list_t *)entry);
+}
+
+
+static inline int handler_specificity(handler_t *h)
+{
+    int score = 0;
+
+    if (h->path && *h->path)
+        score |= 0x4;
+    if (h->interface && *h->interface)
+        score |= 0x2;
+    if (h->member && *h->member)
+        score |= 0x1;
+
+    return score;
+}
+
+
+static void handler_list_insert(handler_list_t *l, handler_t *handler)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+    int              score;
+
+    score = handler_specificity(handler);
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (score >= handler_specificity(h)) {
+            mrp_list_append(h->hook.prev, &handler->hook);  /* add before h */
+            return;
+        }
+    }
+
+    mrp_list_append(&l->handlers, &handler->hook);
+}
+
+
+static handler_t *handler_list_lookup(handler_list_t *l, const char *path,
+                                      const char *interface, const char *member,
+                                      mrp_dbus_handler_t handler,
+                                      void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (h->handler == handler && user_data == h->user_data &&
+            path      && !strcmp(path, h->path) &&
+            interface && !strcmp(interface, h->interface) &&
+            member    && !strcmp(member, h->member))
+            return h;
+    }
+
+    return NULL;
+}
+
+
+static handler_t *handler_list_find(handler_list_t *l, const char *path,
+                                    const char *interface, const char *member)
+{
+#define MATCHES(h, field) (!*field || !*h->field || !strcmp(field, h->field))
+    mrp_list_hook_t *p, *n;
+    handler_t       *h;
+
+    mrp_list_foreach(&l->handlers, p, n) {
+        h = mrp_list_entry(p, handler_t, hook);
+
+        if (MATCHES(h, path) && MATCHES(h, interface) && MATCHES(h, member))
+            return h;
+    }
+
+    return NULL;
+#undef MATCHES
+}
+
+
+int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_list_t *methods;
+    handler_t      *m;
+
+    if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL) {
+        if ((methods = handler_list_alloc(member)) == NULL)
+            return FALSE;
+
+        mrp_htbl_insert(dbus->methods, methods->member, methods);
+    }
+
+    m = handler_alloc(NULL, path, interface, member, handler, user_data);
+    if (m != NULL) {
+        handler_list_insert(methods, m);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data)
+{
+    handler_list_t *methods;
+    handler_t      *m;
+
+    if ((methods = mrp_htbl_lookup(dbus->methods, (void *)member)) == NULL)
+        return FALSE;
+
+    m = handler_list_lookup(methods, path, interface, member,
+                            handler, user_data);
+    if (m != NULL) {
+        mrp_list_delete(&m->hook);
+        handler_free(m);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data)
+{
+    handler_list_t *signals;
+    handler_t      *s;
+
+    if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL) {
+        if ((signals = handler_list_alloc(member)) == NULL)
+            return FALSE;
+
+        if (!mrp_htbl_insert(dbus->signals, signals->member, signals)) {
+            handler_list_free(signals);
+            return FALSE;
+        }
+    }
+
+    s = handler_alloc(sender, path, interface, member, handler, user_data);
+    if (s != NULL) {
+        handler_list_insert(signals, s);
+        return TRUE;
+    }
+    else {
+        handler_free(s);
+        if (mrp_list_empty(&signals->handlers))
+            mrp_htbl_remove(dbus->signals, signals->member, TRUE);
+        return FALSE;
+    }
+}
+
+
+
+int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data)
+{
+    handler_list_t *signals;
+    handler_t      *s;
+
+    MRP_UNUSED(sender);
+
+    if ((signals = mrp_htbl_lookup(dbus->signals, (void *)member)) == NULL)
+        return FALSE;
+
+    s = handler_list_lookup(signals, path, interface, member,
+                            handler, user_data);
+    if (s != NULL) {
+        mrp_list_delete(&s->hook);
+        handler_free(s);
+
+        if (mrp_list_empty(&signals->handlers))
+            mrp_htbl_remove(dbus->signals, (void *)member, TRUE);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+
+int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus,
+                              mrp_dbus_handler_t handler, void *user_data,
+                              const char *sender, const char *path,
+                              const char *interface, const char *member, ...)
+{
+    va_list ap;
+    int     success;
+
+
+    if (mrp_dbus_add_signal_handler(dbus, sender, path, interface, member,
+                                    handler, user_data)) {
+        va_start(ap, member);
+        success = mrp_dbus_install_filterv(dbus,
+                                           sender, path, interface, member, ap);
+        va_end(ap);
+
+        if (success)
+            return TRUE;
+        else
+            mrp_dbus_del_signal_handler(dbus, sender, path, interface, member,
+                                        handler, user_data);
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus,
+                                mrp_dbus_handler_t handler, void *user_data,
+                                const char *sender, const char *path,
+                                const char *interface, const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    status = mrp_dbus_del_signal_handler(dbus, sender, path, interface, member,
+                                         handler, user_data);
+    va_start(ap, member);
+    status &= mrp_dbus_remove_filterv(dbus,
+                                      sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+int mrp_dbus_install_filterv(mrp_dbus_t *dbus, const char *sender,
+                             const char *path, const char *interface,
+                             const char *member, va_list args)
+{
+#define ADD_TAG(tag, value, ...) do {                                     \
+        if (value != NULL) {                                              \
+            l = snprintf(p, n, "%s%s='%s'", p == filter ? "" : ",",       \
+                         tag, value);                                     \
+            if (l >= n)                                                   \
+                do { __VA_ARGS__; } while (0);                            \
+            n -= l;                                                       \
+            p += l;                                                       \
+        }                                                                 \
+    } while (0)
+
+    va_list   ap;
+    DBusError error;
+    char      filter[1024], *p, argn[16], *val;
+    int       n, l, i;
+
+    p = filter;
+    n = sizeof(filter);
+
+    ADD_TAG("type"     , "signal"  , return FALSE);
+    ADD_TAG("sender"   ,  sender   , return FALSE);
+    ADD_TAG("path"     ,  path     , return FALSE);
+    ADD_TAG("interface",  interface, return FALSE);
+    ADD_TAG("member"   ,  member   , return FALSE);
+
+    va_copy(ap, args);
+    i = 0;
+    while ((val = va_arg(ap, char *)) != NULL) {
+        snprintf(argn, sizeof(argn), "arg%d", i);
+        ADD_TAG(argn, val, { va_end(ap); return FALSE; });
+        i++;
+    }
+    va_end(ap);
+
+    dbus_error_init(&error);
+    dbus_bus_add_match(dbus->conn, filter, &error);
+
+    if (dbus_error_is_set(&error)) {
+        mrp_log_error("Failed to install filter '%s' (error: %s).", filter,
+                      mrp_dbus_errmsg(&error));
+        dbus_error_free(&error);
+
+        return FALSE;
+    }
+    else
+        return TRUE;
+
+}
+
+
+int mrp_dbus_install_filter(mrp_dbus_t *dbus, const char *sender,
+                            const char *path, const char *interface,
+                            const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    va_start(ap, member);
+    status = mrp_dbus_install_filterv(dbus,
+                                      sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+int mrp_dbus_remove_filterv(mrp_dbus_t *dbus, const char *sender,
+                            const char *path, const char *interface,
+                            const char *member, va_list args)
+{
+    va_list ap;
+    char    filter[1024], *p, argn[16], *val;
+    int     n, l, i;
+
+    p = filter;
+    n = sizeof(filter);
+
+    ADD_TAG("type"     , "signal"  , return FALSE);
+    ADD_TAG("sender"   ,  sender   , return FALSE);
+    ADD_TAG("path"     ,  path     , return FALSE);
+    ADD_TAG("interface",  interface, return FALSE);
+    ADD_TAG("member"   ,  member   , return FALSE);
+
+    va_copy(ap, args);
+    i = 0;
+    while ((val = va_arg(ap, char *)) != NULL) {
+        snprintf(argn, sizeof(argn), "arg%d", i);
+        ADD_TAG(argn, val, { va_end(ap); return FALSE; });
+        i++;
+    }
+    va_end(ap);
+
+    dbus_bus_remove_match(dbus->conn, filter, NULL);
+    return TRUE;
+#undef ADD_TAG
+}
+
+
+int mrp_dbus_remove_filter(mrp_dbus_t *dbus, const char *sender,
+                           const char *path, const char *interface,
+                           const char *member, ...)
+{
+    va_list ap;
+    int     status;
+
+    va_start(ap, member);
+    status = mrp_dbus_remove_filterv(dbus, sender, path, interface, member, ap);
+    va_end(ap);
+
+    return status;
+}
+
+
+
+static DBusHandlerResult dispatch_method(DBusConnection *c,
+                                         DBusMessage *msg, void *data)
+{
+#define SAFESTR(str) (str ? str : "<none>")
+    const char *path      = dbus_message_get_path(msg);
+    const char *interface = dbus_message_get_interface(msg);
+    const char *member    = dbus_message_get_member(msg);
+
+    mrp_dbus_t     *dbus = (mrp_dbus_t *)data;
+    handler_list_t *l;
+    handler_t      *h;
+
+    MRP_UNUSED(c);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL || !member)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    mrp_debug("path='%s', interface='%s', member='%s')...",
+              SAFESTR(path), SAFESTR(interface), SAFESTR(member));
+
+    if ((l = mrp_htbl_lookup(dbus->methods, (void *)member)) != NULL) {
+    retry:
+        if ((h = handler_list_find(l, path, interface, member)) != NULL) {
+            if (h->handler(dbus, msg, h->user_data))
+                return DBUS_HANDLER_RESULT_HANDLED;
+            else
+                return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+        }
+    }
+    else {
+        if ((l = mrp_htbl_lookup(dbus->methods, "")) != NULL)
+            goto retry;
+    }
+
+    mrp_debug("Unhandled method path=%s, %s.%s.", SAFESTR(path),
+              SAFESTR(interface), SAFESTR(member));
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static DBusHandlerResult dispatch_signal(DBusConnection *c,
+                                         DBusMessage *msg, void *data)
+{
+#define MATCHES(h, field) (!*field || !h->field || !*h->field || \
+                           !strcmp(field, h->field))
+
+    const char *path      = dbus_message_get_path(msg);
+    const char *interface = dbus_message_get_interface(msg);
+    const char *member    = dbus_message_get_member(msg);
+
+    mrp_dbus_t      *dbus = (mrp_dbus_t *)data;
+    mrp_list_hook_t *p, *n;
+    handler_list_t  *l;
+    handler_t       *h;
+    int              retried = FALSE;
+    int              handled = FALSE;
+
+    MRP_UNUSED(c);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_SIGNAL || !member)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    mrp_debug("%s(path='%s', interface='%s', member='%s')...",
+              __FUNCTION__,
+              SAFESTR(path), SAFESTR(interface), SAFESTR(member));
+
+    if ((l = mrp_htbl_lookup(dbus->signals, (void *)member)) != NULL) {
+    retry:
+        mrp_list_foreach(&l->handlers, p, n) {
+            h = mrp_list_entry(p, handler_t, hook);
+
+            if (MATCHES(h,path) && MATCHES(h,interface) && MATCHES(h,member)) {
+                h->handler(dbus, msg, h->user_data);
+                handled = TRUE;
+            }
+        }
+    }
+
+    if (!retried) {
+        if ((l = mrp_htbl_lookup(dbus->signals, "")) != NULL) {
+            retried = TRUE;
+            goto retry;
+        }
+    }
+
+    if (!handled)
+        mrp_debug("Unhandled signal path=%s, %s.%s.", SAFESTR(path),
+                  SAFESTR(interface), SAFESTR(member));
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+#undef MATCHES
+#undef SAFESTR
+}
+
+
+static void call_reply_cb(DBusPendingCall *pend, void *user_data)
+{
+    call_t      *call = (call_t *)user_data;
+    DBusMessage *reply;
+
+    reply = dbus_pending_call_steal_reply(pend);
+
+    call->pend = NULL;
+    mrp_list_delete(&call->hook);
+
+    call->cb(call->dbus, reply, call->user_data);
+
+    dbus_message_unref(reply);
+    dbus_pending_call_unref(pend);
+
+    call_free(call);
+}
+
+
+int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data, int type, ...)
+{
+    va_list          ap;
+    int32_t          id;
+    call_t          *call;
+    DBusMessage     *msg;
+    DBusPendingCall *pend;
+    int              success;
+
+    call = NULL;
+    pend = NULL;
+
+    msg = dbus_message_new_method_call(dest, path, interface, member);
+
+    if (msg == NULL)
+        return 0;
+
+    if (cb != NULL) {
+        if ((call = mrp_allocz(sizeof(*call))) != NULL) {
+            mrp_list_init(&call->hook);
+
+            call->dbus      = dbus;
+            call->id        = dbus->call_id++;
+            call->cb        = cb;
+            call->user_data = user_data;
+
+            id = call->id;
+        }
+        else
+            goto fail;
+    }
+    else
+        id = dbus->call_id++;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = dbus_message_append_args_valist(msg, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (cb == NULL) {
+        dbus_message_set_no_reply(msg, TRUE);
+        if (!dbus_connection_send(dbus->conn, msg, NULL))
+            goto fail;
+    }
+    else {
+        if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout))
+            goto fail;
+
+        if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL))
+            goto fail;
+    }
+
+    if (cb != NULL) {
+        mrp_list_append(&dbus->calls, &call->hook);
+        call->pend = pend;
+    }
+
+    dbus_message_unref(msg);
+
+    return id;
+
+ fail:
+    if (pend != NULL)
+        dbus_pending_call_unref(pend);
+
+    if(msg != NULL)
+        dbus_message_unref(msg);
+
+    call_free(call);
+
+    return 0;
+}
+
+
+int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data, DBusMessage *msg)
+{
+    int32_t          id;
+    call_t          *call;
+    DBusPendingCall *pend;
+    int              method;
+
+    call = NULL;
+    pend = NULL;
+
+    if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL) {
+        if (cb != NULL)
+            goto fail;
+        else
+            method = FALSE;
+    }
+    else
+        method = TRUE;
+
+    if (cb != NULL) {
+        if ((call = mrp_allocz(sizeof(*call))) != NULL) {
+            mrp_list_init(&call->hook);
+
+            call->dbus      = dbus;
+            call->id        = dbus->call_id++;
+            call->cb        = cb;
+            call->user_data = user_data;
+
+            id = call->id;
+        }
+        else
+            goto fail;
+    }
+    else
+        id = dbus->call_id++;
+
+    if (!dbus_message_set_destination(msg, dest))
+        goto fail;
+    if (!dbus_message_set_path(msg, path))
+        goto fail;
+    if (!dbus_message_set_interface(msg, interface))
+        goto fail;
+    if (!dbus_message_set_member(msg, member))
+        goto fail;
+
+    if (cb == NULL) {
+        if (method)
+            dbus_message_set_no_reply(msg, TRUE);
+        if (!dbus_connection_send(dbus->conn, msg, NULL))
+            goto fail;
+    }
+    else {
+        if (!dbus_connection_send_with_reply(dbus->conn, msg, &pend, timeout))
+            goto fail;
+
+        if (!dbus_pending_call_set_notify(pend, call_reply_cb, call, NULL))
+            goto fail;
+    }
+
+    if (cb != NULL) {
+        mrp_list_append(&dbus->calls, &call->hook);
+        call->pend = pend;
+    }
+
+    return id;
+
+ fail:
+    if (pend != NULL)
+        dbus_pending_call_unref(pend);
+
+    if(msg != NULL)
+        dbus_message_unref(msg);
+
+    call_free(call);
+
+    return 0;
+}
+
+
+int mrp_dbus_send_msg(mrp_dbus_t *dbus, DBusMessage *msg)
+{
+    return dbus_connection_send(dbus->conn, msg, NULL);
+}
+
+
+int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id)
+{
+    mrp_list_hook_t *p, *n;
+    call_t          *call;
+
+    mrp_list_foreach(&dbus->calls, p, n) {
+        call = mrp_list_entry(p, call_t, hook);
+
+        if (call->id == id) {
+            mrp_list_delete(p);
+
+            dbus_pending_call_cancel(call->pend);
+            dbus_pending_call_unref(call->pend);
+            call->pend = NULL;
+
+            call_free(call);
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_dbus_reply(mrp_dbus_t *dbus, DBusMessage *msg, int type, ...)
+{
+    va_list      ap;
+    DBusMessage *rpl;
+    int          success;
+
+    rpl = dbus_message_new_method_return(msg);
+
+    if (rpl == NULL)
+        return FALSE;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = dbus_message_append_args_valist(rpl, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (!dbus_connection_send(dbus->conn, rpl, NULL))
+        goto fail;
+
+    dbus_message_unref(rpl);
+
+    return TRUE;
+
+ fail:
+    if(rpl != NULL)
+        dbus_message_unref(rpl);
+
+    return FALSE;
+}
+
+
+int mrp_dbus_reply_error(mrp_dbus_t *dbus, DBusMessage *msg,
+                         const char *errname, const char *errmsg, int type, ...)
+{
+    va_list      ap;
+    DBusMessage *rpl;
+    int          success;
+
+    rpl = dbus_message_new_error(msg, errname, errmsg);
+
+    if (rpl == NULL)
+        return FALSE;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = dbus_message_append_args_valist(rpl, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (!dbus_connection_send(dbus->conn, rpl, NULL))
+        goto fail;
+
+    dbus_message_unref(rpl);
+
+    return TRUE;
+
+ fail:
+    if(rpl != NULL)
+        dbus_message_unref(rpl);
+
+    return FALSE;
+}
+
+
+static void call_free(call_t *call)
+{
+    if (call != NULL)
+        mrp_free(call);
+}
+
+
+static void purge_calls(mrp_dbus_t *dbus)
+{
+    mrp_list_hook_t *p, *n;
+    call_t          *call;
+
+    mrp_list_foreach(&dbus->calls, p, n) {
+        call = mrp_list_entry(p, call_t, hook);
+
+        mrp_list_delete(&call->hook);
+
+        if (call->pend != NULL)
+            dbus_pending_call_unref(call->pend);
+
+        mrp_free(call);
+    }
+}
+
+
+int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path,
+                    const char *interface, const char *member, int type, ...)
+{
+    va_list      ap;
+    DBusMessage *msg;
+    int          success;
+
+    msg = dbus_message_new_signal(path, interface, member);
+
+    if (msg == NULL)
+        return 0;
+
+    if (type == DBUS_TYPE_INVALID)
+        success = TRUE;
+    else {
+        va_start(ap, type);
+        success = dbus_message_append_args_valist(msg, type, ap);
+        va_end(ap);
+    }
+
+    if (!success)
+        goto fail;
+
+    if (dest && *dest && !dbus_message_set_destination(msg, dest))
+        goto fail;
+
+    if (!dbus_connection_send(dbus->conn, msg, NULL))
+        goto fail;
+
+    dbus_message_unref(msg);
+
+    return TRUE;
+
+ fail:
+    /*
+     * XXX TODO: Hmm... IIRC, libdbus unrefs messages upon failure. If it
+     *           was really so, this would corrupt/crash. Check this from
+     *           libdbus code.
+     */
+    if(msg != NULL)
+        dbus_message_unref(msg);
+
+    return 0;
+}
diff --git a/src/common/libdbus.h b/src/common/libdbus.h
new file mode 100644 (file)
index 0000000..84f29b7
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DBUS_H__
+#define __MURPHY_DBUS_H__
+
+#include <murphy/common/mainloop.h>
+#include <dbus/dbus.h>
+
+#define MRP_AF_DBUS 0xDB
+
+/** Our D-BUS (connection) abstraction. */
+struct mrp_dbus_s;
+typedef struct mrp_dbus_s mrp_dbus_t;
+
+/** D-BUS method or signal callback type. */
+typedef int (*mrp_dbus_handler_t)(mrp_dbus_t *, DBusMessage *, void *);
+
+/** Create a new connection to the given bus. */
+mrp_dbus_t *mrp_dbus_connect(mrp_mainloop_t *ml, const char *address,
+                             DBusError *errp);
+#define mrp_dbus_get mrp_dbus_connect
+
+
+/** Set up a DBusConnection with a mainloop. */
+int mrp_dbus_setup_connection(mrp_mainloop_t *ml, DBusConnection *conn);
+
+/** Increase the reference count of the given DBus (connection). */
+mrp_dbus_t *mrp_dbus_ref(mrp_dbus_t *dbus);
+
+/** Decrease the reference count of the given DBus (connection). */
+int mrp_dbus_unref(mrp_dbus_t *dbus);
+
+/** Acquire the given name on the given bus (connection). */
+int mrp_dbus_acquire_name(mrp_dbus_t *dbus, const char *name, DBusError *error);
+
+/** Release the given name on the given bus (connection). */
+int mrp_dbus_release_name(mrp_dbus_t *dbus, const char *name, DBusError *error);
+
+typedef void (*mrp_dbus_name_cb_t)(mrp_dbus_t *, const char *, int,
+                                   const char *, void *);
+int mrp_dbus_follow_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data);
+int mrp_dbus_forget_name(mrp_dbus_t *dbus, const char *name,
+                         mrp_dbus_name_cb_t cb, void *user_data);
+
+int mrp_dbus_export_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data);
+
+int mrp_dbus_remove_method(mrp_dbus_t *dbus, const char *path,
+                           const char *interface, const char *member,
+                           mrp_dbus_handler_t handler, void *user_data);
+
+MRP_NULLTERM int mrp_dbus_subscribe_signal(mrp_dbus_t *dbus,
+                                           mrp_dbus_handler_t handler,
+                                           void *user_data,
+                                           const char *sender,
+                                           const char *path,
+                                           const char *interface,
+                                           const char *member, ...);
+
+MRP_NULLTERM int mrp_dbus_unsubscribe_signal(mrp_dbus_t *dbus,
+                                             mrp_dbus_handler_t handler,
+                                             void *user_data,
+                                             const char *sender,
+                                             const char *path,
+                                             const char *interface,
+                                             const char *member, ...);
+
+MRP_NULLTERM int mrp_dbus_install_filter(mrp_dbus_t *dbus,
+                                         const char *sender,
+                                         const char *path,
+                                         const char *interface,
+                                         const char *member, ...);
+int mrp_dbus_install_filterv(mrp_dbus_t *dbus,
+                             const char *sender,
+                             const char *path,
+                             const char *interface,
+                             const char *member,
+                             va_list ap);
+
+MRP_NULLTERM int mrp_dbus_remove_filter(mrp_dbus_t *dbus,
+                                        const char *sender,
+                                        const char *path,
+                                        const char *interface,
+                                        const char *member, ...);
+
+int mrp_dbus_remove_filterv(mrp_dbus_t *dbus,
+                            const char *sender,
+                            const char *path,
+                            const char *interface,
+                            const char *member,
+                            va_list ap);
+
+int mrp_dbus_add_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data);
+int mrp_dbus_del_signal_handler(mrp_dbus_t *dbus, const char *sender,
+                                const char *path, const char *interface,
+                                const char *member, mrp_dbus_handler_t handler,
+                                void *user_data);
+
+typedef void (*mrp_dbus_reply_cb_t)(mrp_dbus_t *dbus, DBusMessage *reply,
+                                    void *user_data);
+
+int32_t mrp_dbus_call(mrp_dbus_t *dbus, const char *dest,
+                      const char *path, const char *interface,
+                      const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data,
+                      int dbus_type, ...);
+int mrp_dbus_call_cancel(mrp_dbus_t *dbus, int32_t id);
+
+int mrp_dbus_reply(mrp_dbus_t *dbus, DBusMessage *msg, int type, ...);
+
+int mrp_dbus_reply_error(mrp_dbus_t *dbus, DBusMessage *msg,
+                         const char *errname, const char *errmsg,
+                         int type, ...);
+
+int mrp_dbus_signal(mrp_dbus_t *dbus, const char *dest, const char *path,
+                    const char *interface, const char *member, int type, ...);
+
+int32_t mrp_dbus_send(mrp_dbus_t *dbus, const char *dest, const char *path,
+                      const char *interface, const char *member, int timeout,
+                      mrp_dbus_reply_cb_t cb, void *user_data,
+                      DBusMessage *msg);
+
+int mrp_dbus_send_msg(mrp_dbus_t *dbus, DBusMessage *msg);
+
+const char *mrp_dbus_get_unique_name(mrp_dbus_t *dbus);
+
+static inline void mrp_dbus_error_init(DBusError *error)
+{
+    /*
+     * Prevent libdbus error messages for NULL DBusError's...
+     */
+    if (error != NULL)
+        dbus_error_init(error);
+}
+
+
+static inline const char *mrp_dbus_errmsg(DBusError *err)
+{
+    if (err && dbus_error_is_set(err))
+        return err->message;
+    else
+        return "unknown DBUS error";
+}
+
+
+#endif /* __MURPHY_DBUS_H__ */
diff --git a/src/common/list.h b/src/common/list.h
new file mode 100644 (file)
index 0000000..16c0ae4
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LIST_H__
+#define __MURPHY_LIST_H__
+
+#include <murphy/common/macros.h>
+
+
+MRP_CDECL_BEGIN
+
+
+/** \file
+ * A simple doubly-linked circular list implementation, obviously inspired
+ * by the linux kernel.
+ */
+
+
+/** A list hook. Used both a list head and to hook up objects to the list. */
+typedef struct mrp_list_hook_s mrp_list_hook_t;
+struct mrp_list_hook_s {
+    mrp_list_hook_t *prev;
+    mrp_list_hook_t *next;
+};
+
+/** Macro to initialize a list to be empty. */
+#define MRP_LIST_INIT(list) { .prev = &(list), .next = &(list) }
+
+/** Macro to define a list and initialize it to be empty. */
+#define MRP_LIST_HOOK(list) mrp_list_hook_t list = MRP_LIST_INIT(list)
+
+/** Initialize a list to be empty. */
+static inline void mrp_list_init(mrp_list_hook_t *list)
+{
+    list->prev = list->next = list;
+}
+
+/** Check if a list is empty. */
+static inline int mrp_list_empty(mrp_list_hook_t *list)
+{
+    if (list->next == list->prev) {
+        if (list->next == list)
+            return TRUE;
+
+#ifdef __MURPHY_LIST_ALLOW_NULL
+        if (!list->next)
+            return TRUE;
+#endif
+    }
+
+    return FALSE;
+}
+
+/** Append a new item to a list (add it after the last item). */
+static inline void mrp_list_append(mrp_list_hook_t *list, mrp_list_hook_t *item)
+{
+    if (mrp_list_empty(list)) {
+        list->next = list->prev = item;
+        item->next = item->prev = list;
+    }
+    else {
+        mrp_list_hook_t *prev = list->prev;
+
+        prev->next = item;
+        item->prev = prev;
+        item->next = list;
+        list->prev = item;
+    }
+}
+
+/** Prepend a new item to a list (add it before the first item). */
+static inline void mrp_list_prepend(mrp_list_hook_t *list,
+                                    mrp_list_hook_t *item)
+{
+    if (mrp_list_empty(list)) {
+        list->next = list->prev = item;
+        item->next = item->prev = list;
+    }
+    else {
+        mrp_list_hook_t *next = list->next;
+
+        list->next = item;
+        item->prev = list;
+        item->next = next;
+        next->prev = item;
+    }
+}
+
+/** Insert a new item to the list before a given item. */
+static inline void mrp_list_insert_before(mrp_list_hook_t *next,
+                                          mrp_list_hook_t *item)
+{
+    mrp_list_append(next, item);
+}
+
+/** Insert a new item to the list after a given item. */
+static inline void mrp_list_insert_after(mrp_list_hook_t *prev,
+                                         mrp_list_hook_t *item)
+{
+    mrp_list_prepend(prev, item);
+}
+
+/** Delete the given item from the list. */
+static inline void mrp_list_delete(mrp_list_hook_t *item)
+{
+    mrp_list_hook_t *prev, *next;
+
+    if (!mrp_list_empty(item)) {
+        prev = item->prev;
+        next = item->next;
+
+        prev->next = next;
+        next->prev = prev;
+
+        item->prev = item->next = item;
+    }
+}
+
+/** Reattach a list to a new hook. Initialize old hook to be empty. */
+static inline void mrp_list_move(mrp_list_hook_t *new_hook,
+                                 mrp_list_hook_t *old_hook)
+{
+    *new_hook = *old_hook;
+
+    new_hook->next->prev = new_hook;
+    new_hook->prev->next = new_hook;
+
+    mrp_list_init(old_hook);
+}
+
+
+/** Update a list when the address of a hook has changed (eg. by realloc). */
+static inline void mrp_list_update_address(mrp_list_hook_t *new_addr,
+                                           mrp_list_hook_t *old_addr)
+{
+    mrp_list_hook_t *prev, *next;
+    ptrdiff_t        diff;
+
+    diff = new_addr - old_addr;
+    prev = new_addr->prev;
+    next = new_addr->next;
+
+    prev->next += diff;
+    next->prev += diff;
+}
+
+
+/** Macro to iterate through a list (current item safe to remove). */
+#define mrp_list_foreach(list, p, n)                                      \
+    if ((list)->next != NULL)                                             \
+        for (p = (list)->next, n = p->next; p != (list); p = n, n = n->next)
+
+/** Macro to iterate through a list backwards (current item safe to remove). */
+#define mrp_list_foreach_back(list, p, n)                                 \
+    if ((list)->prev != NULL)                                             \
+        for (p = (list)->prev, n = p->prev; p != (list); p = n, n = n->prev)
+
+/** Macro to get a pointer to a embedding structure from a list pointer. */
+#ifndef __cplusplus
+#    define PTR_ARITH_TYPE void
+#else
+#    define PTR_ARITH_TYPE char
+#endif
+
+#define mrp_list_entry(ptr, type, member)                                 \
+    (type *)(((PTR_ARITH_TYPE *)(ptr)) - MRP_OFFSET(type, member))
+
+
+MRP_CDECL_END
+
+
+#endif /* __MURPHY_LIST_H__ */
+
diff --git a/src/common/log.c b/src/common/log.c
new file mode 100644 (file)
index 0000000..e35c43a
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+
+typedef struct {
+    mrp_list_hook_t  hook;
+    char            *name;
+    mrp_logger_t     logger;
+    void            *data;
+    int              builtin;
+} log_target_t;
+
+static log_target_t stderr_target;
+static log_target_t stdout_target;
+static log_target_t syslog_target;
+static log_target_t file_target;
+
+static MRP_LIST_HOOK(log_targets);
+static int           log_mask   = MRP_LOG_MASK_ERROR;
+static log_target_t *log_target = NULL;
+
+
+mrp_log_mask_t mrp_log_parse_levels(const char *levels)
+{
+    const char *p;
+    mrp_log_mask_t mask;
+
+    mask = 0;
+
+    if (levels == NULL) {
+        if (mask == 0)
+            mask = 1;
+        else {
+            mask <<= 1;
+            mask  |= 1;
+        }
+    }
+    else {
+        p = levels;
+        while (p && *p) {
+#           define MATCHES(s, l) (!strcmp(s, l) ||                      \
+                                  !strncmp(s, l",", sizeof(l",") - 1))
+
+            if (MATCHES(p, "info"))
+                mask |= MRP_LOG_MASK_INFO;
+            else if (MATCHES(p, "error"))
+                mask |= MRP_LOG_MASK_ERROR;
+            else if (MATCHES(p, "warning"))
+                mask |= MRP_LOG_MASK_WARNING;
+            else if (MATCHES(p, "none") || MATCHES(p, "off"))
+                mask = 0;
+            else
+                return -1;
+
+            if ((p = strchr(p, ',')) != NULL)
+                p += 1;
+
+#           undef MATCHES
+        }
+    }
+
+    return mask;
+}
+
+
+const char *mrp_log_parse_target(const char *target)
+{
+    return target;
+}
+
+
+const char *mrp_log_dump_mask(mrp_log_mask_t mask, char *buf, size_t size)
+{
+    char *p, *t;
+    int   n, l;
+
+    if (!mask)
+        return "none";
+
+    p = buf;
+    l = size;
+
+    t  = "";
+    *p = '\0';
+
+    if (mask & MRP_LOG_MASK_INFO) {
+        n  = snprintf(p, l, "info");
+        p += n;
+        l -= n;
+        t = ",";
+    }
+    if (mask & MRP_LOG_MASK_WARNING) {
+        n  = snprintf(p, l, "%swarning", t);
+        p += n;
+        l -= n;
+        t = ",";
+    }
+    if (mask & MRP_LOG_MASK_ERROR) {
+        n  = snprintf(p, l, "%serror", t);
+        p += n;
+        l -= n;
+        t = ",";
+    }
+
+    return buf;
+}
+
+
+mrp_log_mask_t mrp_log_enable(mrp_log_mask_t enabled)
+{
+    mrp_log_mask_t old_mask = log_mask;
+
+    log_mask |= enabled;
+
+    return old_mask;
+}
+
+
+mrp_log_mask_t mrp_log_disable(mrp_log_mask_t disabled)
+{
+    mrp_log_mask_t old_mask = log_mask;
+
+    log_mask &= ~disabled;
+
+    return old_mask;
+}
+
+
+mrp_log_mask_t mrp_log_set_mask(mrp_log_mask_t enabled)
+{
+    mrp_log_mask_t old_mask = log_mask;
+
+    log_mask = enabled;
+
+    return old_mask;
+}
+
+
+static log_target_t *find_target(const char *name)
+{
+    log_target_t    *t;
+    mrp_list_hook_t *p, *n;
+
+    mrp_list_foreach(&log_targets, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        if (t->name == name || !strcmp(t->name, name))
+            return t;
+    }
+
+    return NULL;
+}
+
+
+int mrp_log_set_target(const char *name)
+{
+    log_target_t *target;
+    const char   *path;
+
+    if (!strncmp(name, "file:", 5)) {
+        path = name + 5;
+        name = "file";
+    }
+    else
+        path = NULL;
+
+    target = find_target(name);
+
+    if (target == NULL || (target == &file_target && path == NULL))
+        return FALSE;
+
+    /* close files opened by us, if any */
+    if (log_target == &file_target) {
+        if (file_target.data != NULL) {
+            fclose(file_target.data);
+            file_target.data = NULL;
+        }
+    }
+
+    log_target = target;
+
+    /* open any new files if we have to */
+    if (target == &file_target) {
+        target->data = fopen(path, "a");
+
+        if (target->data == NULL) {
+            log_target = &syslog_target;
+
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+const char *mrp_log_get_target(void)
+{
+    return log_target->name;
+}
+
+
+int mrp_log_get_targets(const char **targets, size_t size)
+{
+    mrp_list_hook_t *p, *n;
+    log_target_t    *t;
+    int cnt;
+
+    cnt = 0;
+    mrp_list_foreach(&log_targets, p, n) {
+        if (cnt == (int)size)
+            break;
+
+        t = mrp_list_entry(p, typeof(*t), hook);
+        targets[cnt++] = t->name;
+    }
+
+    return cnt;
+}
+
+
+int mrp_log_register_target(const char *name, mrp_logger_t logger, void *data)
+{
+    log_target_t *target;
+
+    if (find_target(name) != NULL)
+        return FALSE;
+
+    target = mrp_allocz(sizeof(*target));
+
+    mrp_list_init(&target->hook);
+    target->name   = mrp_strdup(name);
+    target->logger = logger;
+    target->data   = data;
+
+    if (target->name != NULL) {
+        mrp_list_append(&log_targets, &target->hook);
+
+        return TRUE;
+    }
+    else {
+        mrp_free(target);
+
+        return FALSE;
+    }
+}
+
+
+int mrp_log_unregister_target(const char *name)
+{
+    log_target_t *target;
+
+    target = find_target(name);
+
+    if (target == NULL || target->builtin)
+        return FALSE;
+
+    if (log_target == target)
+        log_target = &stderr_target;
+
+    mrp_list_delete(&target->hook);
+    mrp_free(target->name);
+    mrp_free(target);
+
+    return TRUE;
+}
+
+
+static void log_msgv(void *data, mrp_log_level_t level, const char *file,
+                     int line, const char *func, const char *format,
+                     va_list ap)
+{
+    FILE       *fp = data;
+    int         lvl;
+    const char *prefix;
+    char        prfx[2*1024];
+
+    if (!(log_mask & (1 << level)))
+        return;
+
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+
+    switch (level) {
+    case MRP_LOG_ERROR:   lvl = LOG_ERR;     prefix = "E: "; break;
+    case MRP_LOG_WARNING: lvl = LOG_WARNING; prefix = "W: "; break;
+    case MRP_LOG_INFO:    lvl = LOG_INFO;    prefix = "I: "; break;
+    case MRP_LOG_DEBUG:   lvl = LOG_INFO;
+        snprintf(prfx, sizeof(prfx) - 1, "D: [%s] ", func);
+        prfx[sizeof(prfx)-1] = '\0';
+        prefix = prfx;
+        break;
+    default:
+        return;
+    }
+
+    if (fp == NULL)
+        vsyslog(lvl, format, ap);
+    else {
+        fputs(prefix, fp);
+        vfprintf(fp, format, ap); fputs("\n", fp);
+        fflush(fp);
+    }
+}
+
+
+void mrp_log_msgv(mrp_log_level_t level, const char *file,
+                  int line, const char *func, const char *format,
+                  va_list ap)
+{
+    static int    busy   = 0;
+    mrp_logger_t  logger = log_target->logger;
+    void         *data   = log_target->data;
+
+    if (MRP_UNLIKELY(busy != 0))
+        return;
+
+    if (!(log_mask & (1 << level)))
+        return;
+
+    busy++;
+    logger(data, level, file, line, func, format, ap);
+    busy--;
+}
+
+
+void mrp_log_msg(mrp_log_level_t level, const char *file,
+                 int line, const char *func, const char *format, ...)
+{
+    va_list ap;
+
+    if (!(log_mask & (1 << level)))
+        return;
+
+    va_start(ap, format);
+    mrp_log_msgv(level, file, line, func, format, ap);
+    va_end(ap);
+}
+
+
+/*
+ * workaround for not being able to initialize log_fp to stderr
+ */
+
+static __attribute__((constructor)) void set_default_logging(void)
+{
+    mrp_list_init(&stderr_target.hook);
+    stderr_target.name    = "stderr";
+    stderr_target.logger  = log_msgv;
+    stderr_target.data    = stderr;
+    stderr_target.builtin = TRUE;
+
+    mrp_list_init(&stdout_target.hook);
+    stdout_target.name    = "stdout";
+    stdout_target.logger  = log_msgv;
+    stdout_target.data    = stdout;
+    stdout_target.builtin = TRUE;
+
+    mrp_list_init(&syslog_target.hook);
+    syslog_target.name    = "syslog";
+    syslog_target.logger  = log_msgv;
+    syslog_target.data    = NULL;
+    syslog_target.builtin = TRUE;
+
+    mrp_list_init(&file_target.hook);
+    file_target.name    = "file";
+    file_target.logger  = log_msgv;
+    file_target.data    = NULL;
+    file_target.builtin = TRUE;
+
+    mrp_list_prepend(&log_targets, &file_target.hook);
+    mrp_list_prepend(&log_targets, &syslog_target.hook);
+    mrp_list_prepend(&log_targets, &stderr_target.hook);
+    mrp_list_prepend(&log_targets, &stdout_target.hook);
+
+    log_target = &stderr_target;
+}
diff --git a/src/common/log.h b/src/common/log.h
new file mode 100644 (file)
index 0000000..61e69c5
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LOG_H__
+#define __MURPHY_LOG_H__
+
+/** \file
+ * Logging functions and macros.
+ */
+
+#include <stdarg.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_LOG_NAME_ERROR   "error"             /**< name for error level */
+#define MRP_LOG_NAME_WARNING "warning"           /**< name for warning level */
+#define MRP_LOG_NAME_INFO    "info"              /**< name for info level */
+#define MRP_LOG_NAME_DEBUG   "debug"             /**< name for debug level */
+
+
+/**
+ * Logging levels.
+ */
+typedef enum {
+    MRP_LOG_ERROR = 0,                           /**< error log level */
+    MRP_LOG_WARNING,                             /**< warning log level */
+    MRP_LOG_INFO,                                /**< info log level  */
+    MRP_LOG_DEBUG,                               /**< debug log level */
+} mrp_log_level_t;
+
+
+/**
+ * Logging masks.
+ */
+typedef enum {
+    MRP_LOG_MASK_ERROR   = 0x01,                 /**< error logging mask */
+    MRP_LOG_MASK_WARNING = 0x02,                 /**< warning logging mask */
+    MRP_LOG_MASK_INFO    = 0x04,                 /**< info logging mask */
+    MRP_LOG_MASK_DEBUG   = 0x08,                 /**< debug logging mask */
+} mrp_log_mask_t;
+
+#define MRP_LOG_MASK(level) (1 << ((level)-1))   /**< mask of level */
+#define MRP_LOG_UPTO(level) ((1 << (level+1))-1) /**< mask up to level */
+
+
+/** Parse a string of comma-separated log level names to a log mask. */
+mrp_log_mask_t mrp_log_parse_levels(const char *levels);
+
+/** Write the given log mask as a string to the given buffer. */
+const char *mrp_log_dump_mask(mrp_log_mask_t mask, char *buf, size_t size);
+
+/** Clear current logging level and enable levels in mask. */
+mrp_log_mask_t mrp_log_set_mask(mrp_log_mask_t mask);
+
+/** Enable logging for levels in mask. */
+mrp_log_mask_t mrp_log_enable(mrp_log_mask_t mask);
+
+/** Disable logging for levels in mask. */
+mrp_log_mask_t mrp_log_disable(mrp_log_mask_t mask);
+
+/** Get the current logging level mask. */
+#define mrp_log_get_mask() mrp_log_disable(0)
+
+/**
+ * Logging target names.
+ */
+#define MRP_LOG_NAME_STDOUT  "stdout"
+#define MRP_LOG_NAME_STDERR  "stderr"
+#define MRP_LOG_NAME_SYSLOG  "syslog"
+
+/**
+ * Logging targets.
+ */
+#define MRP_LOG_TO_STDOUT     "stdout"
+#define MRP_LOG_TO_STDERR     "stderr"
+#define MRP_LOG_TO_SYSLOG     "syslog"
+#define MRP_LOG_TO_FILE(path) ((const char *)(path))
+
+
+/** Parse a log target name to MRP_LOG_TO_*. */
+const char *mrp_log_parse_target(const char *target);
+
+/** Set logging target. */
+int mrp_log_set_target(const char *target);
+
+/** Get the current log target. */
+const char *mrp_log_get_target(void);
+
+/** Get all available logging targets. */
+int mrp_log_get_targets(const char **targets, size_t size);
+
+/** Log an error. */
+#define mrp_log_error(fmt, args...) \
+    mrp_log_msg(MRP_LOG_ERROR, __LOC__, fmt , ## args)
+
+/** Log a warning. */
+#define mrp_log_warning(fmt, args...) \
+    mrp_log_msg(MRP_LOG_WARNING, __LOC__, fmt , ## args)
+
+/** Log an informational message. */
+#define mrp_log_info(fmt, args...) \
+    mrp_log_msg(MRP_LOG_INFO, __LOC__, fmt , ## args)
+
+/** Generic logging function. */
+void mrp_log_msg(mrp_log_level_t level,
+                 const char *file, int line, const char *func,
+                 const char *format, ...) MRP_PRINTF_LIKE(5, 6);
+
+/** Generic logging function for easy wrapping. */
+void mrp_log_msgv(mrp_log_level_t level, const char *file,
+                  int line, const char *func, const char *format, va_list ap);
+
+/** Type for custom logging functions. */
+typedef void (*mrp_logger_t)(void *user_data,
+                             mrp_log_level_t level, const char *file,
+                             int line, const char *func, const char *format,
+                             va_list ap);
+
+/** Register a new logging target. */
+int mrp_log_register_target(const char *name, mrp_logger_t logger,
+                            void *user_data);
+
+/** Unregister the given logging target. */
+int mrp_log_unregister_target(const char *name);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_LOG_H__ */
diff --git a/src/common/macros.h b/src/common/macros.h
new file mode 100644 (file)
index 0000000..1fde94a
--- /dev/null
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_MACROS_H__
+#define __MURPHY_MACROS_H__
+
+#include <stddef.h>
+
+#ifndef FALSE
+#    define FALSE 0
+#    define TRUE (!FALSE)
+#endif
+
+#ifdef __cplusplus
+#  define typeof(expr) decltype(expr)
+#endif
+
+/** Align ptr to multiple of align. */
+#define MRP_ALIGN(ptr, align) (((ptr) + ((align)-1)) & ~((align)-1))
+
+/** Get the offset of the given member in a struct/union of the given type. */
+#define MRP_OFFSET(type, member) ((ptrdiff_t)(&(((type *)0)->member)))
+
+/** Determine the dimension of the given array. */
+#define MRP_ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+
+#ifndef __GNUC__
+#  define __FUNCTION__ __func__
+#endif
+
+#ifdef __GNUC__
+     /** MAX that evalutes its arguments only once. */
+#    define MRP_MAX(a, b) ({                                              \
+            typeof(a) _a = (a);                                           \
+            typeof(b) _b = (b);                                           \
+            _a > _b ? _a : _b;                                            \
+        })
+
+     /** MIN that evalutes its arguments only once. */
+#    define MRP_MIN(a, b) ({                                              \
+            typeof(a) _a = (a), _b = (b);                                 \
+            _a < _b ? _a : _b;                                            \
+        })
+
+     /** Likeliness branch-prediction hint for the compiler. */
+#    define MRP_LIKELY(cond)   __builtin_expect((cond), 1)
+
+     /** Unlikeliness branch-prediction hint for the compiler. */
+#    define MRP_UNLIKELY(cond) __builtin_expect((cond), 0)
+
+     /** Prevent symbol from being exported (overriden by linker scripts). */
+#    define MRP_HIDDEN __attribute__ ((visibility ("hidden")))
+
+     /** Request a symbol to be exported (overriden by linker scripts). */
+#    define MRP_EXPORT __attribute__ ((visibility ("default")))
+
+     /** Ask the compiler to check for the presence of a NULL-sentinel. */
+#    define MRP_NULLTERM __attribute__((sentinel))
+
+     /** Ask for printf-like format string checks of calls to this function. */
+#    define MRP_PRINTF_LIKE(format_idx, first_arg_idx)                    \
+         __attribute__ ((format (printf, format_idx, first_arg_idx)))
+
+     /** Mark a function to be called before main is entered. */
+#    define MRP_INIT __attribute__((constructor(65535)))
+#    define MRP_INIT_AT(prio) __attribute__ ((constructor(prio)))
+
+     /** Mark a function to be called after main returns, or exit is called. */
+#    define MRP_EXIT __attribute__ ((destructor(65535)))
+#    define MRP_EXIT_AT(prio) __attribute__ ((destructor(prio)))
+
+/** Mark a variable unused. */
+#    define MRP_UNUSED(var) (void)var
+#else /* ! __GNUC__ */
+#    define MRP_LIKELY(cond)   (cond)
+#    define MRP_UNLIKELY(cond) (cond)
+#    define MRP_HIDDEN
+#    define MRP_EXPORT
+#    define MRP_NULLTERM
+#    define MRP_PRINTF_LIKE(format_idx, first_arg_idx)
+#    define __FUNCTION__ __func__
+#    define MRP_INIT
+#    define MRP_INIT_AT
+#    define MRP_EXIT
+#    define MRP_EXIT_AT
+#    define MRP_UNUSED(var)
+#endif
+
+/** Macro that can be used to pass the location of its usage. */
+#    define __LOC__ __FILE__, __LINE__, __FUNCTION__
+
+/** Assertions. */
+#ifndef NDEBUG
+#    define MRP_ASSERT(expr, fmt, args...) do {                           \
+        if (!(expr)) {                                                    \
+            printf("assertion '%s' failed at %s@%s:%d: "fmt"\n", #expr,   \
+                   __FUNCTION__, __FILE__, __LINE__, ## args);          \
+            abort();                                                      \
+        }                                                                 \
+    } while (0)
+#else
+#    define MRP_ASSERT(expr, msg) do { } while (0)
+#endif
+
+/** Create a version integer from a (major, minor, micro) tuple. */
+#define MRP_VERSION_INT(maj, min, mic)                                    \
+    ((((maj) & 0xff) << 16) | (((min) & 0xff) << 8) | ((mic) & 0xff))
+
+/** Create a version string from a (const) (major, minor, micro) tuple.  */
+#define MRP_VERSION_STRING(maj, min, mic) #maj"."#min"."#mic
+
+/** Extract major version from a version integer. */
+#define MRP_VERSION_MAJOR(ver) (((ver) >> 16) & 0xff)
+
+/** Extract minor version from a version integer. */
+#define MRP_VERSION_MINOR(ver) (((ver) >>  8) & 0xff)
+
+/** Extract micro version from a version integer. */
+#define MRP_VERSION_MICRO(ver) ((ver) & 0xff)
+
+/** Macro to stringify a macro argument. */
+#define MRP_STRINGIFY(arg) #arg
+
+/** C++-compatibility macros. */
+#ifdef __cplusplus
+#    define MRP_CDECL_BEGIN extern "C" {
+#    define MRP_CDECL_END   }
+#else
+#    define MRP_CDECL_BEGIN
+#    define MRP_CDECL_END
+#endif
+
+#endif /* __MURPHY_MACROS_H__ */
+
diff --git a/src/common/mainloop.c b/src/common/mainloop.c
new file mode 100644 (file)
index 0000000..5702c15
--- /dev/null
@@ -0,0 +1,2625 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+#include <signal.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <sys/epoll.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/json.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/mainloop.h>
+
+#define USECS_PER_SEC  (1000 * 1000)
+#define USECS_PER_MSEC (1000)
+#define NSECS_PER_USEC (1000)
+
+/*
+ * I/O watches
+ */
+
+struct mrp_io_watch_s {
+    mrp_list_hook_t    hook;                     /* to list of watches */
+    mrp_list_hook_t    deleted;                  /* to list of pending delete */
+    int              (*free)(void *ptr);         /* cb to free memory */
+    mrp_mainloop_t    *ml;                       /* mainloop */
+    int                fd;                       /* file descriptor to watch */
+    mrp_io_event_t     events;                   /* events of interest */
+    mrp_io_watch_cb_t  cb;                       /* user callback */
+    void              *user_data;                /* opaque user data */
+    struct pollfd     *pollfd;                   /* associated pollfd */
+    mrp_list_hook_t    slave;                    /* watches with the same fd */
+    int                wrhup;                    /* EPOLLHUPs delivered */
+};
+
+#define is_master(w) !mrp_list_empty(&(w)->hook)
+#define is_slave(w)  !mrp_list_empty(&(w)->slave)
+
+
+/*
+ * timers
+ */
+
+struct mrp_timer_s {
+    mrp_list_hook_t  hook;                       /* to list of timers */
+    mrp_list_hook_t  deleted;                    /* to list of pending delete */
+    int            (*free)(void *ptr);           /* cb to free memory */
+    mrp_mainloop_t  *ml;                         /* mainloop */
+    unsigned int     msecs;                      /* timer interval */
+    uint64_t         expire;                     /* next expiration time */
+    mrp_timer_cb_t   cb;                         /* user callback */
+    void            *user_data;                  /* opaque user data */
+};
+
+
+/*
+ * deferred callbacks
+ */
+
+struct mrp_deferred_s {
+    mrp_list_hook_t    hook;                     /* to list of cbs */
+    mrp_list_hook_t    deleted;                  /* to list of pending delete */
+    int              (*free)(void *ptr);         /* cb to free memory */
+    mrp_mainloop_t    *ml;                       /* mainloop */
+    mrp_deferred_cb_t  cb;                       /* user callback */
+    void              *user_data;                /* opaque user data */
+    int                inactive : 1;
+};
+
+
+/*
+ * signal handlers
+ */
+
+struct mrp_sighandler_s {
+    mrp_list_hook_t      hook;                   /* to list of handlers */
+    mrp_list_hook_t      deleted;                /* to list of pending delete */
+    int                (*free)(void *ptr);       /* cb to free memory */
+    mrp_mainloop_t      *ml;                     /* mainloop */
+    int                  signum;                 /* signal number */
+    mrp_sighandler_cb_t  cb;                     /* user callback */
+    void                *user_data;              /* opaque user data */
+};
+
+
+/*
+ * wakeup notifications
+ */
+
+struct mrp_wakeup_s {
+    mrp_list_hook_t      hook;                   /* to list of wakeup cbs */
+    mrp_list_hook_t      deleted;                /* to list of pending delete */
+    int                (*free)(void *ptr);       /* cb to free memory */
+    mrp_mainloop_t      *ml;                     /* mainloop */
+    mrp_wakeup_event_t   events;                 /* wakeup event mask */
+    uint64_t             lpf;                    /* wakeup at most this often */
+    uint64_t             next;                   /* next wakeup time */
+    mrp_timer_t         *timer;                  /* forced interval timer */
+    mrp_wakeup_cb_t      cb;                     /* user callback */
+    void                *user_data;              /* opaque user data */
+};
+
+#define mark_deleted(o) do {                                    \
+        (o)->cb = NULL;                                         \
+        mrp_list_append(&(o)->ml->deleted, &(o)->deleted);      \
+    } while (0)
+
+#define is_deleted(o) ((o)->cb == NULL)
+
+
+/*
+ * any of the above data structures linked to the list of deleted items
+ *
+ * When deleted, the above data structures are first unlinked from their
+ * native list and linked to the special list of deleted entries. At an
+ * appropriate point upon every iteration of the main loop this list is
+ * checked and all entries are freed. This structure is used to get a
+ * pointer to the real structure that we need free. For this to work link
+ * hooks in all of the above structures need to be kept at the same offset
+ * as it is in deleted_t.
+ */
+
+typedef struct {
+    mrp_list_hook_t  hook;                       /* unfreed deleted items */
+    mrp_list_hook_t  deleted;                    /* to list of pending delete */
+    int            (*free)(void *ptr);           /* cb to free memory */
+} deleted_t;
+
+
+/*
+ * file descriptor table
+ *
+ * We do not want to associate direct pointers to related data structures
+ * with epoll. We might get delivered pending events for deleted fds (at
+ * least for unix domain sockets this seems to be the case) and with direct
+ * pointers we'd get delivered a dangling pointer together with the event.
+ * Instead we keep these structures in an fd table and use the fd to look
+ * up the associated data structure for events. We ignore events for which
+ * no data structure is found. In the fd table we keep a fixed size direct
+ * table for a small amount of fds (we expect to be using at most in the
+ * vast majority of cases) and we hash in the rest.
+ */
+
+#define FDTBL_SIZE 64
+
+typedef struct {
+    void       *t[FDTBL_SIZE];
+    mrp_htbl_t *h;
+} fdtbl_t;
+
+
+/*
+ * external mainloops
+ */
+
+struct mrp_subloop_s {
+    mrp_list_hook_t      hook;                   /* to list of subloops */
+    mrp_list_hook_t      deleted;                /* to list of pending delete */
+    int                (*free)(void *ptr);       /* cb to free memory */
+    mrp_mainloop_t      *ml;                     /* main loop */
+    mrp_subloop_ops_t   *cb;                     /* subloop glue callbacks */
+    void                *user_data;              /* opaque subloop data */
+    int                  epollfd;                /* epollfd for this subloop */
+    struct epoll_event  *events;                 /* epoll event buffer */
+    int                  nevent;                 /* epoll event buffer size */
+    fdtbl_t             *fdtbl;                  /* file descriptor table */
+    mrp_io_watch_t      *w;                      /* watch for epollfd */
+    struct pollfd       *pollfds;                /* pollfds for this subloop */
+    int                  npollfd;                /* number of pollfds */
+    int                  pending;                /* pending events */
+    int                  poll;                   /* need to poll for events */
+};
+
+
+/*
+ * event busses
+ */
+
+struct mrp_event_bus_s {
+    char            *name;                       /* bus name */
+    mrp_list_hook_t  hook;                       /* to list of busses */
+    mrp_mainloop_t  *ml;                         /* associated mainloop */
+    mrp_list_hook_t  watches;                    /* event watches on this bus */
+    int              busy;                       /* whether pumping events */
+    int              dead;
+};
+
+
+/*
+ * event watches
+ */
+
+struct mrp_event_watch_s {
+    mrp_list_hook_t       hook;                  /* to list of event watches */
+    mrp_event_bus_t      *bus;                   /* associated event bus */
+    mrp_event_mask_t      mask;                  /* mask of watched events */
+    mrp_event_watch_cb_t  cb;                    /* notification callback */
+    void                 *user_data;             /* opaque user data */
+    int                   dead : 1;              /* marked for deletion */
+};
+
+
+/*
+ * pending events
+ */
+
+typedef struct {
+    mrp_list_hook_t  hook;                       /* to event queue */
+    mrp_event_bus_t *bus;                        /* bus for this event */
+    uint32_t         id;                         /* event id */
+    int              format;                     /* attached data format */
+    void            *data;                       /* attached data */
+} pending_event_t;
+
+
+/*
+ * main loop
+ */
+
+struct mrp_mainloop_s {
+    int                  epollfd;                /* our epoll descriptor */
+    struct epoll_event  *events;                 /* epoll event buffer */
+    int                  nevent;                 /* epoll event buffer size */
+    fdtbl_t             *fdtbl;                  /* file descriptor table */
+
+    mrp_list_hook_t      iowatches;              /* list of I/O watches */
+    int                  niowatch;               /* number of I/O watches */
+    mrp_io_event_t       iomode;                 /* default event trigger mode */
+
+    mrp_list_hook_t      timers;                 /* list of timers */
+    mrp_timer_t         *next_timer;             /* next expiring timer */
+
+    mrp_list_hook_t      deferred;               /* list of deferred cbs */
+    mrp_list_hook_t      inactive_deferred;      /* inactive defferred cbs */
+
+    mrp_list_hook_t      wakeups;                /* list of wakeup cbs */
+
+    int                  poll_timeout;           /* next poll timeout */
+    int                  poll_result;            /* return value from poll */
+
+    int                  sigfd;                  /* signal polling fd */
+    sigset_t             sigmask;                /* signal mask */
+    mrp_io_watch_t      *sigwatch;               /* sigfd I/O watch */
+    mrp_list_hook_t      sighandlers;            /* signal handlers */
+
+    mrp_list_hook_t      subloops;               /* external main loops */
+
+    mrp_list_hook_t      deleted;                /* unfreed deleted items */
+    int                  quit;                   /* TRUE if _quit called */
+    int                  exit_code;              /* returned from _run */
+
+    mrp_superloop_ops_t *super_ops;              /* superloop options */
+    void                *super_data;             /* superloop glue data */
+    void                *iow;                    /* superloop epollfd watch */
+    void                *timer;                  /* superloop timer */
+    void                *work;                   /* superloop deferred work */
+
+    mrp_list_hook_t      busses;                 /* known event busses */
+    mrp_list_hook_t      eventq;                 /* pending events */
+    mrp_deferred_t      *eventd;                 /* deferred event pump cb */
+};
+
+
+static mrp_event_def_t *events;                  /* registered events */
+static int              nevent;                  /* number of events */
+static MRP_LIST_HOOK   (ewatches);               /* global, synchronous 'bus' */
+
+
+static void dump_pollfds(const char *prefix, struct pollfd *fds, int nfd);
+static void adjust_superloop_timer(mrp_mainloop_t *ml);
+static size_t poll_events(void *id, mrp_mainloop_t *ml, void **bufp);
+static void pump_events(mrp_deferred_t *d, void *user_data);
+
+/*
+ * fd table manipulation
+ */
+
+static int fd_cmp(const void *key1, const void *key2)
+{
+    return key2 - key1;
+}
+
+
+static uint32_t fd_hash(const void *key)
+{
+    uint32_t h;
+
+    h = (uint32_t)(ptrdiff_t)key;
+
+    return h;
+}
+
+
+
+static fdtbl_t *fdtbl_create(void)
+{
+    fdtbl_t           *ft;
+    mrp_htbl_config_t  hcfg;
+
+    if ((ft = mrp_allocz(sizeof(*ft))) != NULL) {
+        mrp_clear(&hcfg);
+
+        hcfg.comp    = fd_cmp;
+        hcfg.hash    = fd_hash;
+        hcfg.free    = NULL;
+        hcfg.nbucket = 16;
+
+        ft->h = mrp_htbl_create(&hcfg);
+
+        if (ft->h != NULL)
+            return ft;
+        else
+            mrp_free(ft);
+    }
+
+    return NULL;
+}
+
+
+static void fdtbl_destroy(fdtbl_t *ft)
+{
+    if (ft != NULL) {
+        mrp_htbl_destroy(ft->h, FALSE);
+        mrp_free(ft);
+    }
+}
+
+
+static void *fdtbl_lookup(fdtbl_t *ft, int fd)
+{
+    if (fd >= 0 && ft != NULL) {
+        if (fd < FDTBL_SIZE)
+            return ft->t[fd];
+        else
+            return mrp_htbl_lookup(ft->h, (void *)(ptrdiff_t)fd);
+    }
+
+    return NULL;
+}
+
+
+static int fdtbl_insert(fdtbl_t *ft, int fd, void *ptr)
+{
+    if (fd >= 0 && ft != NULL) {
+        if (fd < FDTBL_SIZE) {
+            if (ft->t[fd] == NULL) {
+                ft->t[fd] = ptr;
+                return 0;
+            }
+            else
+                errno = EEXIST;
+        }
+        else {
+            if (mrp_htbl_insert(ft->h, (void *)(ptrdiff_t)fd, ptr))
+                return 0;
+            else
+                errno = EEXIST;
+        }
+    }
+    else
+        errno = EINVAL;
+
+    return -1;
+}
+
+
+static void fdtbl_remove(fdtbl_t *ft, int fd)
+{
+    if (fd >= 0 && ft != NULL) {
+        if (fd < FDTBL_SIZE)
+            ft->t[fd] = NULL;
+        else
+            mrp_htbl_remove(ft->h, (void *)(ptrdiff_t)fd, FALSE);
+    }
+}
+
+
+/*
+ * I/O watches
+ */
+
+static uint32_t epoll_event_mask(mrp_io_watch_t *master, mrp_io_watch_t *ignore)
+{
+    mrp_io_watch_t  *w;
+    mrp_list_hook_t *p, *n;
+    uint32_t         mask;
+
+    mask = (master != ignore ?
+            master->events : master->events & MRP_IO_TRIGGER_EDGE);
+
+    mrp_list_foreach(&master->slave, p, n) {
+        w = mrp_list_entry(p, typeof(*w), slave);
+
+        if (w != ignore)
+            mask |= w->events;
+    }
+
+    mrp_debug("epoll event mask for I/O watch %p: %d", master, mask);
+
+    return mask;
+}
+
+
+static int epoll_add_slave(mrp_io_watch_t *master, mrp_io_watch_t *slave)
+{
+    mrp_mainloop_t     *ml = master->ml;
+    struct epoll_event  evt;
+
+    evt.events   = epoll_event_mask(master, NULL) | slave->events;
+    evt.data.u64 = 0;
+    evt.data.fd  = master->fd;
+
+    if (epoll_ctl(ml->epollfd, EPOLL_CTL_MOD, master->fd, &evt) == 0) {
+        mrp_list_append(&master->slave, &slave->slave);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+static int epoll_add(mrp_io_watch_t *w)
+{
+    mrp_mainloop_t     *ml = w->ml;
+    mrp_io_watch_t     *master;
+    struct epoll_event  evt;
+
+    if (fdtbl_insert(ml->fdtbl, w->fd, w) == 0) {
+        evt.events   = w->events;
+        evt.data.u64 = 0;                /* init full union for valgrind... */
+        evt.data.fd  = w->fd;
+
+        if (epoll_ctl(ml->epollfd, EPOLL_CTL_ADD, w->fd, &evt) == 0) {
+            mrp_list_append(&ml->iowatches, &w->hook);
+            ml->niowatch++;
+
+            return 0;
+        }
+        else
+            fdtbl_remove(ml->fdtbl, w->fd);
+    }
+    else {
+        if (errno == EEXIST) {
+            master = fdtbl_lookup(ml->fdtbl, w->fd);
+
+            if (master != NULL)
+                return epoll_add_slave(master, w);
+        }
+    }
+
+    return -1;
+}
+
+
+static int epoll_del(mrp_io_watch_t *w)
+{
+    mrp_mainloop_t     *ml = w->ml;
+    mrp_io_watch_t     *master;
+    struct epoll_event  evt;
+    int                 status;
+
+    if (is_master(w))
+        master = w;
+    else
+        master = fdtbl_lookup(ml->fdtbl, w->fd);
+
+    if (master != NULL) {
+        evt.events   = epoll_event_mask(master, w);
+        evt.data.u64 = 0;                /* init full union for valgrind... */
+        evt.data.fd  = w->fd;
+
+        if ((evt.events & MRP_IO_EVENT_ALL) == 0) {
+            fdtbl_remove(ml->fdtbl, w->fd);
+            status = epoll_ctl(ml->epollfd, EPOLL_CTL_DEL, w->fd, &evt);
+
+            if (status == 0 || (errno == EBADF || errno == ENOENT))
+                ml->niowatch--;
+        }
+        else
+            status = epoll_ctl(ml->epollfd, EPOLL_CTL_MOD, w->fd, &evt);
+
+        if (status == 0 || (errno == EBADF || errno == ENOENT))
+            return 0;
+        else
+            mrp_log_error("Failed to update epoll for deleted I/O watch %p "
+                          "(fd %d, %d: %s).", w, w->fd, errno, strerror(errno));
+    }
+    else {
+        mrp_log_error("Failed to find master for deleted I/O watch %p "
+                      "(fd %d).", w, w->fd);
+        errno = EINVAL;
+    }
+
+    return -1;
+}
+
+
+static int free_io_watch(void *ptr)
+{
+    mrp_io_watch_t *w  = (mrp_io_watch_t *)ptr;
+    mrp_mainloop_t *ml = w->ml;
+    mrp_io_watch_t *master;
+
+    master = fdtbl_lookup(ml->fdtbl, w->fd);
+
+    if (master == w) {
+        fdtbl_remove(ml->fdtbl, w->fd);
+
+        if (!mrp_list_empty(&w->slave)) {
+            /* relink first slave as new master to mainloop */
+            master = mrp_list_entry(w->slave.next, typeof(*master), slave);
+            mrp_list_append(&ml->iowatches, &master->hook);
+
+            fdtbl_insert(ml->fdtbl, master->fd, master);
+        }
+    }
+
+    mrp_list_delete(&w->slave);
+    mrp_free(w);
+
+    return TRUE;
+}
+
+
+mrp_io_watch_t *mrp_add_io_watch(mrp_mainloop_t *ml, int fd,
+                                 mrp_io_event_t events,
+                                 mrp_io_watch_cb_t cb, void *user_data)
+{
+    mrp_io_watch_t *w;
+
+    if (fd < 0 || cb == NULL)
+        return NULL;
+
+    if ((w = mrp_allocz(sizeof(*w))) != NULL) {
+        mrp_list_init(&w->hook);
+        mrp_list_init(&w->deleted);
+        mrp_list_init(&w->slave);
+        w->ml        = ml;
+        w->fd        = fd;
+        w->events    = events & MRP_IO_EVENT_ALL;
+
+        switch (events & MRP_IO_TRIGGER_MASK) {
+        case 0:
+            if (ml->iomode == MRP_IO_TRIGGER_EDGE)
+                w->events |= MRP_IO_TRIGGER_EDGE;
+            break;
+        case MRP_IO_TRIGGER_EDGE:
+            w->events |= MRP_IO_TRIGGER_EDGE;
+            break;
+        case MRP_IO_TRIGGER_LEVEL:
+            break;
+        default:
+            mrp_log_warning("Invalid I/O event trigger mode 0x%x.",
+                            events & MRP_IO_TRIGGER_MASK);
+            break;
+        }
+
+        w->cb        = cb;
+        w->user_data = user_data;
+        w->free      = free_io_watch;
+
+        if (epoll_add(w) != 0) {
+            mrp_free(w);
+            w = NULL;
+        }
+        else
+            mrp_debug("added I/O watch %p (fd %d, events 0x%x)", w, w->fd, w->events);
+    }
+
+    return w;
+}
+
+
+void mrp_del_io_watch(mrp_io_watch_t *w)
+{
+    /*
+     * Notes: It is not safe to free the watch here as there might be
+     *        a delivered but unprocessed epoll event with a pointer
+     *        to the watch. We just mark it deleted and take care of
+     *        the actual deletion in the dispatching loop.
+     */
+
+    if (w != NULL && !is_deleted(w)) {
+        mrp_debug("marking I/O watch %p (fd %d) deleted", w, w->fd);
+
+        mark_deleted(w);
+        w->events = 0;
+
+        epoll_del(w);
+    }
+}
+
+
+mrp_mainloop_t *mrp_get_io_watch_mainloop(mrp_io_watch_t *w)
+{
+    return w ? w->ml : NULL;
+}
+
+
+int mrp_set_io_event_mode(mrp_mainloop_t *ml, mrp_io_event_t mode)
+{
+    if (mode == MRP_IO_TRIGGER_LEVEL || mode == MRP_IO_TRIGGER_EDGE) {
+        ml->iomode = mode;
+        return TRUE;
+    }
+    else {
+        mrp_log_error("Invalid I/O event mode 0x%x.", mode);
+        return FALSE;
+    }
+}
+
+
+mrp_io_event_t mrp_get_io_event_mode(mrp_mainloop_t *ml)
+{
+    return ml->iomode ? ml->iomode : MRP_IO_TRIGGER_LEVEL;
+}
+
+
+/*
+ * timers
+ */
+
+static uint64_t time_now(void)
+{
+    struct timespec ts;
+    uint64_t        now;
+
+    clock_gettime(CLOCK_MONOTONIC, &ts);
+    now  = ts.tv_sec  * USECS_PER_SEC;
+    now += ts.tv_nsec / NSECS_PER_USEC;
+
+    return now;
+}
+
+
+static inline int usecs_to_msecs(uint64_t usecs)
+{
+    int msecs;
+
+    msecs = (usecs + USECS_PER_MSEC - 1) / USECS_PER_MSEC;
+
+    return msecs;
+}
+
+
+static void insert_timer(mrp_timer_t *t)
+{
+    mrp_mainloop_t  *ml = t->ml;
+    mrp_list_hook_t *p, *n;
+    mrp_timer_t     *t1, *next;
+    int              inserted;
+
+    /*
+     * Notes:
+     *     If there is ever a need to run a large number of
+     *     simultaneous timers, we need to change this to a
+     *     self-balancing data structure, eg. an red-black tree.
+     */
+
+    inserted = FALSE;
+    next     = NULL;
+    mrp_list_foreach(&ml->timers, p, n) {
+        t1 = mrp_list_entry(p, mrp_timer_t, hook);
+
+        if (!is_deleted(t1)) {
+            if (t->expire <= t1->expire) {
+                mrp_list_prepend(p->prev, &t->hook);
+                inserted = TRUE;
+                break;
+            }
+            if (next == NULL)
+                next = t1;
+        }
+    }
+
+    if (!inserted)
+        mrp_list_append(&ml->timers, &t->hook);
+
+    if (next)
+        ml->next_timer = next;
+    else {
+        ml->next_timer = t;
+        adjust_superloop_timer(ml);
+    }
+}
+
+
+static inline void rearm_timer(mrp_timer_t *t)
+{
+    mrp_list_delete(&t->hook);
+    t->expire = time_now() + t->msecs * USECS_PER_MSEC;
+    insert_timer(t);
+}
+
+
+static mrp_timer_t *find_next_timer(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_timer_t     *t = NULL;
+
+    mrp_list_foreach(&ml->timers, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        if (!is_deleted(t))
+            break;
+        else
+            t = NULL;
+    }
+
+    ml->next_timer = t;
+    return t;
+}
+
+
+static int free_timer(void *ptr)
+{
+    mrp_timer_t *t = (mrp_timer_t *)ptr;
+
+    mrp_free(t);
+
+    return TRUE;
+}
+
+
+
+mrp_timer_t *mrp_add_timer(mrp_mainloop_t *ml, unsigned int msecs,
+                           mrp_timer_cb_t cb, void *user_data)
+{
+    mrp_timer_t *t;
+
+    if (cb == NULL)
+        return NULL;
+
+    if ((t = mrp_allocz(sizeof(*t))) != NULL) {
+        mrp_list_init(&t->hook);
+        mrp_list_init(&t->deleted);
+        t->ml        = ml;
+        t->expire    = time_now() + msecs * USECS_PER_MSEC;
+        t->msecs     = msecs;
+        t->cb        = cb;
+        t->user_data = user_data;
+        t->free      = free_timer;
+
+        insert_timer(t);
+    }
+
+    return t;
+}
+
+
+void mrp_mod_timer(mrp_timer_t *t, unsigned int msecs)
+{
+    if (t != NULL && !is_deleted(t)) {
+        if (msecs != MRP_TIMER_RESTART)
+            t->msecs = msecs;
+
+        rearm_timer(t);
+    }
+}
+
+
+void mrp_del_timer(mrp_timer_t *t)
+{
+    /*
+     * Notes: It is not safe to simply free this entry here as we might
+     *        be dispatching with this entry being the next to process.
+     *        We check for this and if it is not the case we relink this
+     *        to the list of deleted items which will be then processed
+     *        at end of the mainloop iteration. Otherwise we only mark the
+     *        this entry for deletion and the rest will be taken care of in
+     *        dispatch_timers().
+     */
+
+    if (t != NULL && !is_deleted(t)) {
+        mrp_debug("marking timer %p deleted", t);
+
+        mark_deleted(t);
+
+        if (t->ml->next_timer == t) {
+            find_next_timer(t->ml);
+            adjust_superloop_timer(t->ml);
+        }
+    }
+}
+
+
+mrp_mainloop_t *mrp_get_timer_mainloop(mrp_timer_t *t)
+{
+    return t ? t->ml : NULL;
+}
+
+
+/*
+ * deferred/idle callbacks
+ */
+
+mrp_deferred_t *mrp_add_deferred(mrp_mainloop_t *ml, mrp_deferred_cb_t cb,
+                                 void *user_data)
+{
+    mrp_deferred_t *d;
+
+    if (cb == NULL)
+        return NULL;
+
+    if ((d = mrp_allocz(sizeof(*d))) != NULL) {
+        mrp_list_init(&d->hook);
+        mrp_list_init(&d->deleted);
+        d->ml        = ml;
+        d->cb        = cb;
+        d->user_data = user_data;
+
+        mrp_list_append(&ml->deferred, &d->hook);
+        adjust_superloop_timer(ml);
+    }
+
+    return d;
+}
+
+
+void mrp_del_deferred(mrp_deferred_t *d)
+{
+    /*
+     * Notes: It is not safe to simply free this entry here as we might
+     *        be dispatching with this entry being the next to process.
+     *        We just mark this here deleted and take care of the rest
+     *        in the dispatching loop.
+     */
+
+    if (d != NULL && !is_deleted(d)) {
+        mrp_debug("marking deferred %p deleted", d);
+        mark_deleted(d);
+    }
+}
+
+
+void mrp_disable_deferred(mrp_deferred_t *d)
+{
+    if (d != NULL)
+        d->inactive = TRUE;
+}
+
+
+static inline void disable_deferred(mrp_deferred_t *d)
+{
+    if (MRP_LIKELY(d->inactive)) {
+        mrp_list_delete(&d->hook);
+        mrp_list_append(&d->ml->inactive_deferred, &d->hook);
+    }
+
+}
+
+
+void mrp_enable_deferred(mrp_deferred_t *d)
+{
+    if (d != NULL) {
+        if (!is_deleted(d)) {
+            d->inactive = FALSE;
+            mrp_list_delete(&d->hook);
+            mrp_list_append(&d->ml->deferred, &d->hook);
+        }
+    }
+}
+
+
+mrp_mainloop_t *mrp_get_deferred_mainloop(mrp_deferred_t *d)
+{
+    return d ? d->ml : NULL;
+}
+
+
+/*
+ * signal notifications
+ */
+
+static void dispatch_signals(mrp_io_watch_t *w, int fd,
+                             mrp_io_event_t events, void *user_data)
+{
+    mrp_mainloop_t          *ml = mrp_get_io_watch_mainloop(w);
+    struct signalfd_siginfo  sig;
+    mrp_list_hook_t         *p, *n;
+    mrp_sighandler_t        *h;
+    int                      signum;
+
+    MRP_UNUSED(events);
+    MRP_UNUSED(user_data);
+
+    while (read(fd, &sig, sizeof(sig)) > 0) {
+        signum = sig.ssi_signo;
+
+        mrp_list_foreach(&ml->sighandlers, p, n) {
+            h = mrp_list_entry(p, typeof(*h), hook);
+
+            if (!is_deleted(h)) {
+                if (h->signum == signum)
+                    h->cb(h, signum, h->user_data);
+            }
+        }
+    }
+}
+
+
+static int setup_sighandlers(mrp_mainloop_t *ml)
+{
+    if (ml->sigfd == -1) {
+        sigemptyset(&ml->sigmask);
+
+        ml->sigfd = signalfd(-1, &ml->sigmask, SFD_NONBLOCK | SFD_CLOEXEC);
+
+        if (ml->sigfd == -1)
+            return FALSE;
+
+        ml->sigwatch = mrp_add_io_watch(ml, ml->sigfd, MRP_IO_EVENT_IN,
+                                        dispatch_signals, NULL);
+
+        if (ml->sigwatch == NULL) {
+            close(ml->sigfd);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+mrp_sighandler_t *mrp_add_sighandler(mrp_mainloop_t *ml, int signum,
+                                     mrp_sighandler_cb_t cb, void *user_data)
+{
+    mrp_sighandler_t *s;
+
+    if (cb == NULL || ml->sigfd == -1)
+        return NULL;
+
+    if ((s = mrp_allocz(sizeof(*s))) != NULL) {
+        mrp_list_init(&s->hook);
+        mrp_list_init(&s->deleted);
+        s->ml        = ml;
+        s->signum    = signum;
+        s->cb        = cb;
+        s->user_data = user_data;
+
+        mrp_list_append(&ml->sighandlers, &s->hook);
+        sigaddset(&ml->sigmask, s->signum);
+        signalfd(ml->sigfd, &ml->sigmask, SFD_NONBLOCK|SFD_CLOEXEC);
+        sigprocmask(SIG_BLOCK, &ml->sigmask, NULL);
+    }
+
+    return s;
+}
+
+
+static void recalc_sigmask(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t  *p, *n;
+    mrp_sighandler_t *h;
+
+    sigprocmask(SIG_UNBLOCK, &ml->sigmask, NULL);
+    sigemptyset(&ml->sigmask);
+
+    mrp_list_foreach(&ml->sighandlers, p, n) {
+        h = mrp_list_entry(p, typeof(*h), hook);
+        if (!is_deleted(h))
+            sigaddset(&ml->sigmask, h->signum);
+    }
+
+    sigprocmask(SIG_BLOCK, &ml->sigmask, NULL);
+}
+
+
+void mrp_del_sighandler(mrp_sighandler_t *h)
+{
+    if (h != NULL && !is_deleted(h)) {
+        mrp_debug("marking sighandler %p deleted", h);
+
+        mark_deleted(h);
+        recalc_sigmask(h->ml);
+    }
+}
+
+
+mrp_mainloop_t *mrp_get_sighandler_mainloop(mrp_sighandler_t *h)
+{
+    return h ? h->ml : NULL;
+}
+
+
+/*
+ * wakeup notifications
+ */
+
+static void wakeup_cb(mrp_wakeup_t *w, mrp_wakeup_event_t event, uint64_t now)
+{
+    if (w->next > now) {
+        mrp_debug("skipping wakeup %p because of low-pass filter", w);
+        return;
+    }
+
+    w->cb(w, event, w->user_data);
+
+    if (w->lpf != MRP_WAKEUP_NOLIMIT)
+        w->next = now + w->lpf;
+
+    if (w->timer != NULL)
+        mrp_mod_timer(w->timer, MRP_TIMER_RESTART);
+}
+
+
+static void forced_wakeup_cb(mrp_timer_t *t, void *user_data)
+{
+    mrp_wakeup_t *w = (mrp_wakeup_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    if (is_deleted(w))
+        return;
+
+    mrp_debug("dispatching forced wakeup cb %p", w);
+
+    wakeup_cb(w, MRP_WAKEUP_EVENT_LIMIT, time_now());
+}
+
+
+mrp_wakeup_t *mrp_add_wakeup(mrp_mainloop_t *ml, mrp_wakeup_event_t events,
+                             unsigned int lpf_msecs, unsigned int force_msecs,
+                             mrp_wakeup_cb_t cb, void *user_data)
+{
+    mrp_wakeup_t *w;
+
+    if (cb == NULL)
+        return NULL;
+
+    if (lpf_msecs > force_msecs && force_msecs != MRP_WAKEUP_NOLIMIT)
+        return NULL;
+
+    if ((w = mrp_allocz(sizeof(*w))) != NULL) {
+        mrp_list_init(&w->hook);
+        mrp_list_init(&w->deleted);
+        w->ml        = ml;
+        w->events    = events;
+        w->cb        = cb;
+        w->user_data = user_data;
+
+        w->lpf = lpf_msecs * USECS_PER_MSEC;
+
+        if (lpf_msecs != MRP_WAKEUP_NOLIMIT)
+            w->next = time_now() + w->lpf;
+
+        if (force_msecs != MRP_WAKEUP_NOLIMIT) {
+            w->timer = mrp_add_timer(ml, force_msecs, forced_wakeup_cb, w);
+
+            if (w->timer == NULL) {
+                mrp_free(w);
+                return NULL;
+            }
+        }
+
+        mrp_list_append(&ml->wakeups, &w->hook);
+    }
+
+    return w;
+}
+
+
+void mrp_del_wakeup(mrp_wakeup_t *w)
+{
+    /*
+     * Notes: It is not safe to simply free this entry here as we might
+     *        be dispatching with this entry being the next to process.
+     *        We just mark this here deleted and take care of the rest
+     *        in the dispatching loop.
+     */
+
+    if (w != NULL && !is_deleted(w)) {
+        mrp_debug("marking wakeup %p deleted", w);
+        mark_deleted(w);
+    }
+}
+
+
+mrp_mainloop_t *mrp_get_wakeup_mainloop(mrp_wakeup_t *w)
+{
+    return w ? w->ml : NULL;
+}
+
+
+/*
+ * external mainloops we pump
+ */
+
+static int free_subloop(void *ptr)
+{
+    mrp_subloop_t *sl = (mrp_subloop_t *)ptr;
+
+    mrp_debug("freeing subloop %p", sl);
+
+    mrp_free(sl->pollfds);
+    mrp_free(sl->events);
+    mrp_free(sl);
+
+    return TRUE;
+}
+
+
+static void subloop_event_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                             void *user_data)
+{
+    mrp_subloop_t *sl = (mrp_subloop_t *)user_data;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(fd);
+    MRP_UNUSED(events);
+
+    mrp_debug("subloop %p has events, setting poll to TRUE", sl);
+
+    sl->poll = TRUE;
+}
+
+
+mrp_subloop_t *mrp_add_subloop(mrp_mainloop_t *ml, mrp_subloop_ops_t *ops,
+                               void *user_data)
+{
+    mrp_subloop_t *sl;
+
+    if (ops == NULL || user_data == NULL)
+        return NULL;
+
+    if ((sl = mrp_allocz(sizeof(*sl))) != NULL) {
+        mrp_list_init(&sl->hook);
+        mrp_list_init(&sl->deleted);
+        sl->free      = free_subloop;
+        sl->ml        = ml;
+        sl->cb        = ops;
+        sl->user_data = user_data;
+        sl->epollfd   = epoll_create1(EPOLL_CLOEXEC);
+        sl->fdtbl     = fdtbl_create();
+
+        if (sl->epollfd >= 0 && sl->fdtbl != NULL) {
+            sl->w = mrp_add_io_watch(ml, sl->epollfd, MRP_IO_EVENT_IN,
+                                     subloop_event_cb, sl);
+
+            if (sl->w != NULL)
+                mrp_list_append(&ml->subloops, &sl->hook);
+            else
+                goto fail;
+        }
+        else {
+        fail:
+            close(sl->epollfd);
+            fdtbl_destroy(sl->fdtbl);
+            mrp_free(sl);
+            sl = NULL;
+        }
+    }
+
+    return sl;
+}
+
+
+void mrp_del_subloop(mrp_subloop_t *sl)
+{
+    struct epoll_event dummy;
+    int                i;
+
+    /*
+     * Notes: It is not safe to free the loop here as there might be
+     *        a delivered but unprocessed epoll event with a pointers
+     *        to the loops pollfds. However, since we do not dispatch
+     *        loops by traversing the list of loops, it is safe to relink
+     *        it to the list of data structures to be deleted at the
+     *        end of the next main loop iteration. So we just remove the
+     *        pollfds from epoll, mark this as deleted and relink it.
+     */
+
+    if (sl != NULL && !is_deleted(sl)) {
+        mrp_debug("deactivating and marking subloop %p deleted", sl);
+
+        mrp_del_io_watch(sl->w);
+
+        /* XXX TODO: Why ? close(sl->epollfd) should be enough... */
+        for (i = 0; i < sl->npollfd; i++)
+            epoll_ctl(sl->epollfd, EPOLL_CTL_DEL, sl->pollfds[i].fd, &dummy);
+
+        close(sl->epollfd);
+        sl->epollfd = -1;
+        fdtbl_destroy(sl->fdtbl);
+        sl->fdtbl = NULL;
+
+        mark_deleted(sl);
+    }
+}
+
+
+/*
+ * external mainloop that pumps us
+ */
+
+
+static void super_io_cb(void *super_data, void *id, int fd,
+                        mrp_io_event_t events, void *user_data)
+{
+    mrp_mainloop_t      *ml  = (mrp_mainloop_t *)user_data;
+    mrp_superloop_ops_t *ops = ml->super_ops;
+
+    MRP_UNUSED(super_data);
+    MRP_UNUSED(id);
+    MRP_UNUSED(fd);
+    MRP_UNUSED(events);
+
+    ops->mod_defer(ml->super_data, ml->work, TRUE);
+}
+
+
+static void super_timer_cb(void *super_data, void *id, void *user_data)
+{
+    mrp_mainloop_t      *ml  = (mrp_mainloop_t *)user_data;
+    mrp_superloop_ops_t *ops = ml->super_ops;
+
+    MRP_UNUSED(super_data);
+    MRP_UNUSED(id);
+
+    ops->mod_defer(ml->super_data, ml->work, TRUE);
+}
+
+
+static void super_work_cb(void *super_data, void *id, void *user_data)
+{
+    mrp_mainloop_t      *ml  = (mrp_mainloop_t *)user_data;
+    mrp_superloop_ops_t *ops = ml->super_ops;
+    unsigned int         timeout;
+
+    MRP_UNUSED(super_data);
+    MRP_UNUSED(id);
+
+    mrp_mainloop_poll(ml, FALSE);
+    mrp_mainloop_dispatch(ml);
+
+    if (!ml->quit) {
+        mrp_mainloop_prepare(ml);
+
+        /*
+         * Notes:
+         *
+         *     Some mainloop abstractions (eg. the one in PulseAudio)
+         *     have deferred callbacks that starve all other event
+         *     processing until no more deferred callbacks are pending.
+         *     For this reason, we cannot map our deferred callbacks
+         *     directly to superloop deferred callbacks (in some cases
+         *     this could starve the superloop indefinitely). Hence, if
+         *     we have enabled deferred callbacks, we arm our timer with
+         *     0 timeout to let the superloop do one round of its event
+         *     processing.
+         */
+
+        timeout = mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0;
+        ops->mod_timer(ml->super_data, ml->timer, timeout);
+        ops->mod_defer(ml->super_data, ml->work, FALSE);
+    }
+    else {
+        ops->del_io(ml->super_data, ml->iow);
+        ops->del_timer(ml->super_data, ml->timer);
+        ops->del_defer(ml->super_data, ml->work);
+
+        ml->iow   = NULL;
+        ml->timer = NULL;
+        ml->work  = NULL;
+    }
+}
+
+
+static void adjust_superloop_timer(mrp_mainloop_t *ml)
+{
+    mrp_superloop_ops_t *ops = ml->super_ops;
+    unsigned int         timeout;
+
+    if (ops == NULL)
+        return;
+
+    mrp_mainloop_prepare(ml);
+    timeout = mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0;
+    ops->mod_timer(ml->super_data, ml->timer, timeout);
+}
+
+
+int mrp_set_superloop(mrp_mainloop_t *ml, mrp_superloop_ops_t *ops,
+                      void *loop_data)
+{
+    mrp_io_event_t events;
+    int            timeout;
+
+    if (ml->super_ops == NULL) {
+        if (ops->poll_io != NULL)
+            ops->poll_events = poll_events;
+
+        ml->super_ops  = ops;
+        ml->super_data = loop_data;
+
+        mrp_mainloop_prepare(ml);
+
+        events    = MRP_IO_EVENT_IN | MRP_IO_EVENT_OUT | MRP_IO_EVENT_HUP;
+        ml->iow   = ops->add_io(ml->super_data, ml->epollfd, events,
+                                super_io_cb, ml);
+        ml->work  = ops->add_defer(ml->super_data, super_work_cb, ml);
+
+        /*
+         * Notes:
+         *
+         *     Some mainloop abstractions (eg. the one in PulseAudio)
+         *     have deferred callbacks that starve all other event
+         *     processing until no more deferred callbacks are pending.
+         *     For this reason, we cannot map our deferred callbacks
+         *     directly to superloop deferred callbacks (in some cases
+         *     this could starve the superloop indefinitely). Hence, if
+         *     we have enabled deferred callbacks, we arm our timer with
+         *     0 timeout to let the superloop do one round of its event
+         *     processing.
+         */
+
+        timeout   = mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0;
+        ml->timer = ops->add_timer(ml->super_data, timeout, super_timer_cb, ml);
+
+        if (ml->iow != NULL && ml->timer != NULL && ml->work != NULL)
+            return TRUE;
+        else
+            mrp_clear_superloop(ml);
+    }
+
+    return FALSE;
+}
+
+
+int mrp_clear_superloop(mrp_mainloop_t *ml)
+{
+    mrp_superloop_ops_t *ops  = ml->super_ops;
+    void                *data = ml->super_data;
+
+    if (ops != NULL) {
+        if (ml->iow != NULL) {
+            ops->del_io(data, ml->iow);
+            ml->iow = NULL;
+        }
+
+        if (ml->work != NULL) {
+            ops->del_defer(data, ml->work);
+            ml->work = NULL;
+        }
+
+        if (ml->timer != NULL) {
+            ops->del_timer(data, ml->timer);
+            ml->timer = NULL;
+        }
+
+        ml->super_ops  = NULL;
+        ml->super_data = NULL;
+
+        ops->unregister(data);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_mainloop_unregister(mrp_mainloop_t *ml)
+{
+    return mrp_clear_superloop(ml);
+}
+
+
+/*
+ * mainloop
+ */
+
+static void purge_io_watches(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n, *sp, *sn;
+    mrp_io_watch_t  *w, *s;
+
+    mrp_list_foreach(&ml->iowatches, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+        mrp_list_delete(&w->hook);
+        mrp_list_delete(&w->deleted);
+
+        mrp_list_foreach(&w->slave, sp, sn) {
+            s = mrp_list_entry(sp, typeof(*s), slave);
+            mrp_list_delete(&s->slave);
+            mrp_free(s);
+        }
+
+        mrp_free(w);
+    }
+}
+
+
+static void purge_timers(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_timer_t     *t;
+
+    mrp_list_foreach(&ml->timers, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+        mrp_list_delete(&t->hook);
+        mrp_list_delete(&t->deleted);
+        mrp_free(t);
+    }
+}
+
+
+static void purge_deferred(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_deferred_t  *d;
+
+    mrp_list_foreach(&ml->deferred, p, n) {
+        d = mrp_list_entry(p, typeof(*d), hook);
+        mrp_list_delete(&d->hook);
+        mrp_list_delete(&d->deleted);
+        mrp_free(d);
+    }
+
+    mrp_list_foreach(&ml->inactive_deferred, p, n) {
+        d = mrp_list_entry(p, typeof(*d), hook);
+        mrp_list_delete(&d->hook);
+        mrp_list_delete(&d->deleted);
+        mrp_free(d);
+    }
+}
+
+
+static void purge_sighandlers(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t  *p, *n;
+    mrp_sighandler_t *s;
+
+    mrp_list_foreach(&ml->sighandlers, p, n) {
+        s = mrp_list_entry(p, typeof(*s), hook);
+        mrp_list_delete(&s->hook);
+        mrp_list_delete(&s->deleted);
+        mrp_free(s);
+    }
+}
+
+
+static void purge_wakeups(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_wakeup_t    *w;
+
+    mrp_list_foreach(&ml->wakeups, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+        mrp_list_delete(&w->hook);
+        mrp_list_delete(&w->deleted);
+        mrp_free(w);
+    }
+}
+
+
+static void purge_deleted(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    deleted_t       *d;
+
+    mrp_list_foreach(&ml->deleted, p, n) {
+        d = mrp_list_entry(p, typeof(*d), deleted);
+        mrp_list_delete(&d->deleted);
+        mrp_list_delete(&d->hook);
+        if (d->free == NULL) {
+            mrp_debug("purging deleted object %p", d);
+            mrp_free(d);
+        }
+        else {
+            mrp_debug("purging deleted object %p (free cb: %p)", d, d->free);
+            if (!d->free(d)) {
+                mrp_log_error("Failed to free purged item %p.", d);
+                mrp_list_prepend(p, &d->deleted);
+            }
+        }
+    }
+}
+
+
+static void purge_subloops(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_subloop_t   *sl;
+
+    mrp_list_foreach(&ml->subloops, p, n) {
+        sl = mrp_list_entry(p, typeof(*sl), hook);
+        mrp_list_delete(&sl->hook);
+        mrp_list_delete(&sl->deleted);
+        free_subloop(sl);
+    }
+}
+
+
+mrp_mainloop_t *mrp_mainloop_create(void)
+{
+    mrp_mainloop_t *ml;
+
+    if ((ml = mrp_allocz(sizeof(*ml))) != NULL) {
+        ml->epollfd = epoll_create1(EPOLL_CLOEXEC);
+        ml->sigfd   = -1;
+        ml->fdtbl   = fdtbl_create();
+
+        if (ml->epollfd >= 0 && ml->fdtbl != NULL) {
+            mrp_list_init(&ml->iowatches);
+            mrp_list_init(&ml->timers);
+            mrp_list_init(&ml->deferred);
+            mrp_list_init(&ml->inactive_deferred);
+            mrp_list_init(&ml->sighandlers);
+            mrp_list_init(&ml->wakeups);
+            mrp_list_init(&ml->deleted);
+            mrp_list_init(&ml->subloops);
+            mrp_list_init(&ml->busses);
+            mrp_list_init(&ml->eventq);
+
+            ml->eventd = mrp_add_deferred(ml, pump_events, ml);
+            if (ml->eventd == NULL)
+                goto fail;
+            mrp_disable_deferred(ml->eventd);
+
+            if (!setup_sighandlers(ml))
+                goto fail;
+        }
+        else {
+        fail:
+            close(ml->epollfd);
+            fdtbl_destroy(ml->fdtbl);
+            mrp_free(ml);
+            ml = NULL;
+        }
+    }
+
+
+
+    return ml;
+}
+
+
+void mrp_mainloop_destroy(mrp_mainloop_t *ml)
+{
+    if (ml != NULL) {
+        mrp_clear_superloop(ml);
+        purge_io_watches(ml);
+        purge_timers(ml);
+        purge_deferred(ml);
+        purge_sighandlers(ml);
+        purge_wakeups(ml);
+        purge_subloops(ml);
+        purge_deleted(ml);
+
+        close(ml->sigfd);
+        close(ml->epollfd);
+        fdtbl_destroy(ml->fdtbl);
+
+        mrp_free(ml->events);
+        mrp_free(ml);
+    }
+}
+
+
+static int prepare_subloop(mrp_subloop_t *sl)
+{
+    /*
+     * Notes:
+     *
+     *     If we have a relatively large number of file descriptors to
+     *     poll but typically only a small fraction of them has pending
+     *     events per mainloop iteration epoll has significant advantages
+     *     over poll. This is the main reason why our mainloop uses epoll.
+     *     However, there is a considerable amount of pain one needs to
+     *     go through to integrate an external poll-based (sub-)mainloop
+     *     (e.g. glib's GMainLoop) with an epoll-based mainloop. I mean,
+     *     just look at the code below !
+     *
+     *     If it eventually turns out that we typically only have a small
+     *     number of file descriptors while at the same time we practically
+     *     always need to pump GMainLoop, it is probably a good idea to
+     *     bite the bullet and change our mainloop to be poll-based as well.
+     *     But let's not go there yet...
+     */
+
+
+    struct epoll_event  evt;
+    struct pollfd      *fds, *pollfds;
+    int                 timeout;
+    int                 nfd, npollfd, n, i;
+    int                 nmatch;
+    int                 fd, idx;
+
+    MRP_UNUSED(dump_pollfds);
+
+    mrp_debug("preparing subloop %p", sl);
+
+    pollfds = sl->pollfds;
+    npollfd = sl->npollfd;
+
+    if (sl->cb->prepare(sl->user_data)) {
+        mrp_debug("subloop %p prepare reported ready, dispatching it", sl);
+        sl->cb->dispatch(sl->user_data);
+    }
+    sl->poll = FALSE;
+
+    nfd = npollfd;
+    fds = nfd ? mrp_allocz(nfd * sizeof(*fds)) : NULL;
+
+    MRP_ASSERT(nfd == 0 || fds != NULL, "failed to allocate pollfd's");
+
+    while ((n = sl->cb->query(sl->user_data, fds, nfd, &timeout)) > nfd) {
+        fds = mrp_reallocz(fds, nfd, n);
+        nfd = n;
+        MRP_ASSERT(fds != NULL, "failed to allocate pollfd's");
+    }
+    nfd = n;
+
+
+#if 0
+    printf("-------------------------\n");
+    dump_pollfds("old: ", sl->pollfds, sl->npollfd);
+    dump_pollfds("new: ", fds, nfd);
+    printf("-------------------------\n");
+#endif
+
+
+    /*
+     * skip over the identical portion of the old and new pollfd's
+     */
+
+    for (i = nmatch = 0; i < npollfd && i < n; i++, nmatch++) {
+        if (fds[i].fd     != pollfds[i].fd ||
+            fds[i].events != pollfds[i].events)
+            break;
+        else
+            fds[i].revents = pollfds[i].revents = 0;
+    }
+
+
+    if (nmatch == npollfd && npollfd == nfd) {
+        mrp_free(fds);
+        goto out;
+    }
+
+
+    /*
+     * replace file descriptors with the new set (remove old, add new)
+     */
+
+    for (i = 0; i < npollfd; i++) {
+        fd = pollfds[i].fd;
+        fdtbl_remove(sl->fdtbl, fd);
+        if (epoll_ctl(sl->epollfd, EPOLL_CTL_DEL, fd, &evt) < 0) {
+            if (errno != EBADF && errno != ENOENT)
+                mrp_log_error("Failed to delete subloop fd %d from epoll "
+                              "(%d: %s)", fd, errno, strerror(errno));
+        }
+    }
+
+    for (i = 0; i < nfd; i++) {
+        fd  = fds[i].fd;
+        idx = i + 1;
+
+        evt.events   = fds[i].events;
+        evt.data.u64 = 0;                /* init full union for valgrind... */
+        evt.data.fd  = fd;
+
+        if (fdtbl_insert(sl->fdtbl, fd, (void *)(ptrdiff_t)idx) == 0) {
+            if (epoll_ctl(sl->epollfd, EPOLL_CTL_ADD, fd, &evt) != 0) {
+                mrp_log_error("Failed to add subloop fd %d to epoll "
+                              "(%d: %s)", fd, errno, strerror(errno));
+            }
+        }
+        else {
+            mrp_log_error("Failed to add subloop fd %d to fd table "
+                          "(%d: %s)", fd, errno, strerror(errno));
+        }
+
+        fds[i].revents = 0;
+    }
+
+    mrp_free(sl->pollfds);
+    sl->pollfds = fds;
+    sl->npollfd = nfd;
+
+
+    /*
+     * resize event buffer if needed
+     */
+
+    if (sl->nevent < nfd) {
+        sl->nevent = nfd;
+        sl->events = mrp_realloc(sl->events, sl->nevent * sizeof(*sl->events));
+
+        MRP_ASSERT(sl->events != NULL || sl->nevent == 0,
+                   "can't allocate epoll event buffer");
+    }
+
+ out:
+    mrp_debug("subloop %p: fds: %d, timeout: %d, poll: %s",
+              sl, sl->npollfd, timeout, sl->poll ? "TRUE" : "FALSE");
+
+    return timeout;
+}
+
+
+static int prepare_subloops(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_subloop_t   *sl;
+    int              ext_timeout, min_timeout;
+
+    min_timeout = INT_MAX;
+
+    mrp_list_foreach(&ml->subloops, p, n) {
+        sl = mrp_list_entry(p, typeof(*sl), hook);
+
+        if (!is_deleted(sl)) {
+            ext_timeout = prepare_subloop(sl);
+            min_timeout = MRP_MIN(min_timeout, ext_timeout);
+        }
+        else
+            mrp_debug("skipping deleted subloop %p", sl);
+    }
+
+    return min_timeout;
+}
+
+
+#if 0
+static inline void dump_timers(mrp_mainloop_t *ml)
+{
+    mrp_timer_t     *t;
+    mrp_list_hook_t *p, *n;
+    int              i;
+    mrp_timer_t     *next = NULL;
+
+    mrp_debug("timer dump:");
+    i = 0;
+    mrp_list_foreach(&ml->timers, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        mrp_debug("  #%d: %p, @%u, next %llu (%s)", i, t, t->msecs, t->expire,
+                  is_deleted(t) ? "DEAD" : "alive");
+
+        if (!is_deleted(t) && next == NULL)
+            next = t;
+
+        i++;
+    }
+
+    mrp_debug("next timer: %p", ml->next_timer);
+    mrp_debug("poll timer: %d", ml->poll_timeout);
+
+    if (next != NULL && ml->next_timer != NULL &&
+        !is_deleted(ml->next_timer) && next != ml->next_timer) {
+        mrp_debug("*** BUG ml->next_timer is not the nearest !!! ***");
+        if (getenv("__MURPHY_TIMER_CHECK_ABORT") != NULL)
+            abort();
+    }
+}
+#endif
+
+
+int mrp_mainloop_prepare(mrp_mainloop_t *ml)
+{
+    mrp_timer_t *next_timer;
+    int          timeout, ext_timeout;
+    uint64_t     now;
+
+    if (!mrp_list_empty(&ml->deferred)) {
+        timeout = 0;
+    }
+    else {
+        next_timer = ml->next_timer;
+
+        if (next_timer == NULL)
+            timeout = -1;
+        else {
+            now = time_now();
+            if (MRP_UNLIKELY(next_timer->expire <= now))
+                timeout = 0;
+            else
+                timeout = usecs_to_msecs(next_timer->expire - now);
+        }
+    }
+
+    ext_timeout = prepare_subloops(ml);
+
+    if (ext_timeout != -1 && timeout != -1)
+        ml->poll_timeout = MRP_MIN(timeout, ext_timeout);
+    else if (ext_timeout == -1)
+        ml->poll_timeout = timeout;
+    else
+        ml->poll_timeout = ext_timeout;
+
+    if (ml->nevent < ml->niowatch) {
+        ml->nevent = ml->niowatch;
+        ml->events = mrp_realloc(ml->events, ml->nevent * sizeof(*ml->events));
+
+        MRP_ASSERT(ml->events != NULL, "can't allocate epoll event buffer");
+    }
+
+    mrp_debug("mainloop %p prepared: %d I/O watches, timeout %d", ml,
+              ml->niowatch, ml->poll_timeout);
+
+    return TRUE;
+}
+
+
+static size_t poll_events(void *id, mrp_mainloop_t *ml, void **bufp)
+{
+    void *buf;
+    int   n;
+
+    if (MRP_UNLIKELY(id != ml->iow)) {
+        mrp_log_error("superloop polling with invalid I/O watch (%p != %p)",
+                      id, ml->iow);
+        *bufp = NULL;
+        return 0;
+    }
+
+    buf = mrp_allocz(ml->nevent * sizeof(ml->events[0]));
+
+    if (buf != NULL) {
+        n = epoll_wait(ml->epollfd, buf, ml->nevent, 0);
+
+        if (n < 0)
+            n = 0;
+    }
+    else
+        n = 0;
+
+    *bufp = buf;
+    return n * sizeof(ml->events[0]);
+}
+
+
+int mrp_mainloop_poll(mrp_mainloop_t *ml, int may_block)
+{
+    int n, timeout;
+
+    timeout = may_block && mrp_list_empty(&ml->deferred) ? ml->poll_timeout : 0;
+
+    if (ml->nevent > 0) {
+        if (ml->super_ops == NULL || ml->super_ops->poll_io == NULL) {
+            mrp_debug("polling %d descriptors with timeout %d",
+                      ml->nevent, timeout);
+
+            n = epoll_wait(ml->epollfd, ml->events, ml->nevent, timeout);
+
+            if (n < 0 && errno == EINTR)
+                n = 0;
+        }
+        else {
+            mrp_superloop_ops_t *super_ops  = ml->super_ops;
+            void                *super_data = ml->super_data;
+            void                *id         = ml->iow;
+            void                *buf        = ml->events;
+            size_t               size       = ml->nevent * sizeof(ml->events[0]);
+
+            size = super_ops->poll_io(super_data, id, buf, size);
+            n    = size / sizeof(ml->events[0]);
+
+            MRP_ASSERT(n * sizeof(ml->events[0]) == size,
+                       "superloop passed us a partial epoll_event");
+        }
+
+        mrp_debug("mainloop %p has %d/%d I/O events waiting", ml, n,
+                  ml->nevent);
+
+        ml->poll_result = n;
+    }
+    else {
+        /*
+         * Notes: Practically we should never branch here because
+         *     we always have at least ml->sigfd registered for epoll.
+         */
+        if (timeout > 0)
+            usleep(timeout * USECS_PER_MSEC);
+
+        ml->poll_result = 0;
+    }
+
+    return TRUE;
+}
+
+
+static int poll_subloop(mrp_subloop_t *sl)
+{
+    struct epoll_event *e;
+    struct pollfd      *pfd;
+    int                 fd, idx, n, i;
+
+    if (sl->poll) {
+        n = epoll_wait(sl->epollfd, sl->events, sl->nevent, 0);
+
+        if (n < 0 && errno == EINTR)
+            n = 0;
+
+        for (i = 0, e = sl->events; i < n; i++, e++) {
+            fd  = e->data.fd;
+            idx = ((int)(ptrdiff_t)fdtbl_lookup(sl->fdtbl, fd)) - 1;
+
+            if (0 <= idx && idx < sl->npollfd) {
+                pfd = sl->pollfds + idx;
+                pfd->revents = e->events;
+            }
+        }
+
+        mrp_debug("subloop %p has %d fds ready", sl, sl->npollfd);
+
+        return n;
+    }
+    else {
+        mrp_debug("subloop %p has poll flag off", sl);
+
+        return 0;
+    }
+}
+
+
+static void dispatch_wakeup(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t    *p, *n;
+    mrp_wakeup_t       *w;
+    mrp_wakeup_event_t  event;
+    uint64_t            now;
+
+    if (ml->poll_timeout == 0) {
+        mrp_debug("skipping wakeup callbacks (poll timeout was 0)");
+        return;
+    }
+
+    if (ml->poll_result == 0) {
+        mrp_debug("woken up by timeout");
+        event = MRP_WAKEUP_EVENT_TIMER;
+    }
+    else {
+        mrp_debug("woken up by I/O (or signal)");
+        event = MRP_WAKEUP_EVENT_IO;
+    }
+
+    now = time_now();
+
+    mrp_list_foreach(&ml->wakeups, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+
+        if (!(w->events & event))
+            continue;
+
+        if (!is_deleted(w)) {
+            mrp_debug("dispatching wakeup cb %p", w);
+            wakeup_cb(w, event, now);
+        }
+        else
+            mrp_debug("skipping deleted wakeup cb %p", w);
+
+        if (ml->quit)
+            break;
+    }
+}
+
+
+static void dispatch_deferred(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_deferred_t  *d;
+
+    mrp_list_foreach(&ml->deferred, p, n) {
+        d = mrp_list_entry(p, typeof(*d), hook);
+
+        if (!is_deleted(d) && !d->inactive) {
+            mrp_debug("dispatching active deferred cb %p", d);
+            d->cb(d, d->user_data);
+        }
+        else
+            mrp_debug("skipping %s deferred cb %p",
+                      is_deleted(d) ? "deleted" : "inactive", d);
+
+        if (!is_deleted(d) && d->inactive)
+            disable_deferred(d);
+
+        if (ml->quit)
+            break;
+    }
+}
+
+
+static void dispatch_timers(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_timer_t     *t;
+    uint64_t         now;
+
+    now = time_now();
+
+    mrp_list_foreach(&ml->timers, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        if (!is_deleted(t)) {
+            if (t->expire <= now) {
+                mrp_debug("dispatching expired timer %p", t);
+
+                t->cb(t, t->user_data);
+
+                if (!is_deleted(t))
+                    rearm_timer(t);
+            }
+            else
+                break;
+        }
+        else
+            mrp_debug("skipping deleted timer %p", t);
+
+        if (ml->quit)
+            break;
+    }
+}
+
+
+static void dispatch_subloops(mrp_mainloop_t *ml)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_subloop_t   *sl;
+
+    mrp_list_foreach(&ml->subloops, p, n) {
+        sl = mrp_list_entry(p, typeof(*sl), hook);
+
+        if (!is_deleted(sl)) {
+            poll_subloop(sl);
+
+            if (sl->cb->check(sl->user_data, sl->pollfds,
+                              sl->npollfd)) {
+                mrp_debug("dispatching subloop %p", sl);
+                sl->cb->dispatch(sl->user_data);
+            }
+            else
+                mrp_debug("skipping subloop %p, check said no", sl);
+        }
+    }
+}
+
+
+static void dispatch_slaves(mrp_io_watch_t *w, struct epoll_event *e)
+{
+    mrp_io_watch_t  *s;
+    mrp_list_hook_t *p, *n;
+    mrp_io_event_t   events;
+
+    events = e->events & ~(MRP_IO_EVENT_INOUT & w->events);
+
+    mrp_list_foreach(&w->slave, p, n) {
+        if (events == MRP_IO_EVENT_NONE)
+            break;
+
+        s = mrp_list_entry(p, typeof(*s), slave);
+
+        if (!is_deleted(s)) {
+            mrp_debug("dispatching slave I/O watch %p (fd %d)", s, s->fd);
+            s->cb(s, s->fd, events, s->user_data);
+        }
+        else
+            mrp_debug("skipping slave I/O watch %p (fd %d)", s, s->fd);
+
+        events &= ~(MRP_IO_EVENT_INOUT & s->events);
+    }
+}
+
+
+static void dispatch_poll_events(mrp_mainloop_t *ml)
+{
+    struct epoll_event *e;
+    mrp_io_watch_t     *w, *tblw;
+    int                 i, fd;
+
+    for (i = 0, e = ml->events; i < ml->poll_result; i++, e++) {
+        fd = e->data.fd;
+        w  = fdtbl_lookup(ml->fdtbl, fd);
+
+        if (w == NULL) {
+            mrp_debug("ignoring event for deleted fd %d", fd);
+            continue;
+        }
+
+        if (!is_deleted(w)) {
+            mrp_debug("dispatching I/O watch %p (fd %d)", w, fd);
+            w->cb(w, w->fd, e->events, w->user_data);
+        }
+        else
+            mrp_debug("skipping deleted I/O watch %p (fd %d)", w, fd);
+
+        if (!mrp_list_empty(&w->slave))
+            dispatch_slaves(w, e);
+
+        if (e->events & EPOLLRDHUP) {
+            tblw = fdtbl_lookup(ml->fdtbl, w->fd);
+
+            if (tblw == w) {
+                mrp_debug("forcibly stop polling fd %d for watch %p", w->fd, w);
+                epoll_del(w);
+            }
+            else if (tblw != NULL)
+                mrp_debug("don't stop polling reused fd %d of watch %p",
+                          w->fd, w);
+        }
+        else {
+            if ((e->events & EPOLLHUP) && !is_deleted(w)) {
+                /*
+                 * Notes:
+                 *
+                 *    If the user does not react to EPOLLHUPs delivered
+                 *    we stop monitoring the fd to avoid sitting in an
+                 *    infinite busy loop just delivering more EPOLLHUP
+                 *    notifications...
+                 */
+
+                if (w->wrhup++ > 5) {
+                    tblw = fdtbl_lookup(ml->fdtbl, w->fd);
+
+                    if (tblw == w) {
+                        mrp_debug("forcibly stop polling fd %d for watch %p",
+                                  w->fd, w);
+                        epoll_del(w);
+                    }
+                    else if (tblw != NULL)
+                        mrp_debug("don't stop polling reused fd %d of watch %p",
+                                  w->fd, w);
+                }
+            }
+        }
+
+        if (ml->quit)
+            break;
+    }
+
+    if (ml->quit)
+        return;
+
+    dispatch_subloops(ml);
+
+    mrp_debug("done dispatching poll events");
+}
+
+
+int mrp_mainloop_dispatch(mrp_mainloop_t *ml)
+{
+    dispatch_wakeup(ml);
+
+    if (ml->quit)
+        goto quit;
+
+    dispatch_deferred(ml);
+
+    if (ml->quit)
+        goto quit;
+
+    dispatch_timers(ml);
+
+    if (ml->quit)
+        goto quit;
+
+    dispatch_poll_events(ml);
+
+ quit:
+    purge_deleted(ml);
+
+    return !ml->quit;
+}
+
+
+int mrp_mainloop_iterate(mrp_mainloop_t *ml)
+{
+    return
+        mrp_mainloop_prepare(ml) &&
+        mrp_mainloop_poll(ml, TRUE) &&
+        mrp_mainloop_dispatch(ml) &&
+        !ml->quit;
+}
+
+
+int mrp_mainloop_run(mrp_mainloop_t *ml)
+{
+    while (mrp_mainloop_iterate(ml))
+        ;
+
+    return ml->exit_code;
+}
+
+
+void mrp_mainloop_quit(mrp_mainloop_t *ml, int exit_code)
+{
+    ml->exit_code = exit_code;
+    ml->quit      = TRUE;
+}
+
+
+/*
+ * debugging routines
+ */
+
+
+static void dump_pollfds(const char *prefix, struct pollfd *fds, int nfd)
+{
+    char *t;
+    int   i;
+
+    printf("%s (%d): ", prefix, nfd);
+    for (i = 0, t = ""; i < nfd; i++, t = ", ")
+        printf("%s%d/0x%x", t, fds[i].fd, fds[i].events);
+    printf("\n");
+}
+
+
+/*
+ * event bus and events
+ */
+
+static inline void *ref_event_data(void *data, int format)
+{
+    switch (format & MRP_EVENT_FORMAT_MASK) {
+    case MRP_EVENT_FORMAT_JSON:
+        return mrp_json_ref((mrp_json_t *)data);
+    case MRP_EVENT_FORMAT_MSG:
+        return mrp_msg_ref((mrp_msg_t *)data);
+    default:
+        return data;
+    }
+}
+
+
+static inline void unref_event_data(void *data, int format)
+{
+    switch (format & MRP_EVENT_FORMAT_MASK) {
+    case MRP_EVENT_FORMAT_JSON:
+        mrp_json_unref((mrp_json_t *)data);
+        break;
+    case MRP_EVENT_FORMAT_MSG:
+        mrp_msg_unref((mrp_msg_t *)data);
+        break;
+    default:
+        break;
+    }
+}
+
+
+mrp_event_bus_t *mrp_event_bus_get(mrp_mainloop_t *ml, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_event_bus_t *bus;
+
+    if (name == NULL || !strcmp(name, MRP_GLOBAL_BUS_NAME))
+        return MRP_GLOBAL_BUS;
+
+    mrp_list_foreach(&ml->busses, p, n) {
+        bus = mrp_list_entry(p, typeof(*bus), hook);
+
+        if (!strcmp(bus->name, name))
+            return bus;
+    }
+
+    bus = mrp_allocz(sizeof(*bus));
+
+    if (bus == NULL)
+        return NULL;
+
+    bus->name = mrp_strdup(name);
+
+    if (bus->name == NULL) {
+        mrp_free(bus);
+        return NULL;
+    }
+
+    mrp_list_init(&bus->hook);
+    mrp_list_init(&bus->watches);
+    bus->ml = ml;
+
+    mrp_list_append(&ml->busses, &bus->hook);
+
+    return bus;
+}
+
+
+uint32_t mrp_event_id(const char *name)
+{
+    mrp_event_def_t *e;
+    int              i;
+
+    if (events != NULL)
+        for (i = 0, e = events; i < nevent; i++, e++)
+            if (!strcmp(e->name, name))
+                return e->id;
+
+    if (!mrp_reallocz(events, nevent, nevent + 1))
+        return 0;
+
+    e = events + nevent;
+
+    e->id   = nevent;
+    e->name = mrp_strdup(name);
+
+    if (e->name == NULL) {
+        mrp_reallocz(events, nevent + 1, nevent);
+        return 0;
+    }
+
+    nevent++;
+
+    return e->id;
+}
+
+
+const char *mrp_event_name(uint32_t id)
+{
+    if ((int)id < nevent)
+        return events[id].name;
+    else
+        return MRP_EVENT_UNKNOWN_NAME;
+}
+
+
+char *mrp_event_dump_mask(mrp_event_mask_t *mask, char *buf, size_t size)
+{
+    char *p, *t;
+    int   l, n, id;
+
+    p = buf;
+    l = (int)size;
+    t = "";
+
+    MRP_MASK_FOREACH_SET(mask, id, 1) {
+        n = snprintf(p, l, "%s%s", t, mrp_event_name(id));
+        t = "|";
+
+        if (n >= l)
+            return "<insufficient mask dump buffer>";
+
+        p += n;
+        l -= n;
+    }
+
+    return buf;
+}
+
+
+mrp_event_watch_t *mrp_event_add_watch(mrp_event_bus_t *bus, uint32_t id,
+                                       mrp_event_watch_cb_t cb, void *user_data)
+{
+    mrp_list_hook_t   *watches = bus ? &bus->watches : &ewatches;
+    mrp_event_watch_t *w;
+
+    w = mrp_allocz(sizeof(*w));
+
+    if (w == NULL)
+        return NULL;
+
+    mrp_list_init(&w->hook);
+    mrp_mask_init(&w->mask);
+    w->bus       = bus;
+    w->cb        = cb;
+    w->user_data = user_data;
+
+    if (!mrp_mask_set(&w->mask, id)) {
+        mrp_free(w);
+        return NULL;
+    }
+
+    mrp_list_append(watches, &w->hook);
+
+    mrp_debug("added event watch %p for event %d (%s) on bus %s", w, id,
+              mrp_event_name(id), bus ? bus->name : MRP_GLOBAL_BUS_NAME);
+
+    return w;
+}
+
+
+mrp_event_watch_t *mrp_event_add_watch_mask(mrp_event_bus_t *bus,
+                                            mrp_event_mask_t *mask,
+                                            mrp_event_watch_cb_t cb,
+                                            void *user_data)
+{
+    mrp_list_hook_t   *watches = bus ? &bus->watches : &ewatches;
+    mrp_event_watch_t *w;
+    char               events[512];
+
+    w = mrp_allocz(sizeof(*w));
+
+    if (w == NULL)
+        return NULL;
+
+    mrp_list_init(&w->hook);
+    mrp_mask_init(&w->mask);
+    w->bus       = bus;
+    w->cb        = cb;
+    w->user_data = user_data;
+
+    if (!mrp_mask_copy(&w->mask, mask)) {
+        mrp_free(w);
+        return NULL;
+    }
+
+    mrp_list_append(watches, &w->hook);
+
+    mrp_debug("added event watch %p for events <%s> on bus %s", w,
+              mrp_event_dump_mask(&w->mask, events, sizeof(events)),
+              bus ? bus->name : MRP_GLOBAL_BUS_NAME);
+
+    return w;
+}
+
+
+void mrp_event_del_watch(mrp_event_watch_t *w)
+{
+    if (w == NULL)
+        return;
+
+    if (w->bus != NULL && w->bus->busy) {
+        w->dead = TRUE;
+        w->bus->dead++;
+        return;
+    }
+
+    mrp_list_delete(&w->hook);
+    mrp_mask_reset(&w->mask);
+    mrp_free(w);
+}
+
+
+void bus_purge_dead(mrp_event_bus_t *bus)
+{
+    mrp_event_watch_t *w;
+    mrp_list_hook_t   *p, *n;
+
+    if (!bus->dead)
+        return;
+
+    mrp_list_foreach(&bus->watches, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+
+        if (!w->dead)
+            continue;
+
+        mrp_list_delete(&w->hook);
+        mrp_mask_reset(&w->mask);
+        mrp_free(w);
+    }
+
+    bus->dead = 0;
+}
+
+
+static int queue_event(mrp_event_bus_t *bus, uint32_t id, void *data,
+                       mrp_event_flag_t flags)
+{
+    pending_event_t *e;
+
+    e = mrp_allocz(sizeof(*e));
+
+    if (e == NULL)
+        return -1;
+
+    mrp_list_init(&e->hook);
+    e->bus    = bus;
+    e->id     = id;
+    e->format = flags & MRP_EVENT_FORMAT_MASK;
+    e->data   = ref_event_data(data, e->format);
+    mrp_list_append(&bus->ml->eventq, &e->hook);
+
+    mrp_enable_deferred(bus->ml->eventd);
+
+    return 0;
+}
+
+
+static int emit_event(mrp_event_bus_t *bus, uint32_t id, void *data,
+                      mrp_event_flag_t flags)
+{
+    mrp_list_hook_t   *watches;
+    mrp_event_watch_t *w;
+    mrp_list_hook_t   *p, *n;
+
+    if (bus)
+        watches = &bus->watches;
+    else {
+        if (!(flags & MRP_EVENT_SYNCHRONOUS)) {
+            errno = EINVAL;
+            return -1;
+        }
+        watches = &ewatches;
+    }
+
+    if (bus)
+        bus->busy++;
+
+    mrp_debug("emitting event 0x%x (%s) on bus <%s>", id, mrp_event_name(id),
+              bus ? bus->name : MRP_GLOBAL_BUS_NAME);
+
+    mrp_list_foreach(watches, p, n) {
+        w = mrp_list_entry(p, typeof(*w), hook);
+
+        if (w->dead)
+            continue;
+
+        if (mrp_mask_test(&w->mask, id))
+            w->cb(w, id, flags & MRP_EVENT_FORMAT_MASK, data, w->user_data);
+    }
+
+    if (bus) {
+        bus->busy--;
+
+        if (!bus->busy)
+            bus_purge_dead(bus);
+    }
+
+    return 0;
+}
+
+
+static void pump_events(mrp_deferred_t *d, void *user_data)
+{
+    mrp_mainloop_t  *ml = (mrp_mainloop_t *)user_data;
+    mrp_list_hook_t *p, *n;
+    pending_event_t *e;
+
+ pump:
+    mrp_list_foreach(&ml->eventq, p, n) {
+        e = mrp_list_entry(p, typeof(*e), hook);
+
+        emit_event(e->bus, e->id, e->data, e->format);
+
+        mrp_list_delete(&e->hook);
+        unref_event_data(e->data, e->format);
+
+        mrp_free(e);
+    }
+
+    if (!mrp_list_empty(&ml->eventq))
+        goto pump;
+
+    mrp_disable_deferred(d);
+}
+
+
+int mrp_emit_event(mrp_event_bus_t *bus, uint32_t id, mrp_event_flag_t flags,
+                   void *data)
+{
+    int status;
+
+    if (flags & MRP_EVENT_SYNCHRONOUS) {
+        ref_event_data(data, flags);
+        status = emit_event(bus, id, data, flags);
+        unref_event_data(data, flags);
+
+        return status;
+    }
+    else {
+        if (bus != NULL)
+            return queue_event(bus, id, data, flags);
+
+        errno = EOPNOTSUPP;
+        return -1;
+    }
+}
+
+
+int _mrp_event_emit_msg(mrp_event_bus_t *bus, uint32_t id,
+                        mrp_event_flag_t flags, ...)
+{
+    mrp_msg_t *msg;
+    uint16_t   tag;
+    va_list    ap;
+    int        status;
+
+    va_start(ap, flags);
+    tag = va_arg(ap, unsigned int);
+    msg = tag ? mrp_msg_createv(tag, ap) : NULL;
+    va_end(ap);
+
+    flags &= ~MRP_EVENT_FORMAT_MASK;
+    status = mrp_emit_event(bus, id, flags | MRP_EVENT_FORMAT_MSG, msg);
+    mrp_msg_unref(msg);
+
+    return status;
+}
+
+
+MRP_INIT static void init_events(void)
+{
+    MRP_ASSERT(mrp_event_id(MRP_EVENT_UNKNOWN_NAME) == MRP_EVENT_UNKNOWN,
+               "reserved id 0x%x for builtin event <%s> already taken",
+               MRP_EVENT_UNKNOWN, MRP_EVENT_UNKNOWN_NAME);
+}
diff --git a/src/common/mainloop.h b/src/common/mainloop.h
new file mode 100644 (file)
index 0000000..4e7eee2
--- /dev/null
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_MAINLOOP_H__
+#define __MURPHY_MAINLOOP_H__
+
+#include <signal.h>
+#include <stdint.h>
+#include <sys/poll.h>
+#include <sys/epoll.h>
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+typedef struct mrp_mainloop_s mrp_mainloop_t;
+
+/*
+ * I/O watches
+ */
+
+/** I/O events */
+typedef enum {
+    MRP_IO_EVENT_NONE  = 0x0,
+    MRP_IO_EVENT_IN    = EPOLLIN,
+    MRP_IO_EVENT_PRI   = EPOLLPRI,
+    MRP_IO_EVENT_OUT   = EPOLLOUT,
+    MRP_IO_EVENT_RDHUP = EPOLLRDHUP,
+    MRP_IO_EVENT_WRHUP = EPOLLHUP,
+    MRP_IO_EVENT_HUP   = EPOLLRDHUP|EPOLLHUP,
+    MRP_IO_EVENT_ERR   = EPOLLERR,
+    MRP_IO_EVENT_INOUT = EPOLLIN|EPOLLOUT,
+    MRP_IO_EVENT_ALL   = EPOLLIN|EPOLLPRI|EPOLLOUT|EPOLLRDHUP|EPOLLERR,
+    /* event trigger modes */
+    MRP_IO_TRIGGER_LEVEL = 0x1U << 25,
+    MRP_IO_TRIGGER_EDGE  = EPOLLET,
+    MRP_IO_TRIGGER_MASK  = MRP_IO_TRIGGER_LEVEL|MRP_IO_TRIGGER_EDGE
+} mrp_io_event_t;
+
+typedef struct mrp_io_watch_s mrp_io_watch_t;
+
+/** I/O watch notification callback type. */
+typedef void (*mrp_io_watch_cb_t)(mrp_io_watch_t *w, int fd,
+                                  mrp_io_event_t events, void *user_data);
+/** Register a new file descriptor to watch. */
+mrp_io_watch_t *mrp_add_io_watch(mrp_mainloop_t *ml, int fd,
+                                 mrp_io_event_t events,
+                                 mrp_io_watch_cb_t cb, void *user_data);
+/** Unregister an I/O watch. */
+void mrp_del_io_watch(mrp_io_watch_t *watch);
+
+/** Get the mainloop of an I/O watch. */
+mrp_mainloop_t *mrp_get_io_watch_mainloop(mrp_io_watch_t *watch);
+
+/** Set the default event trigger mode for the given mainloop. */
+int mrp_set_io_event_mode(mrp_mainloop_t *ml, mrp_io_event_t mode);
+
+/** Get the default event trigger mode of the given mainloop. */
+mrp_io_event_t mrp_get_io_event_mode(mrp_mainloop_t *ml);
+
+/*
+ * timers
+ */
+
+typedef struct mrp_timer_s mrp_timer_t;
+
+/** Timer notification callback type. */
+typedef void (*mrp_timer_cb_t)(mrp_timer_t *t, void *user_data);
+
+/** Add a new timer. */
+mrp_timer_t *mrp_add_timer(mrp_mainloop_t *ml, unsigned int msecs,
+                           mrp_timer_cb_t cb, void *user_data);
+/** Modify the timeout or rearm the given timer. */
+#define MRP_TIMER_RESTART (unsigned int)-1
+void mrp_mod_timer(mrp_timer_t *t, unsigned int msecs);
+
+/** Delete a timer. */
+void mrp_del_timer(mrp_timer_t *t);
+
+/** Get the mainloop of a timer. */
+mrp_mainloop_t *mrp_get_timer_mainloop(mrp_timer_t *t);
+
+
+/*
+ * deferred callbacks
+ */
+
+typedef struct mrp_deferred_s mrp_deferred_t;
+
+/** Deferred callback notification callback type. */
+typedef void (*mrp_deferred_cb_t)(mrp_deferred_t *d, void *user_data);
+
+/** Add a deferred callback. */
+mrp_deferred_t *mrp_add_deferred(mrp_mainloop_t *ml, mrp_deferred_cb_t cb,
+                                 void *user_data);
+/** Remove a deferred callback. */
+void mrp_del_deferred(mrp_deferred_t *d);
+
+/** Disable a deferred callback. */
+void mrp_disable_deferred(mrp_deferred_t *d);
+
+/** Enable a deferred callback. */
+void mrp_enable_deferred(mrp_deferred_t *d);
+
+/** Get the mainloop of a deferred callback. */
+mrp_mainloop_t *mrp_get_deferred_mainloop(mrp_deferred_t *d);
+
+
+/*
+ * signals
+ */
+
+typedef struct mrp_sighandler_s mrp_sighandler_t;
+
+/* Signal handler callback type. */
+typedef void (*mrp_sighandler_cb_t)(mrp_sighandler_t *h, int signum,
+                                    void *user_data);
+/** Register a signal handler. */
+mrp_sighandler_t *mrp_add_sighandler(mrp_mainloop_t *ml, int signum,
+                                     mrp_sighandler_cb_t cb, void *user_data);
+/** Unregister a signal handler. */
+void mrp_del_sighandler(mrp_sighandler_t *h);
+
+/** Get the mainloop of a signal handler. */
+mrp_mainloop_t *mrp_get_sighandler_mainloop(mrp_sighandler_t *h);
+
+
+/*
+ * wakeup callbacks
+ */
+
+/** I/O events */
+typedef enum {
+    MRP_WAKEUP_EVENT_NONE  = 0x0,        /* no event */
+    MRP_WAKEUP_EVENT_TIMER = 0x1,        /* woken up by timeout */
+    MRP_WAKEUP_EVENT_IO    = 0x2,        /* woken up by I/O (or signal) */
+    MRP_WAKEUP_EVENT_ANY   = 0x3,        /* mask of all selectable events */
+    MRP_WAKEUP_EVENT_LIMIT = 0x4,        /* woken up by forced trigger */
+} mrp_wakeup_event_t;
+
+typedef struct mrp_wakeup_s mrp_wakeup_t;
+
+/** Wakeup callback notification type. */
+typedef void (*mrp_wakeup_cb_t)(mrp_wakeup_t *w, mrp_wakeup_event_t event,
+                                void *user_data);
+
+/** Add a wakeup callback for the specified events. lpf_msecs and
+ *  force_msecs specifiy two limiting intervals in milliseconds.
+ *  lpf_msecs is a low-pass filtering interval. It is guaranteed that
+ *  the wakeup callback will not be invoked more ofter than this.
+ *  force_msecs is a forced trigger interval. It is guaranteed that
+ *  the wakeup callback will be triggered at least this often. You can
+ *  MRP_WAKEUP_NOLIMIT to omit either or both limiting intervals. */
+#define MRP_WAKEUP_NOLIMIT ((unsigned int)0)
+mrp_wakeup_t *mrp_add_wakeup(mrp_mainloop_t *ml, mrp_wakeup_event_t events,
+                             unsigned int lpf_msecs, unsigned int force_msecs,
+                             mrp_wakeup_cb_t cb, void *user_data);
+
+/** Remove a wakeup callback. */
+void mrp_del_wakeup(mrp_wakeup_t *w);
+
+/** Get the mainloop of a wakeup callback. */
+mrp_mainloop_t *mrp_get_wakeup_mainloop(mrp_wakeup_t *w);
+
+
+/*
+ * subloops - external mainloops pumped by this mainloop
+ */
+
+typedef struct mrp_subloop_s mrp_subloop_t;
+
+typedef struct {
+    int  (*prepare)(void *user_data);
+    int  (*query)(void *user_data, struct pollfd *fds, int nfd, int *timeout);
+    int  (*check)(void *user_data, struct pollfd *fds, int nfd);
+    void (*dispatch)(void *user_data);
+} mrp_subloop_ops_t;
+
+
+/** Register an external mainloop to be pumped by the given mainloop. */
+mrp_subloop_t *mrp_add_subloop(mrp_mainloop_t *ml, mrp_subloop_ops_t *ops,
+                               void *user_data);
+
+/** Stop pumping a registered external mainloop. */
+void mrp_del_subloop(mrp_subloop_t *sl);
+
+
+/*
+ * superloops - external mainloop to pump murphy mainloops
+ */
+
+typedef struct {
+    void *(*add_io)(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data);
+    void (*del_io)(void *glue_data, void *id);
+
+    void *(*add_timer)(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+    void (*del_timer)(void *glue_data, void *id);
+    void (*mod_timer)(void *glue_data, void *id, unsigned int msecs);
+
+    void *(*add_defer)(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+    void  (*del_defer)(void *glue_data, void *id);
+    void  (*mod_defer)(void *glue_data, void *id, int enabled);
+    void  (*unregister)(void *glue_data);
+
+    /*
+     * Notes:
+     *
+     *     This is a band-aid attempt to get our mainloop run under the
+     *     threaded event loop of xwalk which has strict limitations about
+     *     what (type of) thread can access which functionality of the
+     *     event loop. In particular, the I/O watch equivalent is limited
+     *     for the I/O thread. In the current media element resource infra
+     *     integration code of xwalk, the related code is run in the UI
+     *     thread. This makes interacting from there with the daemon using
+     *     the stock resource libraries (in particular, pumping the mainloop)
+     *     non-straightforward.
+     *
+     *     The superloop glue code is supposed to use poll_events to trigger
+     *     a nonblocking epoll_wait on our epoll fd to retrieve (and cache)
+     *     pending epoll events from the kernel. poll_io is then used by us
+     *     to retrieve pending epoll events from the glue code. The glue code
+     *     needs to take care of any necessary locking to protect itself/us
+     *     from potentially concurrent invocations of poll_io and poll_events
+     *     from different threads.
+     *
+     *     The superloop abstraction now became really really ugly. poll_io
+     *     and poll_events implicitly assume/know that add_io/del_io is only
+     *     used for adding a single superloop I/O watch for our epoll fd.
+     *     The I/O watch id in the functions below is passed around only for
+     *     doublechecing this. Probably we should change the I/O watch part
+     *     of the superloop API to be actually less generic and only usable
+     *     for the epoll fd to avoid further confusion.
+     */
+    size_t (*poll_events)(void *id, mrp_mainloop_t *ml, void **events);
+    size_t (*poll_io)(void *glue_data, void *id, void *buf, size_t size);
+} mrp_superloop_ops_t;
+
+
+/** Set a superloop to pump the given mainloop. */
+int mrp_set_superloop(mrp_mainloop_t *ml, mrp_superloop_ops_t *ops,
+                      void *loop_data);
+
+/** Clear the superloop that pumps the given mainloop. */
+int mrp_clear_superloop(mrp_mainloop_t *ml);
+
+/** Unregister a mainloop from its superloop if it has one. */
+int mrp_mainloop_unregister(mrp_mainloop_t *ml);
+
+/*
+ * mainloop
+ */
+
+/** Create a new mainloop. */
+mrp_mainloop_t *mrp_mainloop_create(void);
+
+/** Destroy an existing mainloop, free all I/O watches, timers, etc. */
+void mrp_mainloop_destroy(mrp_mainloop_t *ml);
+
+/** Keep iterating the mainloop until mrp_mainloop_quit is called. */
+int mrp_mainloop_run(mrp_mainloop_t *ml);
+
+/** Prepare the mainloop for polling. */
+int mrp_mainloop_prepare(mrp_mainloop_t *ml);
+
+/** Poll the mainloop. */
+int mrp_mainloop_poll(mrp_mainloop_t *ml, int may_block);
+
+/** Dispatch pending events of the mainloop. */
+int mrp_mainloop_dispatch(mrp_mainloop_t *ml);
+
+/** Run a single prepare-poll-dispatch cycle of the mainloop. */
+int mrp_mainloop_iterate(mrp_mainloop_t *ml);
+
+/** Quit the mainloop. */
+void mrp_mainloop_quit(mrp_mainloop_t *ml, int exit_code);
+
+/*
+ * event bus and and events
+ */
+
+#include <murphy/common/mask.h>
+#include <murphy/common/msg.h>
+
+#define MRP_GLOBAL_BUS      NULL         /* global synchronous bus */
+#define MRP_GLOBAL_BUS_NAME "global"     /* global synchronous bus name */
+
+/** event flags */
+typedef enum {
+    MRP_EVENT_ASYNCHRONOUS  = 0x00,      /* deliver asynchronously */
+    MRP_EVENT_SYNCHRONOUS   = 0x01,      /* deliver synchronously */
+    MRP_EVENT_FORMAT_JSON   = 0x01 << 1, /* attached data is JSON */
+    MRP_EVENT_FORMAT_MSG    = 0x02 << 1, /* attached data is mrp_msg_t */
+    MRP_EVENT_FORMAT_CUSTOM = 0x03 << 1, /* attached data is of custom format */
+    MRP_EVENT_FORMAT_MASK   = 0x03 << 1,
+} mrp_event_flag_t;
+
+/** Reserved event id for unknown events. */
+#define MRP_EVENT_UNKNOWN 0
+
+/** Reserved name for unknown events. */
+#define MRP_EVENT_UNKNOWN_NAME "unknown"
+
+/**
+ * Event definition for registering event(name)s.
+ */
+typedef struct {
+    char     *name;                      /* event name */
+    uint32_t  id;                        /* event id assigned by murphy */
+} mrp_event_def_t;
+
+/** Opaque event watch type. */
+typedef struct mrp_event_watch_s mrp_event_watch_t;
+
+/** Event watch notification callback type. */
+typedef void (*mrp_event_watch_cb_t)(mrp_event_watch_t *w, uint32_t id,
+                                     int format, void *data, void *user_data);
+
+/** Opaque event bus type. */
+typedef struct mrp_event_bus_s mrp_event_bus_t;
+
+/** Event mask type (a bitmask of event ids). */
+typedef mrp_mask_t mrp_event_mask_t;
+
+/** Look up (or create) the event bus with the given name. */
+mrp_event_bus_t *mrp_event_bus_get(mrp_mainloop_t *ml, const char *name);
+#define mrp_event_bus_create mrp_event_bus_get
+
+/** Look up (or register) the id of the given event. */
+uint32_t mrp_event_id(const char *name);
+#define mrp_event_register mrp_event_id
+
+/** Look up the name of the given event. */
+const char *mrp_event_name(uint32_t id);
+
+/** Dump the names of the events set in mask. */
+char *mrp_event_dump_mask(mrp_event_mask_t *mask, char *buf, size_t size);
+
+/** Register an event watch for a single event on the given bus. */
+mrp_event_watch_t *mrp_event_add_watch(mrp_event_bus_t *bus, uint32_t id,
+                                       mrp_event_watch_cb_t cb, void *user_data);
+
+/** Register an event watch for a number of events on the given bus. */
+mrp_event_watch_t *mrp_event_add_watch_mask(mrp_event_bus_t *bus,
+                                            mrp_event_mask_t *mask,
+                                            mrp_event_watch_cb_t cb,
+                                            void *user_data);
+
+/** Unregister the given event watch. */
+void mrp_event_del_watch(mrp_event_watch_t *w);
+
+/** Emit the given event with the data attached on the given bus. */
+int mrp_event_emit(mrp_event_bus_t *bus, uint32_t id, mrp_event_flag_t flags,
+                   void *data);
+
+/** Convenience macro to emit an event with JSON data attached. */
+#define mrp_event_emit_json(bus, id, flags, data)                 \
+    mrp_event_emit((bus), (id), (flags) | MRP_EVENT_FORMAT_JSON, (data))
+
+/** Convenience macro to emit an event with mrp_msg_t data attached. */
+#define mrp_event_emit_msg(bus, id, flags, ...)                         \
+    _mrp_event_emit_msg((bus), (id), (flags), __VA_ARGS__, MRP_MSG_END)
+
+/** Emit an event with mrp_msg_t data attached. */
+int _mrp_event_emit_msg(mrp_event_bus_t *bus, uint32_t id,
+                        mrp_event_flag_t flags, ...);
+
+/** Convenience macro to emit an event with custom data attached. */
+#define mrp_event_emit_custom(bus, id, data, flags) \
+    mrp_event_emit((bus), (id), (data), (flags) | MRP_EVENT_FORMAT_CUSTOM)
+
+/** Convenience macros for autoregistering a table of events on startup. */
+#define MRP_EVENT(_name, _id) [_id] = { .name = _name, .id = 0 }
+#define MRP_REGISTER_EVENTS(_event_table, ...)                  \
+    static mrp_event_def_t _event_table[] = {                   \
+        __VA_ARGS__,                                            \
+        { .name = NULL }                                        \
+    };                                                          \
+                                                                \
+    MRP_INIT static void register_##_event_table(void)          \
+    {                                                           \
+        mrp_event_def_t *e;                                     \
+                                                                \
+        for (e = _event_table; e->name != NULL; e++) {          \
+            e->id = mrp_event_register(e->name);                \
+                                                                \
+            if (e->id != MRP_EVENT_UNKNOWN)                     \
+                mrp_log_info("Event '%s' registered as 0x%x.",  \
+                             e->name, e->id);                   \
+            else                                                \
+                mrp_log_error("Failed to register event '%s'.", \
+                              e->name);                         \
+        }                                                       \
+    }                                                           \
+    struct __mrp_allow_trailing_semicolon
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_MAINLOOP_H__ */
diff --git a/src/common/mask.h b/src/common/mask.h
new file mode 100644 (file)
index 0000000..12ed336
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_MASK_H__
+#define __MURPHY_MASK_H__
+
+#include <stdint.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+
+
+MRP_CDECL_BEGIN
+
+/** Type used to store bits in bitmasks. */
+typedef uint64_t _mask_t;
+
+
+/**
+ * trivial representation of a bitmask of arbitrary size
+ */
+
+typedef struct {
+    int          nbit;                   /* number of bits in this mask */
+    union {
+        _mask_t  bits;                   /* bits for nbit <= 64 */
+        _mask_t *bitp;                   /* bits for nbit >  64 */
+    };
+} mrp_mask_t;
+
+
+/** Macro to intialize a bitmask to empty. */
+#define MRP_MASK_EMPTY   { .nbit = 64, .bits = 0 }
+#define MRP_MASK_INIT(m) do { (m)->nbit = 64; (m)->bits = 0; } while (0)
+
+/** Macro to declare a bitmask variable and initialize it. */
+#define MRP_MASK(m)    mrp_mask_t m = MRP_MASK_EMPTY
+
+/* Various bit-fiddling macros. */
+#define MRP_MASK_BIT(bit)   (1ULL << (bit))
+#define MRP_MASK_UPTO(bit)  ((1ULL << (bit)) - 1)
+#define MRP_MASK_BELOW(bit) (MRP_MASK_UPTO(bit) >> 1)
+
+
+#define _BITS_PER_WORD ((int)(sizeof(_mask_t) * 8))
+#define _WORD(bit)     ((bit) /  _BITS_PER_WORD)
+#define _BIT(bit)      ((bit) & (_BITS_PER_WORD - 1))
+#define _MASK(bit)     (0x1ULL << (bit))
+
+
+/** Initialize the given mask. */
+static inline void mrp_mask_init(mrp_mask_t *m)
+{
+#ifndef __cplusplus
+    *m = (mrp_mask_t)MRP_MASK_EMPTY;
+#else
+    MRP_MASK_INIT(m);
+#endif
+}
+
+
+/** Reset the given mask. */
+static inline void mrp_mask_reset(mrp_mask_t *m)
+{
+    if (m->nbit > _BITS_PER_WORD)
+        mrp_free(m->bitp);
+
+    mrp_mask_init(m);
+}
+
+
+/** Ensure the given mask to accomodate the given number of bits. */
+static inline mrp_mask_t *mrp_mask_ensure(mrp_mask_t *m, int bits)
+{
+    _mask_t w;
+    int     o, n;
+
+    if (bits <= m->nbit)
+        return m;
+
+    if (m->nbit == _BITS_PER_WORD) {
+        w = m->bits;
+        n = (bits + _BITS_PER_WORD - 1) / _BITS_PER_WORD;
+
+        m->bitp = (_mask_t *)mrp_allocz(n * sizeof(*m->bitp));
+
+        if (m->bitp == NULL) {
+            m->bits = w;
+
+            return NULL;
+        }
+
+        m->bitp[0] = w;
+        m->nbit    = n * _BITS_PER_WORD;
+    }
+    else {
+        o = m->nbit / _BITS_PER_WORD;
+        n = (bits + _BITS_PER_WORD - 1) / _BITS_PER_WORD;
+
+        if (!mrp_reallocz(m->bitp, o, n + 1))
+            return NULL;
+
+        m->nbit = n * _BITS_PER_WORD;
+    }
+
+    return m;
+}
+
+
+/** Resize mask to accomodate the given number of bits, truncate if possible. */
+static inline mrp_mask_t *mrp_mask_trunc(mrp_mask_t *m, int bits)
+{
+    int       n;
+    uint64_t *bitp;
+
+    if (m->nbit <= bits)
+        return mrp_mask_ensure(m, bits);
+
+    n = (bits + _BITS_PER_WORD - 1) / _BITS_PER_WORD;
+
+    if (n == 1) {
+        bitp    = m->bitp;
+        m->bits = bitp[0];
+
+        mrp_free(bitp);
+    }
+    else
+        mrp_reallocz(m->bitp, m->nbit / _BITS_PER_WORD, n);
+
+    m->nbit = n * _BITS_PER_WORD;
+
+    return m;
+}
+
+
+/** Set the given bit in the mask. */
+static inline mrp_mask_t *mrp_mask_set(mrp_mask_t *m, int bit)
+{
+    int w, b;
+
+    if (!mrp_mask_ensure(m, bit))
+        return NULL;
+
+    b = _BIT(bit);
+
+    if (m->nbit == _BITS_PER_WORD)
+        m->bits |= _MASK(b);
+    else {
+        w = _WORD(bit);
+        m->bitp[w] |= _MASK(b);
+    }
+
+    return m;
+}
+
+
+/** Clear the given bit in the mask. */
+static inline mrp_mask_t *mrp_mask_clear(mrp_mask_t *m, int bit)
+{
+    int w, b;
+
+    if (bit >= m->nbit)
+        return m;
+
+    b = _BIT(bit);
+
+    if (m->nbit == _BITS_PER_WORD)
+        m->bits &= ~_MASK(b);
+    else {
+        w = _WORD(bit);
+        m->bitp[w] &= ~_MASK(b);
+    }
+
+    return m;
+}
+
+
+/** Test the given bit in the mask. */
+static inline int mrp_mask_test(mrp_mask_t *m, int bit)
+{
+    int w, b;
+
+    if (bit >= m->nbit)
+        return 0;
+
+    b = _BIT(bit);
+
+    if (m->nbit == _BITS_PER_WORD)
+        return !!(m->bits & _MASK(b));
+    else {
+        w = _WORD(bit);
+        return !!(m->bitp[w] & _MASK(b));
+    }
+}
+
+
+/** Copy the given mask, overwriting dst. */
+static inline mrp_mask_t *mrp_mask_copy(mrp_mask_t *dst, mrp_mask_t *src)
+{
+    mrp_mask_reset(dst);
+
+    dst->nbit = src->nbit;
+
+    if (src->nbit == _BITS_PER_WORD)
+        *dst = *src;
+    else {
+        dst->bitp = (_mask_t *)mrp_alloc(dst->nbit * _BITS_PER_WORD);
+
+        if (dst->bitp == NULL)
+            return NULL;
+
+        memcpy(dst->bitp, src->bitp, dst->nbit * _BITS_PER_WORD);
+    }
+
+    return dst;
+}
+
+
+/** Set all bits in src into dst (dst |= src). */
+static inline mrp_mask_t *mrp_mask_or(mrp_mask_t *dst, mrp_mask_t *src)
+{
+    int i, n;
+
+    if (!mrp_mask_ensure(dst, src->nbit))
+        return NULL;
+
+    if (src->nbit == _BITS_PER_WORD) {
+        if (dst->nbit == _BITS_PER_WORD)
+            dst->bits |= src->bits;
+        else
+            dst->bitp[0] |= src->bits;
+    }
+    else {
+        n = src->nbit / _BITS_PER_WORD;
+
+        for (i = 0; i < n; i++)
+            dst->bitp[i] |= src->bitp[i];
+    }
+
+    return dst;
+}
+
+
+/** Mask all bits in dst with the corresponding ones from src (dst &= src). */
+static inline mrp_mask_t *mrp_mask_and(mrp_mask_t *dst, mrp_mask_t *src)
+{
+    int i, n;
+
+    n = MRP_MIN(dst->nbit, src->nbit);
+    mrp_mask_trunc(dst, n);
+
+    n /= _BITS_PER_WORD;
+
+    if (dst->nbit == _BITS_PER_WORD) {
+        if (src->nbit == _BITS_PER_WORD)
+            dst->bits &= src->bits;
+        else
+            dst->bits &= src->bitp[0];
+    }
+    else {
+        for (i = 0; i < n; i++)
+            dst->bitp[i] &= src->bitp[i];
+    }
+
+    return dst;
+}
+
+
+/** Set all bits in src into dst (dst ^= src). */
+static inline mrp_mask_t *mrp_mask_xor(mrp_mask_t *dst, mrp_mask_t *src)
+{
+    int i, n;
+
+    if (!mrp_mask_ensure(dst, src->nbit))
+        return NULL;
+
+    if (src->nbit == _BITS_PER_WORD) {
+        if (dst->nbit == _BITS_PER_WORD)
+            dst->bits |= src->bits;
+        else
+            dst->bitp[0] |= src->bits;
+
+#if 0
+        /*
+         * Hmm... this would consider those bits in src which are not
+         * actually there but are in dst to be implicit 0's. However,
+         * I'm not sure if this really is a good idea... Needs a bit
+         * exposure to using this code to decide.
+         */
+
+        n = dst->nbit / _BITS_PER_WORD;
+        for (i = 1; i < n; i++)
+            dst->bitp[i] ^= 0;
+#endif
+    }
+    else {
+        n = src->nbit / _BITS_PER_WORD;
+        for (i = 0; i < n; i++)
+            dst->bitp[i] ^= src->bitp[i];
+
+#if 0
+        /*
+         * Hmm... ditto for this piece of code.
+         */
+
+        n = dst->nbit / _BITS_PER_WORD;
+        while (i < n)
+            dst->bitp[i] ^= 0;
+#endif
+    }
+
+    return dst;
+}
+
+
+/** Negate all bits in mask (~mask). */
+static inline mrp_mask_t *mrp_mask_neg(mrp_mask_t *m)
+{
+    int i, n;
+
+    if (m->nbit == _BITS_PER_WORD)
+        m->bits = ~m->bits;
+    else {
+        n = m->nbit / _BITS_PER_WORD;
+
+        for (i = 0; i < n; i++)
+            m->bitp[i] = ~m->bitp[i];
+    }
+
+    return m;
+}
+
+
+/** Find the first bit set (1-based indexing) in the given mask. */
+static inline int mrp_ffsll(_mask_t bits)
+{
+#ifdef __GNUC__
+    return __builtin_ffsll(bits);
+#else
+    _mask_t mask = 0xffffffff;
+    int     w, n;
+
+    if (!bits)
+        return 0;
+
+    n = 0;
+    w = _BITS_PER_WORD / 2;
+    while (w) {
+        if (!(bits & mask)) {
+            bits >>= w;
+            mask >>= w / 2;
+            n     += w;
+            w     /= 2;
+        }
+        else {
+            bits  &= mask;
+            mask >>= w / 2;
+            w     /= 2;
+        }
+    }
+
+    return n + 1;
+#endif
+}
+
+
+/** Get the first bit set starting at the given bit. */
+static inline int mrp_mask_next_set(mrp_mask_t *m, int bit)
+{
+    _mask_t wrd, clr;
+    int     w, b, n;
+
+    while (bit < m->nbit - 1) {
+        w = _WORD(bit);
+        b = _BIT(bit);
+
+        if (m->nbit == _BITS_PER_WORD)
+            wrd = m->bits;
+        else
+            wrd = m->bitp[w];
+
+        clr = ~(_MASK(b) - 1);
+        n   = mrp_ffsll(wrd & clr);
+
+        if (n > 0)
+            return w * _BITS_PER_WORD + n - 1;
+
+        bit = (bit + _BITS_PER_WORD) & ~(_BITS_PER_WORD - 1);
+    }
+
+    return -1;
+}
+
+
+/** Get the first bit cleared starting at the given bit. */
+static inline int mrp_mask_next_clear(mrp_mask_t *m, int bit)
+{
+    _mask_t wrd, clr;
+    int     w, b, n;
+
+    while (bit < m->nbit - 1) {
+        w = _WORD(bit);
+        b = _BIT(bit);
+
+        if (m->nbit == _BITS_PER_WORD)
+            wrd = m->bits;
+        else
+            wrd = m->bitp[w];
+
+        clr = _MASK(b) - 1;
+        n   = mrp_ffsll(~(wrd | clr));
+
+        if (n > 0)
+            return w * _BITS_PER_WORD + n - 1;
+
+        bit = (bit + _BITS_PER_WORD) & ~(_BITS_PER_WORD - 1);
+    }
+
+    return -1;
+}
+
+
+/** Loop through all bits set in a mask. */
+#define MRP_MASK_FOREACH_SET(m, bit, start)     \
+    for (bit = mrp_mask_next_set(m, start);     \
+         bit >= 0;                              \
+         bit = mrp_mask_next_set(m, bit + 1))
+
+
+/** Loop through all bits cleared in a mask. */
+#define MRP_MASK_FOREACH_CLEAR(m, bit, start)    \
+    for (bit = mrp_mask_next_clear(m, start);    \
+         bit >= 0;                               \
+         bit = mrp_mask_next_clear(m, bit + 1))
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_MASK_H__ */
diff --git a/src/common/mm.c b/src/common/mm.c
new file mode 100644 (file)
index 0000000..b461241
--- /dev/null
@@ -0,0 +1,1414 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <execinfo.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/hashtbl.h>
+
+
+#define DEFAULT_DEPTH   8                     /* default backtrace depth */
+#define MAX_DEPTH     128                     /* max. backtrace depth */
+
+/*
+ * memory allocator state
+ */
+
+typedef struct {
+    mrp_list_hook_t blocks;                   /* list of allocated blocks */
+    size_t          hdrsize;                  /* header size */
+    int             depth;                    /* backtrace depth */
+    uint32_t        cur_blocks;               /* currently allocated blocks */
+    uint32_t        max_blocks;               /* max allocated blocks */
+    uint64_t        cur_alloc;                /* currently allocated memory */
+    uint64_t        max_alloc;                /* max allocated memory */
+    int             poison;                   /* poisoning pattern */
+    size_t          chunk_size;               /* object pool chunk size */
+    mrp_mm_type_t   mode;                     /* passthru/debug mode */
+
+    void *(*alloc)(size_t size, const char *file, int line, const char *func);
+    void *(*realloc)(void *ptr, size_t size, const char *file,
+                     int line, const char *func);
+    int   (*memalign)(void **ptr, size_t align, size_t size,
+                      const char *file, int line, const char *func);
+    void  (*free)(void *ptr, const char *file, int line, const char *func);
+} mm_t;
+
+
+/*
+ * memory block
+ */
+
+typedef struct {
+    mrp_list_hook_t hook;                     /* to allocated blocks */
+    mrp_list_hook_t more;                     /* with the same backtrace */
+    const char     *file;                     /* file of immediate caller */
+    int             line;                     /* line of immediate caller */
+    const char     *func;                     /* name of immediate caller */
+    size_t          size;                     /* requested size */
+    void           *bt[];                     /* for accessing backtrace */
+} memblk_t;
+
+
+
+static mm_t __mm = {                          /* allocator state */
+    .hdrsize = MRP_ALIGN(MRP_OFFSET(memblk_t, bt[DEFAULT_DEPTH]),
+                         MRP_MM_ALIGN),
+    .depth   = DEFAULT_DEPTH,
+    .poison  = 0xdeadbeef,
+};
+
+
+static const char *get_config_key(const char *config, const char *key)
+{
+    const char *beg;
+    int         len;
+
+    if (config != NULL) {
+        len = strlen(key);
+
+        beg = config;
+        while (beg != NULL) {
+            beg = strstr(beg, key);
+
+            if (beg != NULL) {
+                if ((beg == config || beg[-1] == ':') &&
+                    (beg[len] == '=' || beg[len] == ':' || beg[len] == '\0'))
+                    return (beg[len] == '=' ? beg + len + 1 : "");
+                else
+                    beg++;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+static int32_t get_config_int32(const char *cfg, const char *key,
+                                int32_t defval)
+{
+    const char *v;
+    char       *end;
+    int         i;
+
+    v = get_config_key(cfg, key);
+
+    if (v != NULL) {
+        if (*v) {
+            i = strtol(v, &end, 10);
+
+            if (end && (!*end || *end == ':'))
+                return i;
+        }
+    }
+
+    return defval;
+}
+
+
+static uint32_t get_config_uint32(const char *cfg, const char *key,
+                                  uint32_t defval)
+{
+    const char *v;
+    char       *end;
+    int         i;
+
+    v = get_config_key(cfg, key);
+
+    if (v != NULL) {
+        if (*v) {
+            i = strtol(v, &end, 10);
+
+            if (end && (!*end || *end == ':'))
+                return i;
+        }
+    }
+
+    return defval;
+}
+
+
+static int get_config_bool(const char *config, const char *key, int defval)
+{
+    const char *v;
+
+    v = get_config_key(config, key);
+
+    if (v != NULL) {
+        if (*v) {
+
+            if ((!strncasecmp(v, "false", 5) && (v[5] == ':' || !v[5])) ||
+                (!strncasecmp(v, "true" , 4) && (v[4] == ':' || !v[4])))
+                return (v[0] == 't' || v[0] == 'T');
+        }
+        else if (*v == '\0')
+            return TRUE;
+    }
+
+    return defval;
+}
+
+
+static int get_config_string(const char *cfg, const char *key,
+                             const char *defval, char *buf, size_t size)
+{
+    const char *v;
+    char       *end;
+    int         len;
+
+    v = get_config_key(cfg, key);
+
+    if (v == NULL)
+        v = defval;
+
+    end = strchr(v, ':');
+
+    if (end != NULL)
+        len = end - v;
+    else
+        len = strlen(v);
+
+    len = snprintf(buf, size, "%*.*s", len, len, v);
+
+    if (len >= (int)size - 1)
+        buf[size - 1] = '\0';
+
+    return len;
+}
+
+
+
+MRP_INIT_AT(101) static void setup(void)
+{
+    char *config = getenv(MRP_MM_CONFIG_ENVVAR);
+
+    mrp_list_init(&__mm.blocks);
+
+    __mm.depth   = get_config_int32(config, "depth", DEFAULT_DEPTH);
+
+    if (__mm.depth > MAX_DEPTH)
+        __mm.depth = MAX_DEPTH;
+
+    __mm.hdrsize = MRP_ALIGN(MRP_OFFSET(memblk_t, bt[__mm.depth]),
+                             MRP_MM_ALIGN);
+
+    __mm.cur_blocks = 0;
+    __mm.max_blocks = 0;
+    __mm.cur_alloc  = 0;
+    __mm.max_alloc  = 0;
+
+    __mm.poison     = get_config_uint32(config, "poison", 0xdeadbeef);
+    __mm.chunk_size = sysconf(_SC_PAGESIZE) * 2;
+
+    if (config == NULL || !get_config_bool(config, "debug", FALSE))
+        mrp_mm_config(MRP_MM_PASSTHRU);
+    else
+        mrp_mm_config(MRP_MM_DEBUG);
+}
+
+
+static void __attribute__((destructor)) cleanup(void)
+{
+    if (__mm.mode == MRP_MM_DEBUG) {
+        mrp_mm_dump(stdout);
+        /*mrp_mm_check(stdout);*/
+    }
+}
+
+
+
+int32_t mrp_mm_config_int32(const char *key, int32_t defval)
+{
+    return get_config_int32(getenv(MRP_MM_CONFIG_ENVVAR), key, defval);
+}
+
+
+uint32_t mrp_mm_config_uint32(const char *key, uint32_t defval)
+{
+    return get_config_uint32(getenv(MRP_MM_CONFIG_ENVVAR), key, defval);
+}
+
+
+int mrp_mm_config_bool(const char *key, int defval)
+{
+    return get_config_bool(getenv(MRP_MM_CONFIG_ENVVAR), key, defval);
+}
+
+
+int mrp_mm_config_string(const char *key, const char *defval,
+                         char *buf, size_t size)
+{
+    return get_config_string(getenv(MRP_MM_CONFIG_ENVVAR), key, defval,
+                             buf, size);
+}
+
+
+/*
+ * memblk handling
+ */
+
+static memblk_t *memblk_alloc(size_t size, const char *file, int line,
+                              const char *func, void **bt)
+{
+    memblk_t *blk;
+
+    if (MRP_UNLIKELY(size == 0))
+        blk = NULL;
+    else {
+        if ((blk = malloc(__mm.hdrsize + size)) != NULL) {
+            mrp_list_init(&blk->hook);
+            mrp_list_init(&blk->more);
+            mrp_list_append(&__mm.blocks, &blk->hook);
+
+            blk->file = file;
+            blk->line = line;
+            blk->func = func;
+            blk->size = size;
+
+            memcpy(blk->bt, bt, __mm.depth * sizeof(*bt));
+
+            __mm.cur_blocks++;
+            __mm.cur_alloc += size;
+
+            __mm.max_blocks = MRP_MAX(__mm.max_blocks, __mm.cur_blocks);
+            __mm.max_alloc  = MRP_MAX(__mm.max_alloc , __mm.cur_alloc);
+        }
+    }
+
+    return blk;
+}
+
+
+static void memblk_free(memblk_t *blk, const char *file, int line,
+                        const char *func, void **bt)
+{
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+    MRP_UNUSED(bt);
+
+    if (blk != NULL) {
+        mrp_list_delete(&blk->hook);
+
+        __mm.cur_blocks--;
+        __mm.cur_alloc -= blk->size;
+
+        if (__mm.poison != 0)
+            memset(&blk->bt[__mm.depth], __mm.poison, blk->size);
+
+        free(blk);
+    }
+}
+
+
+static memblk_t *memblk_resize(memblk_t *blk, size_t size, const char *file,
+                               int line, const char *func, void **bt)
+{
+    memblk_t *resized;
+
+    if (blk != NULL) {
+        mrp_list_delete(&blk->hook);
+
+        if (size != 0) {
+            resized = realloc(blk, __mm.hdrsize + size);
+
+            if (resized != NULL) {
+                mrp_list_init(&resized->hook);
+
+                mrp_list_append(&__mm.blocks, &resized->hook);
+
+                __mm.cur_alloc -= resized->size;
+                __mm.cur_alloc += size;
+                __mm.max_alloc  = MRP_MAX(__mm.max_alloc, __mm.cur_alloc);
+
+                resized->file = file;
+                resized->line = line;
+                resized->func = func;
+
+                memcpy(resized->bt, bt, __mm.depth * sizeof(*bt));
+
+                resized->size = size;
+            }
+            else
+                mrp_list_append(&__mm.blocks, &blk->hook);
+        }
+        else {
+            resized = NULL;
+            memblk_free(blk, file, line, func, bt);
+        }
+
+        return resized;
+    }
+    else
+        return memblk_alloc(size, file, line, func, bt);
+}
+
+
+static inline void *memblk_to_ptr(memblk_t *blk)
+{
+    if (blk != NULL)
+        return (void *)&blk->bt[__mm.depth];
+    else
+        return NULL;
+}
+
+
+static inline memblk_t *ptr_to_memblk(void *ptr)
+{
+    /*
+     * XXX Hmm... maybe we should also add pre- and post-sentinels
+     * and check them here to have minimal protection/detection of
+     * trivial buffer overflow bugs when running in debug mode.
+     */
+
+    if (ptr != NULL)
+        return ptr - MRP_OFFSET(memblk_t, bt[__mm.depth]);
+    else
+        return NULL;
+}
+
+
+/*
+ * debugging allocator
+ */
+
+static inline int __mm_backtrace(void **bt, size_t size)
+{
+    int i, n;
+
+    n = backtrace(bt, (int)size);
+    for (i = n; i < (int)size; i++)
+        bt[i] = NULL;
+
+    return n;
+}
+
+
+static void *__mm_alloc(size_t size, const char *file, int line,
+                        const char *func)
+{
+    memblk_t *blk;
+    void     *bt[__mm.depth + 1];
+
+    __mm_backtrace(bt, MRP_ARRAY_SIZE(bt));
+    blk = memblk_alloc(size, file, line, func, bt + 1);
+
+    return memblk_to_ptr(blk);
+}
+
+
+static void *__mm_realloc(void *ptr, size_t size, const char *file,
+                          int line, const char *func)
+{
+    memblk_t *blk;
+    void     *bt[__mm.depth + 1];
+
+    __mm_backtrace(bt, MRP_ARRAY_SIZE(bt));
+    blk = ptr_to_memblk(ptr);
+
+    if (blk != NULL)
+        blk = memblk_resize(blk, size, file, line, func, bt + 1);
+    else
+        blk = memblk_alloc(size, file, line, func, bt + 1);
+
+    return memblk_to_ptr(blk);
+}
+
+
+static int __mm_memalign(void **ptr, size_t align, size_t size,
+                         const char *file, int line, const char *func)
+{
+    MRP_UNUSED(align);
+    MRP_UNUSED(size);
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+
+    *ptr  = NULL;
+    errno = ENOSYS;
+
+    mrp_log_error("XXX %s not implemented!!!", __FUNCTION__);
+    return -1;
+}
+
+
+static void __mm_free(void *ptr, const char *file, int line,
+                      const char *func)
+{
+    memblk_t *blk;
+    void     *bt[__mm.depth + 1];
+
+    if (ptr != NULL) {
+        __mm_backtrace(bt, MRP_ARRAY_SIZE(bt));
+        blk = ptr_to_memblk(ptr);
+
+        if (blk != NULL)
+            memblk_free(blk, file, line, func, bt + 1);
+    }
+}
+
+
+/*
+ * passthru allocator
+ */
+
+static void *__passthru_alloc(size_t size, const char *file, int line,
+                              const char *func)
+{
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+
+    if (MRP_UNLIKELY(size == 0))
+        return NULL;
+    else
+        return malloc(size);
+}
+
+
+static void *__passthru_realloc(void *ptr, size_t size, const char *file,
+                                int line, const char *func)
+{
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+
+    return realloc(ptr, size);
+}
+
+
+static int __passthru_memalign(void **ptr, size_t align, size_t size,
+                               const char *file, int line, const char *func)
+{
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+
+    return posix_memalign(ptr, align, size);
+}
+
+
+static void __passthru_free(void *ptr, const char *file, int line,
+                            const char *func)
+{
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+
+    free(ptr);
+}
+
+
+/*
+ * common public interface - uses either passthru or debugging
+ */
+
+void *mrp_mm_alloc(size_t size, const char *file, int line, const char *func)
+{
+    return __mm.alloc(size, file, line, func);
+}
+
+
+void *mrp_mm_realloc(void *ptr, size_t size, const char *file, int line,
+                     const char *func)
+{
+    return __mm.realloc(ptr, size, file, line, func);
+}
+
+
+char *mrp_mm_strdup(const char *s, const char *file, int line, const char *func)
+{
+    char   *p;
+    size_t  size;
+
+    if (s != NULL) {
+        size = strlen(s) + 1;
+        p    = mrp_mm_alloc(size, file, line, func);
+
+        if (p != NULL)
+            strcpy(p, s);
+    }
+    else
+        p = NULL;
+
+    return p;
+}
+
+
+int mrp_mm_memalign(void **ptr, size_t align, size_t size, const char *file,
+                    int line, const char *func)
+{
+    return __mm.memalign(ptr, align, size, file, line, func);
+}
+
+
+void mrp_mm_free(void *ptr, const char *file, int line, const char *func)
+{
+    return __mm.free(ptr, file, line, func);
+}
+
+
+int mrp_mm_config(mrp_mm_type_t type)
+{
+    if (__mm.cur_blocks != 0)
+        return FALSE;
+
+    switch (type) {
+    case MRP_MM_PASSTHRU:
+        __mm.alloc    = __passthru_alloc;
+        __mm.realloc  = __passthru_realloc;
+        __mm.memalign = __passthru_memalign;
+        __mm.free     = __passthru_free;
+        __mm.mode     = MRP_MM_PASSTHRU;
+        return TRUE;
+
+    case MRP_MM_DEBUG:
+        __mm.alloc    = __mm_alloc;
+        __mm.realloc  = __mm_realloc;
+        __mm.memalign = __mm_memalign;
+        __mm.free     = __mm_free;
+        __mm.mode     = MRP_MM_DEBUG;
+        return TRUE;
+
+    default:
+        mrp_log_error("Invalid memory allocator type 0x%x requested.", type);
+        return FALSE;
+    }
+}
+
+
+#define NBUCKET 1024
+
+static int btcmp(void **bt1, void **bt2)
+{
+    ptrdiff_t diff;
+    int       i;
+
+    for (i = 0; i < __mm.depth; i++) {
+        diff = bt1[i] - bt2[i];
+
+        if (diff < 0)
+            return -1;
+        else if (diff > 0)
+            return +1;
+    }
+
+    return 0;
+}
+
+
+static uint32_t blkhash(memblk_t *blk)
+{
+    uint32_t h;
+    int      i;
+
+    h = 0;
+    for (i = 0; i < __mm.depth; i++)
+        h ^= (blk->bt[i] - NULL) & 0xffffffffUL;
+
+    return h % NBUCKET;
+}
+
+
+static memblk_t *blkfind(mrp_list_hook_t *buckets, memblk_t *blk)
+{
+    uint32_t         h    = blkhash(blk);
+    mrp_list_hook_t *head = buckets + h;
+    mrp_list_hook_t *p, *n;
+    memblk_t        *b;
+
+    mrp_list_foreach(head, p, n) {
+        b = mrp_list_entry(p, typeof(*b), hook);
+        if (!btcmp(&b->bt[0], &blk->bt[0]))
+            return b;
+    }
+
+    return NULL;
+}
+
+
+static void collect_blocks(mrp_list_hook_t *buckets)
+{
+    mrp_list_hook_t *p, *n;
+    memblk_t        *head, *blk;
+    uint32_t         h;
+    int              i;
+
+    for (i = 0; i < NBUCKET; i++)
+        mrp_list_init(buckets + i);
+
+    mrp_list_foreach(&__mm.blocks, p, n) {
+        blk = mrp_list_entry(p, typeof(*blk), hook);
+
+        mrp_list_init(&blk->more);
+        head = blkfind(buckets, blk);
+
+        if (head != NULL) {
+            mrp_list_append(&head->more, &blk->more);
+            head->size += blk->size;
+        }
+        else {
+            h = blkhash(blk);
+            mrp_list_delete(&blk->hook);
+            mrp_list_append(buckets + h, &blk->hook);
+        }
+    }
+}
+
+
+static uint32_t group_usage(memblk_t *head, int exclude_head)
+{
+    mrp_list_hook_t *p, *n;
+    memblk_t        *blk;
+    uint32_t         total;
+
+    total = exclude_head ? 0 : head->size;
+    mrp_list_foreach(&head->more, p, n) {
+        blk = mrp_list_entry(p, typeof(*blk), more);
+        total += blk->size;
+    }
+
+    return total;
+}
+
+
+static void dump_group(FILE *fp, memblk_t *head)
+{
+    mrp_list_hook_t  *p, *n;
+    memblk_t         *blk;
+    char            **syms, *sym;
+    uint32_t          total;
+    int               nblk, i;
+
+    fprintf(fp, "Allocations with call stack fingerprint:\n");
+    syms = backtrace_symbols(head->bt, __mm.depth);
+    for (i = 0; i < __mm.depth && head->bt[i]; i++) {
+        sym = syms && syms[i] ? strrchr(syms[i], '/') : NULL;
+        fprintf(fp, "    %p (%s)\n", head->bt[i], sym ? sym + 1 : "<unknown>");
+    }
+    free(syms);
+
+    total = head->size - group_usage(head, TRUE);
+    nblk  = 1;
+
+    fprintf(fp, "        %lu bytes at %p\n", (unsigned long)total,
+            memblk_to_ptr(head));
+
+    mrp_list_foreach(&head->more, p, n) {
+        blk = mrp_list_entry(p, typeof(*blk), more);
+
+        total += blk->size;
+        nblk++;
+
+        fprintf(fp, "        %zd bytes at %p\n", blk->size, memblk_to_ptr(blk));
+    }
+
+    if (nblk > 1)
+        fprintf(fp, "    total %lu bytes in %d blocks\n",
+                (unsigned long)total, nblk);
+}
+
+
+static void sort_blocks(mrp_list_hook_t *buckets, mrp_list_hook_t *sorted)
+{
+    mrp_list_hook_t *bp, *bn, *sp, *sn;
+    memblk_t        *head, *entry, *next;
+    int              i;
+
+    mrp_list_init(sorted);
+
+    for (i = 0; i < NBUCKET; i++) {
+        mrp_list_foreach(buckets + i, bp, bn) {
+            head = mrp_list_entry(bp, typeof(*head), hook);
+
+            next = NULL;
+            mrp_list_foreach(sorted, sp, sn) {
+                entry = mrp_list_entry(sp, typeof(*entry), hook);
+
+                if (head->size <= entry->size) {
+                    next = entry;
+                    break;
+                }
+            }
+
+            mrp_list_delete(&head->hook);
+
+            if (next != NULL)
+                mrp_list_insert_before(&next->hook, &head->hook);
+            else
+                mrp_list_append(sorted, &head->hook);
+        }
+    }
+}
+
+
+static void dump_blocks(FILE *fp, mrp_list_hook_t *sorted)
+{
+    mrp_list_hook_t *p, *n;
+    memblk_t        *head;
+
+    mrp_list_foreach(sorted, p, n) {
+        head = mrp_list_entry(p, typeof(*head), hook);
+        dump_group(fp, head);
+    }
+}
+
+
+static void relink_blocks(mrp_list_hook_t *sorted)
+{
+    mrp_list_hook_t *p, *n;
+    memblk_t        *head;
+    uint32_t         rest;
+
+    mrp_list_foreach(sorted, p, n) {
+        head = mrp_list_entry(p, typeof(*head), hook);
+        mrp_list_delete(&head->hook);
+        mrp_list_append(&__mm.blocks, &head->hook);
+
+        rest = group_usage(head, TRUE);
+        head->size -= rest;
+    }
+}
+
+
+void mrp_mm_dump(FILE *fp)
+{
+    mrp_list_hook_t buckets[NBUCKET];
+    mrp_list_hook_t sorted;
+
+    mrp_list_init(&sorted);
+
+    collect_blocks(buckets);
+    sort_blocks(buckets, &sorted);
+    dump_blocks(fp, &sorted);
+    relink_blocks(&sorted);
+
+    fprintf(fp, "Max: %llu bytes (%.2f M, %.2f G), %ld blocks\n",
+            (unsigned long long)__mm.max_alloc,
+            1.0 * __mm.max_alloc / (1024 * 1024),
+            1.0 * __mm.max_alloc / (1024 * 1024 * 1024),
+            (unsigned long)__mm.max_blocks);
+    fprintf(fp, "Current: %llu bytes (%.2f M, %.2f G) in %ld blocks.\n",
+            (unsigned long long)__mm.cur_alloc,
+            1.0 * __mm.cur_alloc / (1024 * 1024),
+            1.0 * __mm.cur_alloc / (1024 * 1024 * 1024),
+            (unsigned long)__mm.cur_blocks);
+}
+
+
+void mrp_mm_check(FILE *fp)
+{
+    mrp_mm_dump(fp);
+}
+
+
+
+
+
+/*
+ * object pool interface
+ */
+
+typedef unsigned int mask_t;
+
+#define W sizeof(mask_t)
+#define B (W * 8)
+#define MASK_BYTES (sizeof(mask_t))
+#define MASK_BITS  (MASK_BYTES * 8)
+#define MASK_EMPTY ((mask_t)-1)
+#define MASK_FULL  ((mask_t) 0)
+
+typedef struct pool_chunk_s pool_chunk_t;
+
+static int pool_calc_sizes(mrp_objpool_t *pool);
+static int pool_grow(mrp_objpool_t *pool, int nobj);
+static int pool_shrink(mrp_objpool_t *pool, int nobj);
+static pool_chunk_t *chunk_alloc(int nperchunk);
+static void chunk_free(pool_chunk_t *chunk);
+static inline int chunk_empty(pool_chunk_t *chunk);
+static void pool_foreach_object(mrp_objpool_t *pool,
+                                void (*cb)(void *obj, void *user_data),
+                                void *user_data);
+static void chunk_foreach_object(pool_chunk_t *chunk,
+                                 void (*cb)(void *obj, void *user_data),
+                                 void *user_data);
+
+
+/*
+ * an object pool
+ */
+
+struct mrp_objpool_s {
+    char             *name;                      /* verbose pool name */
+    size_t            limit;                     /* max. number of objects */
+    size_t            objsize;                   /* size of a single object */
+    size_t            prealloc;                  /* preallocate this many */
+    size_t            nobj;                      /* currently allocated */
+    int             (*setup)(void *);            /* object setup callback */
+    void            (*cleanup)(void *);          /* object cleanup callback */
+    uint32_t          flags;                     /* pool flags */
+    int               poison;                    /* poisoning pattern */
+
+    size_t            nperchunk;                 /* objects per chunk */
+    size_t            dataidx;                   /* data  */
+    mrp_list_hook_t   space;                     /* chunk with frees slots */
+    size_t            nspace;                    /* number of such chunks */
+    mrp_list_hook_t   full;                      /* fully allocated chunks */
+    size_t            nfull;                     /* number of such chunks */
+};
+
+
+/*
+ * a chunk of memory allocated to an object pool
+ */
+
+struct pool_chunk_s {
+    mrp_objpool_t   *pool;                       /* pool we're alloced to */
+    mrp_list_hook_t  hook;                       /* hook to chunk list */
+    mask_t           cache;                      /* cache bits */
+    mask_t           used[];                     /* allocation mask */
+};
+
+
+
+mrp_objpool_t *mrp_objpool_create(mrp_objpool_config_t *cfg)
+{
+    mrp_objpool_t *pool;
+
+    if ((pool = mrp_allocz(sizeof(*pool))) != NULL) {
+        if ((pool->name = mrp_strdup(cfg->name)) == NULL)
+            goto fail;
+
+        pool->limit    = cfg->limit;
+        pool->objsize  = MRP_MAX(cfg->objsize, (size_t)MRP_MM_OBJSIZE_MIN);
+        pool->prealloc = cfg->prealloc;
+        pool->setup    = cfg->setup;
+        pool->cleanup  = cfg->cleanup;
+        pool->flags    = cfg->flags;
+        pool->poison   = cfg->poison;
+
+        mrp_list_init(&pool->space);
+        mrp_list_init(&pool->full);
+        pool->nspace = 0;
+        pool->nfull  = 0;
+
+        if (!pool_calc_sizes(pool))
+            goto fail;
+
+        if (!mrp_objpool_grow(pool, pool->prealloc))
+            goto fail;
+
+        mrp_debug("pool <%s> created, with %zd/%zd objects.", pool->name,
+                  pool->prealloc, pool->limit);
+
+        return pool;
+    }
+
+
+ fail:
+    mrp_objpool_destroy(pool);
+    return NULL;
+}
+
+
+static void free_object(void *obj, void *user_data)
+{
+    mrp_objpool_t *pool = (mrp_objpool_t *)user_data;
+
+    printf("Releasing unfreed object %p from pool <%s>.\n", obj, pool->name);
+    mrp_objpool_free(obj);
+}
+
+
+void mrp_objpool_destroy(mrp_objpool_t *pool)
+{
+    if (pool != NULL) {
+        if (pool->cleanup != NULL)
+            pool_foreach_object(pool, free_object, pool);
+
+        mrp_free(pool->name);
+        mrp_free(pool);
+    }
+}
+
+
+void *mrp_objpool_alloc(mrp_objpool_t *pool)
+{
+    pool_chunk_t *chunk;
+    void         *obj;
+    unsigned int  cidx, uidx, sidx;
+
+    if (pool->limit && pool->nobj >= pool->limit)
+        return NULL;
+
+    if (mrp_list_empty(&pool->space)) {
+        if (!pool_grow(pool, 1))
+            return NULL;
+    }
+
+    chunk = mrp_list_entry(pool->space.next, pool_chunk_t, hook);
+    cidx  = ffs(chunk->cache);
+
+    if (!cidx) {
+        mrp_log_error("object pool bug: no free slots in cache mask.");
+        return NULL;
+    }
+    else
+        cidx--;
+
+    uidx = ffs(chunk->used[cidx]);
+
+    if (!uidx) {
+        mrp_log_error("object pool bug: no free slots in used mask.");
+        return NULL;
+    }
+    else
+        uidx--;
+
+    sidx = cidx * MASK_BITS + uidx;
+    obj  = ((void *)&chunk->used[pool->dataidx]) + (sidx * pool->objsize);
+
+    mrp_debug("%p: %u/%u: %u, offs %zd\n", obj, cidx, uidx, sidx,
+              sidx * pool->objsize);
+
+    chunk->used[cidx] &= ~(1 << uidx);
+
+    if (chunk->used[cidx] == MASK_FULL) {
+        chunk->cache &= ~(1 << cidx);
+
+        if (chunk->cache == MASK_FULL) {          /* chunk exhausted */
+            mrp_list_delete(&chunk->hook);
+            pool->nspace--;
+            mrp_list_append(&pool->full, &chunk->hook);
+            pool->nfull++;
+        }
+    }
+
+    if (pool->setup == NULL || pool->setup(obj)) {
+        pool->nobj++;
+        return obj;
+    }
+    else {
+        mrp_objpool_free(obj);
+        return NULL;
+    }
+}
+
+
+void mrp_objpool_free(void *obj)
+{
+    pool_chunk_t  *chunk;
+    mrp_objpool_t *pool;
+    unsigned int   cidx, uidx, sidx;
+    mask_t         cache, used;
+    void          *base;
+
+    if (obj == NULL)
+        return;
+
+    chunk = (pool_chunk_t *)(((ptrdiff_t)obj) & ~(__mm.chunk_size - 1));
+    pool  = chunk->pool;
+
+    base = (void *)&chunk->used[pool->dataidx];
+    sidx = (obj - base) / pool->objsize;
+    cidx = sidx / MASK_BITS;
+    uidx = sidx & (MASK_BITS - 1);
+
+    mrp_debug("%p: %u/%u: %u, offs %zd\n", obj, cidx, uidx, sidx,
+              sidx * pool->objsize);
+
+    cache = chunk->cache;
+    used  = chunk->used[cidx];
+
+    if (used & (1 << uidx)) {
+        mrp_log_error("Trying to free unallocated object %p of pool <%s>.",
+                      obj, pool->name);
+        return;
+    }
+
+    if (pool->cleanup != NULL)
+        pool->cleanup(obj);
+
+    if (pool->flags & MRP_OBJPOOL_FLAG_POISON)
+        memset(obj, pool->poison, pool->objsize);
+
+    chunk->used[cidx] |= (1 << uidx);
+    chunk->cache      |= (1 << cidx);
+
+    if (cache == MASK_FULL) {                    /* chunk was full */
+        mrp_list_delete(&chunk->hook);
+        pool->nfull--;
+        mrp_list_append(&pool->space, &chunk->hook);
+        pool->nspace++;
+    }
+
+    pool->nobj--;
+}
+
+
+int mrp_objpool_grow(mrp_objpool_t *pool, int nobj)
+{
+    int nchunk = (nobj + pool->nperchunk - 1) / pool->nperchunk;
+
+    return pool_grow(pool, nchunk) == nchunk;
+}
+
+
+int mrp_objpool_shrink(mrp_objpool_t *pool, int nobj)
+{
+    int nchunk = (nobj + pool->nperchunk - 1) / pool->nperchunk;
+
+    return pool_shrink(pool, nchunk) == nchunk;
+}
+
+
+static int pool_calc_sizes(mrp_objpool_t *pool)
+{
+    size_t S, C, Hf, Hv, P;
+    size_t n, T;
+
+    if (!pool->objsize)
+        return FALSE;
+
+    pool->objsize = MRP_ALIGN(pool->objsize, MRP_MM_ALIGN);
+
+    /*
+     * Pool chunks consist of an administrative header followed by object
+     * slots each of which can be either claimed/allocated or free. The
+     * header contains a back pointer to the pool, a hook to one of the
+     * chunk lists and a two-level bit-mask for slot allocation status.
+     * The two-level mask consists of a 32-bit cache word and actual slot
+     * status words. The nth bit of the cache word caches whether there are
+     * any free among the nth - (n + 31)th slots. The slot status words keep
+     * the status of the actual slots. To find a free slot we find the idx
+     * of the 1st word with a free slot from the cache and then the free
+     * slot index in that word. To be able to use FFS we use inverted bit
+     * semantics (0=allocated, 1=free) and we populate the words starting
+     * at the LSB.
+     *
+     * Here we calculate how many objects we'll be able to squeeze into a
+     * single pool chunk and how many mask bits we'll need to administer
+     * the status of these. To do this we use the following equations:
+     *
+     *     1) Hf + Hv + n * S = C
+     *     2) Hv = W + (n + B - 1) / B * W
+     * where
+     *     C: chunk size
+     *     S: object size (aligned to our minimum alignment)
+     *    Hf: header size, fixed part
+     *    Hv: Header size, variable part (allocation mask)
+     *     n: number of objects / chunk
+     *     W: bitmask word size in bytes
+     *     B: bitmask word size in bits
+     *
+     * Solving the equations for n gives us
+     *     n = (B*C - B*Hf - W*(2*B - 1)) / (B*S + W)
+     *
+     * If any, the only non-obvious thing below is that instead of trying
+     * to express padding as part of the equation system (which seems to be
+     * way beyond my abilities in math nowadays), we initally assume no
+     * padding then check and compensate for it in the end if necessary.
+     */
+
+    Hf = sizeof(pool_chunk_t);
+    C  = __mm.chunk_size;
+    P  = 0;
+
+    S  = MRP_ALIGN(pool->objsize, MRP_MM_ALIGN);
+    n  = (B * C - B * Hf - W * (2*B - 1)) / (B * S + W);
+    Hv = W + W * (n + B - 1) / B;
+
+    P = (Hf + Hv) % sizeof(void *);
+    if (P != 0) {
+        P = sizeof(void *) - P;
+
+        if (Hv + Hf + P + n * S > C) {
+            n--;
+            Hv = W + W * (n + B - 1) / B;
+        }
+    }
+
+    T  = Hf + Hv + P + n * S;
+
+    if (T > C) {
+        mrp_log_error("Could not size pool '%s' properly.", pool->name);
+        return FALSE;
+    }
+
+    pool->nperchunk = n;
+    pool->dataidx   = (n + B - 1) / B;
+
+    if (pool->limit && (pool->limit % pool->nperchunk) != 0)
+        pool->limit += (pool->nperchunk - (pool->limit % pool->nperchunk));
+
+    return TRUE;
+}
+
+
+static int pool_grow(mrp_objpool_t *pool, int nchunk)
+{
+    pool_chunk_t *chunk;
+    int           cnt;
+
+    for (cnt = 0; cnt < nchunk; cnt++) {
+        chunk = chunk_alloc(pool->nperchunk);
+
+        if (chunk != NULL) {
+            chunk->pool = pool;
+            mrp_list_append(&pool->space, &chunk->hook);
+            pool->nspace++;
+        }
+        else
+            break;
+    }
+
+    return cnt;
+}
+
+
+static int pool_shrink(mrp_objpool_t *pool, int nchunk)
+{
+    mrp_list_hook_t *p, *n;
+    pool_chunk_t    *chunk;
+    int              cnt;
+
+    cnt = 0;
+    mrp_list_foreach(&pool->space, p, n) {
+        chunk = mrp_list_entry(p, pool_chunk_t, hook);
+
+        if (chunk_empty(chunk)) {
+            mrp_list_delete(&chunk->hook);
+            chunk_free(chunk);
+            pool->nspace--;
+            cnt++;
+        }
+
+        if (cnt >= nchunk)
+            break;
+    }
+
+    return cnt;
+}
+
+
+static void pool_foreach_object(mrp_objpool_t *pool,
+                                void (*cb)(void *obj, void *user_data),
+                                void *user_data)
+{
+    mrp_list_hook_t *p, *n;
+    pool_chunk_t    *chunk;
+
+    mrp_list_foreach(&pool->full, p, n) {
+        chunk = mrp_list_entry(p, pool_chunk_t, hook);
+        chunk_foreach_object(chunk, cb, user_data);
+    }
+
+    mrp_list_foreach(&pool->space, p, n) {
+        chunk = mrp_list_entry(p, pool_chunk_t, hook);
+        chunk_foreach_object(chunk, cb, user_data);
+    }
+}
+
+
+static void chunk_foreach_object(pool_chunk_t *chunk,
+                                 void (*cb)(void *obj, void *user_data),
+                                 void *user_data)
+{
+    mrp_objpool_t *pool = chunk->pool;
+    void          *obj;
+    int            sidx, cidx, uidx;
+    mask_t         used;
+
+    sidx = 0;
+    while (sidx < (int)pool->nperchunk) {
+        cidx = sidx / MASK_BITS;
+        uidx = sidx & (MASK_BITS - 1);
+        used = chunk->used[cidx];
+
+        if (!(used & (1 << uidx))) {
+            obj = ((void *)&chunk->used[pool->dataidx]) + (sidx*pool->objsize);
+            cb(obj, user_data);
+            sidx++;
+        }
+        else {
+            if (used == MASK_EMPTY)
+                sidx = (sidx + MASK_BITS) & ~(MASK_BITS - 1);
+            else
+                sidx++;
+        }
+    }
+}
+
+
+static inline int chunk_empty(pool_chunk_t *chunk)
+{
+    mask_t mask;
+    int    i, n;
+
+    if (chunk->cache != (MASK_EMPTY & 0xffff))
+        return FALSE;
+    else {
+        for (n = chunk->pool->nperchunk, i = 0; n > 0; n -= MASK_BITS, i++) {
+            if (n >= (int)MASK_BITS)
+                mask = MASK_EMPTY;
+            else
+                mask = (1 << n) - 1;
+
+            if ((chunk->used[i] & mask) != mask)
+                return FALSE;
+        }
+
+        return TRUE;
+    }
+}
+
+
+static void chunk_init(pool_chunk_t *chunk, int nperchunk)
+{
+    int nword, left, i;
+
+    mrp_list_init(&chunk->hook);
+
+    left  = nperchunk;
+    nword = (nperchunk + MASK_BITS - 1) / MASK_BITS;
+
+    /*
+     * initialize allocation bitmask
+     *
+     * Note that every bit that corresponds to a non-allocatable slots
+     * we mark as reserved. This keeps both the allocation and freeing
+     * code paths simpler.
+     */
+
+    chunk->cache = (1 << nword) - 1;
+
+    for (i = 0; left > 0; i++) {
+        if (left >= (int)MASK_BITS)
+            chunk->used[i] = MASK_EMPTY;
+        else
+            chunk->used[i] = (((mask_t)1) << left) - 1;
+
+        left -= B;
+    }
+}
+
+
+static pool_chunk_t *chunk_alloc(int nperchunk)
+{
+    void *chunk;
+    int   err;
+
+    err = posix_memalign(&chunk, __mm.chunk_size, __mm.chunk_size);
+
+    if (err == 0) {
+        memset(chunk, 0, __mm.chunk_size);
+        chunk_init((pool_chunk_t *)chunk, nperchunk);
+    }
+    else
+        chunk = NULL;
+
+    return chunk;
+}
+
+
+static void chunk_free(pool_chunk_t *chunk)
+{
+    free(chunk);
+}
+
+
+#if 0
+static void test_sizes(void)
+{
+    size_t S, C, Hf, Hv, P;
+    size_t i, n, T, Hv1, n1, T1;
+    int    ok, ok1;
+
+    Hf = sizeof(pool_chunk_t);
+    C  = __mm.chunk_size;
+    P  = 0;
+
+    printf(" C: %zd\n", C);
+    printf("Hf: %zd\n", Hf);
+
+    for (i = 1; i < __mm.chunk_size / 8; i++) {
+        S  = MRP_ALIGN(i, MRP_MM_ALIGN);
+        n  = (B * C - B * Hf - W * (2*B - 1)) / (B * S + W);
+        Hv = W + W * (n + B - 1) / B;
+
+        P = (Hf + Hv) % sizeof(void *);
+        if (P != 0) {
+            P = sizeof(void *) - P;
+
+            if (Hv + Hf + P + n * S > C) {
+                n--;
+                Hv = W + W * (n + B - 1) / B;
+            }
+        }
+
+        T  = Hf + Hv + P + n * S;
+        ok = T <= C;
+
+        n1  = n + 1;
+        Hv1 = W + W * (n1 + B - 1) / B;
+        T1  = Hf + Hv1 + P + n1 * S;
+        ok1 = T1 > C;
+
+        printf("  i = %zd: %zd * %zd + %zd (%zd: %s, %zd: %s)\n", i, n, S, P,
+               T , ok  ? "OK" : "FAIL",
+               T1, ok1 ? "OK" : "FAIL");
+        {
+            size_t hs, us;
+
+            us = sizeof(uint32_t);
+            hs = (Hf + Hv + P) / us;
+
+            printf("  H+P: %zd (%zd * %zd = %zd)\n", Hf + Hv + P,
+                   hs, us, hs * us);
+
+            if (((Hf + Hv + P) % sizeof(void *)) != 0) {
+                printf("Padding error!\n");
+                exit(1);
+            }
+        }
+
+        if (!ok || !ok1)
+            exit(1);
+    }
+}
+#endif
diff --git a/src/common/mm.h b/src/common/mm.h
new file mode 100644 (file)
index 0000000..73ffacb
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_MM_H__
+#define __MURPHY_MM_H__
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_MM_ALIGN 8                       /* object alignment */
+#define MRP_MM_CONFIG_ENVVAR "__MURPHY_MM_CONFIG"
+
+#define mrp_alloc(size)        mrp_mm_alloc((size), __LOC__)
+#define mrp_free(ptr)          mrp_mm_free((ptr), __LOC__)
+#define mrp_strdup(s)          mrp_mm_strdup((s), __LOC__)
+#define mrp_datadup(ptr, size) ({                                         \
+            typeof(ptr) _ptr = mrp_alloc(size);                           \
+                                                                          \
+            if (_ptr != NULL)                                             \
+                memcpy(_ptr, ptr, size);                                  \
+                                                                          \
+            _ptr;                                                         \
+        })
+
+#define mrp_allocz(size) ({                                               \
+            void *_ptr;                                                   \
+                                                                          \
+            if ((_ptr = mrp_mm_alloc(size, __LOC__)) != NULL)             \
+                memset(_ptr, 0, size);                                    \
+                                                                          \
+            _ptr;})
+
+#define mrp_calloc(n, size) mrp_allocz((n) * (size))
+
+#define mrp_reallocz(ptr, o, n) ({                                        \
+            typeof(ptr) _ptr;                                             \
+            typeof(o)   _o;                                               \
+            typeof(n)   _n    = (n);                                      \
+            size_t      _size = sizeof(*_ptr) * (_n);                     \
+                                                                          \
+            if ((ptr) != NULL)                                            \
+                _o = o;                                                   \
+            else                                                          \
+                _o = 0;                                                   \
+                                                                          \
+            _ptr = (typeof(ptr))mrp_mm_realloc(ptr, _size, __LOC__);      \
+            if (_ptr != NULL || _n == 0) {                                \
+                if ((unsigned)(_n) > (unsigned)(_o))                      \
+                    memset(_ptr + (_o), 0,                                \
+                           ((_n)-(_o)) * sizeof(*_ptr));                  \
+                ptr = _ptr;                                               \
+            }                                                             \
+            _ptr; })
+
+#define mrp_realloc(ptr, size) ({                                         \
+            typeof(ptr) _ptr;                                             \
+            size_t      _size = size;                                     \
+                                                                          \
+            _ptr = (typeof(ptr))mrp_mm_realloc(ptr, _size, __LOC__);      \
+            if (_ptr != NULL || _size == 0)                               \
+                ptr = _ptr;                                               \
+                                                                          \
+            _ptr; })
+
+#define mrp_clear(obj) memset((obj), 0, sizeof(*(obj)))
+
+
+#define mrp_alloc_array(type, n)  ((type *)mrp_alloc(sizeof(type) * (n)))
+#define mrp_allocz_array(type, n) ((type *)mrp_allocz(sizeof(type) * (n)))
+
+typedef enum {
+    MRP_MM_PASSTHRU = 0,                 /* passthru allocator */
+    MRP_MM_DEFAULT  = MRP_MM_PASSTHRU,   /* default is passthru */
+    MRP_MM_DEBUG                         /* debugging allocator */
+} mrp_mm_type_t;
+
+
+int mrp_mm_config(mrp_mm_type_t type);
+void mrp_mm_check(FILE *fp);
+void mrp_mm_dump(FILE *fp);
+
+void *mrp_mm_alloc(size_t size, const char *file, int line, const char *func);
+void *mrp_mm_realloc(void *ptr, size_t size, const char *file, int line,
+                     const char *func);
+char *mrp_mm_strdup(const char *s, const char *file, int line,
+                    const char *func);
+int mrp_mm_memalign(void **ptr, size_t align, size_t size, const char *file,
+                    int line, const char *func);
+void mrp_mm_free(void *ptr, const char *file, int line, const char *func);
+
+
+
+
+#define MRP_MM_OBJSIZE_MIN 16                    /* minimum object size */
+
+enum {
+    MRP_OBJPOOL_FLAG_POISON = 0x1,               /* poison free'd objects */
+};
+
+
+/*
+ * object pool configuration
+ */
+
+typedef struct {
+    char      *name;                             /* verbose pool name */
+    size_t     limit;                            /* max. number of objects */
+    size_t     objsize;                          /* size of a single object */
+    size_t     prealloc;                         /* preallocate this many */
+    int      (*setup)(void *);                   /* object setup callback */
+    void     (*cleanup)(void *);                 /* object cleanup callback */
+    uint32_t   flags;                            /* MRP_OBJPOOL_FLAG_* */
+    int        poison;                           /* poisoning pattern */
+} mrp_objpool_config_t;
+
+
+typedef struct mrp_objpool_s mrp_objpool_t;
+
+/** Create a new object pool with the given configuration. */
+mrp_objpool_t *mrp_objpool_create(mrp_objpool_config_t *cfg);
+
+/** Destroy an object pool, freeing all associated memory. */
+void mrp_objpool_destroy(mrp_objpool_t *pool);
+
+/** Allocate a new object from the pool. */
+void *mrp_objpool_alloc(mrp_objpool_t *pool);
+
+/** Free the given object. */
+void mrp_objpool_free(void *obj);
+
+/** Grow @pool to accomodate @nobj new objects. */
+int mrp_objpool_grow(mrp_objpool_t *pool, int nobj);
+
+/** Shrink @pool by @nobj new objects, if possible. */
+int mrp_objpool_shrink(mrp_objpool_t *pool, int nobj);
+
+/** Get the value of a boolean key from the configuration. */
+int mrp_mm_config_bool(const char *key, int defval);
+
+/** Get the value of a boolean key from the configuration. */
+int32_t mrp_mm_config_int32(const char *key, int32_t defval);
+
+/** Get the value of a boolean key from the configuration. */
+uint32_t mrp_mm_config_uint32(const char *key, uint32_t defval);
+
+/** Get the value of a string key from the configuration. */
+int mrp_mm_config_string(const char *key, const char *defval,
+                         char *buf, size_t size);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_MM_H__ */
+
diff --git a/src/common/msg.c b/src/common/msg.c
new file mode 100644 (file)
index 0000000..2db8214
--- /dev/null
@@ -0,0 +1,2222 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/msg.h>
+
+#define NDIRECT_TYPE      256            /* directly indexed types */
+
+static mrp_data_descr_t **direct_types;  /* directly indexed types */
+static mrp_data_descr_t **other_types;   /* linearly searched types */
+static int                nother_type;
+
+
+static inline void destroy_field(mrp_msg_field_t *f)
+{
+    uint32_t i;
+
+    if (f != NULL) {
+        mrp_list_delete(&f->hook);
+
+        switch (f->type) {
+        case MRP_MSG_FIELD_STRING:
+            mrp_free(f->str);
+            break;
+
+        case MRP_MSG_FIELD_BLOB:
+            mrp_free(f->blb);
+            break;
+
+        default:
+            if (f->type & MRP_MSG_FIELD_ARRAY) {
+                if ((f->type & ~MRP_MSG_FIELD_ARRAY) == MRP_MSG_FIELD_STRING) {
+                    for (i = 0; i < f->size[0]; i++) {
+                        mrp_free(f->astr[i]);
+                    }
+                }
+
+                mrp_free(f->aany);
+            }
+            break;
+        }
+
+        mrp_free(f);
+    }
+}
+
+
+static inline mrp_msg_field_t *create_field(uint16_t tag, va_list *ap)
+{
+    mrp_msg_field_t *f;
+    uint16_t         type, base;
+    uint32_t         size;
+    void            *blb;
+
+    type = va_arg(*ap, uint32_t);
+
+#define CREATE(_f, _tag, _type, _fldtype, _fld, _last, _errlbl) do {      \
+                                                                          \
+            (_f) = mrp_allocz(MRP_OFFSET(typeof(*_f), _last) +            \
+                              sizeof(_f->_last));                         \
+                                                                          \
+            if ((_f) != NULL) {                                           \
+                mrp_list_init(&(_f)->hook);                             \
+                (_f)->tag  = _tag;                                        \
+                (_f)->type = _type;                                       \
+                (_f)->_fld = va_arg(*ap, _fldtype);                       \
+            }                                                             \
+            else {                                                        \
+                goto _errlbl;                                             \
+            }                                                             \
+        } while (0)
+
+#define CREATE_ARRAY(_f, _tag, _type, _fld, _fldtype, _errlbl) do {       \
+            uint16_t _base;                                               \
+            uint32_t _i;                                                  \
+                                                                          \
+            (_f) = mrp_allocz(MRP_OFFSET(typeof(*_f), size[1]));          \
+                                                                          \
+            if ((_f) != NULL) {                                           \
+                mrp_list_init(&(_f)->hook);                               \
+                (_f)->tag  = _tag;                                        \
+                (_f)->type = _type | MRP_MSG_FIELD_ARRAY;                 \
+                _base      = _type & ~MRP_MSG_FIELD_ARRAY;                \
+                                                                          \
+                _f->size[0] = va_arg(*ap, uint32_t);                      \
+                _f->_fld    = mrp_allocz_array(typeof(*_f->_fld),         \
+                                               _f->size[0]);              \
+                                                                          \
+                if (_f->_fld == NULL && _f->size[0] != 0)                 \
+                    goto _errlbl;                                         \
+                else                                                      \
+                    memcpy(_f->_fld, va_arg(*ap, typeof(_f->_fld)),       \
+                           _f->size[0] * sizeof(_f->_fld[0]));            \
+                                                                          \
+                if (_base == MRP_MSG_FIELD_STRING) {                      \
+                    for (_i = 0; _i < _f->size[0]; _i++) {                \
+                        _f->astr[_i] = mrp_strdup(_f->astr[_i]);          \
+                        if (_f->astr[_i] == NULL)                         \
+                            goto _errlbl;                                 \
+                    }                                                     \
+                }                                                         \
+            }                                                             \
+            else                                                          \
+                goto _errlbl;                                             \
+        } while (0)
+
+    f = NULL;
+
+    switch (type) {
+    case MRP_MSG_FIELD_STRING:
+        CREATE(f, tag, type, char *, str, str, fail);
+        f->str = mrp_strdup(f->str);
+        if (f->str == NULL)
+            goto fail;
+        break;
+    case MRP_MSG_FIELD_BOOL:
+        CREATE(f, tag, type, int, bln, bln, fail);
+        break;
+    case MRP_MSG_FIELD_UINT8:
+        CREATE(f, tag, type, unsigned int, u8, u8, fail);
+        break;
+    case MRP_MSG_FIELD_SINT8:
+        CREATE(f, tag, type, signed int, s8, s8, fail);
+        break;
+    case MRP_MSG_FIELD_UINT16:
+        CREATE(f, tag, type, unsigned int, u16, u16, fail);
+        break;
+    case MRP_MSG_FIELD_SINT16:
+        CREATE(f, tag, type, signed int, s16, s16, fail);
+        break;
+    case MRP_MSG_FIELD_UINT32:
+        CREATE(f, tag, type, unsigned int, u32, u32, fail);
+        break;
+    case MRP_MSG_FIELD_SINT32:
+        CREATE(f, tag, type, signed int, s32, s32, fail);
+        break;
+    case MRP_MSG_FIELD_UINT64:
+        CREATE(f, tag, type, uint64_t, u64, u64, fail);
+        break;
+    case MRP_MSG_FIELD_SINT64:
+        CREATE(f, tag, type, int64_t, s64, s64, fail);
+        break;
+    case MRP_MSG_FIELD_DOUBLE:
+        CREATE(f, tag, type, double, dbl, dbl, fail);
+        break;
+
+    case MRP_MSG_FIELD_BLOB:
+        size = va_arg(*ap, uint32_t);
+        CREATE(f, tag, type, void *, blb, size[0], fail);
+
+        blb        = f->blb;
+        f->size[0] = size;
+        f->blb     = mrp_allocz(size);
+
+        if (f->blb != NULL) {
+            memcpy(f->blb, blb, size);
+            f->size[0] = size;
+        }
+        else
+            goto fail;
+        break;
+
+    default:
+        if (!(type & MRP_MSG_FIELD_ARRAY)) {
+            errno = EINVAL;
+            goto fail;
+        }
+
+        base = type & ~MRP_MSG_FIELD_ARRAY;
+
+        switch (base) {
+        case MRP_MSG_FIELD_STRING:
+            CREATE_ARRAY(f, tag, base, astr, char *, fail);
+            break;
+        case MRP_MSG_FIELD_BOOL:
+            CREATE_ARRAY(f, tag, base, abln, int, fail);
+            break;
+        case MRP_MSG_FIELD_UINT8:
+            CREATE_ARRAY(f, tag, base, au8, unsigned int, fail);
+            break;
+        case MRP_MSG_FIELD_SINT8:
+            CREATE_ARRAY(f, tag, base, as8, int, fail);
+            break;
+        case MRP_MSG_FIELD_UINT16:
+            CREATE_ARRAY(f, tag, base, au16, unsigned int, fail);
+            break;
+        case MRP_MSG_FIELD_SINT16:
+            CREATE_ARRAY(f, tag, base, as16, int, fail);
+            break;
+        case MRP_MSG_FIELD_UINT32:
+            CREATE_ARRAY(f, tag, base, au32, unsigned int, fail);
+            break;
+        case MRP_MSG_FIELD_SINT32:
+            CREATE_ARRAY(f, tag, base, as32, int, fail);
+            break;
+        case MRP_MSG_FIELD_UINT64:
+            CREATE_ARRAY(f, tag, base, au64, unsigned long long, fail);
+            break;
+        case MRP_MSG_FIELD_SINT64:
+            CREATE_ARRAY(f, tag, base, as64, long long, fail);
+            break;
+        case MRP_MSG_FIELD_DOUBLE:
+            CREATE_ARRAY(f, tag, base, adbl, double, fail);
+            break;
+        default:
+            errno = EINVAL;
+            goto fail;
+        }
+        break;
+    }
+
+    return f;
+
+ fail:
+    destroy_field(f);
+    return NULL;
+
+#undef CREATE
+#undef CREATE_ARRAY
+}
+
+
+static void msg_destroy(mrp_msg_t *msg)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_msg_field_t *f;
+
+    if (msg != NULL) {
+        mrp_list_foreach(&msg->fields, p, n) {
+            f = mrp_list_entry(p, typeof(*f), hook);
+            destroy_field(f);
+        }
+
+        mrp_free(msg);
+    }
+}
+
+
+mrp_msg_t *mrp_msg_createv(uint16_t tag, va_list ap)
+{
+    mrp_msg_t       *msg;
+    mrp_msg_field_t *f;
+    va_list          aq;
+
+    va_copy(aq, ap);
+    if ((msg = mrp_allocz(sizeof(*msg))) != NULL) {
+        mrp_list_init(&msg->fields);
+        mrp_refcnt_init(&msg->refcnt);
+
+        while (tag != MRP_MSG_FIELD_INVALID) {
+            f = create_field(tag, &aq);
+
+            if (f != NULL) {
+                mrp_list_append(&msg->fields, &f->hook);
+                msg->nfield++;
+            }
+            else {
+                msg_destroy(msg);
+                msg = NULL;
+                goto out;
+            }
+            tag = va_arg(aq, uint32_t);
+        }
+    }
+ out:
+    va_end(aq);
+
+    return msg;
+}
+
+
+mrp_msg_t *mrp_msg_create(uint16_t tag, ...)
+{
+    mrp_msg_t *msg;
+    va_list    ap;
+
+    va_start(ap, tag);
+    msg = mrp_msg_createv(tag, ap);
+    va_end(ap);
+
+    return msg;
+}
+
+
+mrp_msg_t *mrp_msg_ref(mrp_msg_t *msg)
+{
+    return mrp_ref_obj(msg, refcnt);
+}
+
+
+void mrp_msg_unref(mrp_msg_t *msg)
+{
+    if (mrp_unref_obj(msg, refcnt))
+            msg_destroy(msg);
+}
+
+
+int mrp_msg_append(mrp_msg_t *msg, uint16_t tag, ...)
+{
+    mrp_msg_field_t *f;
+    va_list          ap;
+
+    va_start(ap, tag);
+    f = create_field(tag, &ap);
+    va_end(ap);
+
+    if (f != NULL) {
+        mrp_list_append(&msg->fields, &f->hook);
+        msg->nfield++;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_msg_prepend(mrp_msg_t *msg, uint16_t tag, ...)
+{
+    mrp_msg_field_t *f;
+    va_list          ap;
+
+    va_start(ap, tag);
+    f = create_field(tag, &ap);
+    va_end(ap);
+
+    if (f != NULL) {
+        mrp_list_prepend(&msg->fields, &f->hook);
+        msg->nfield++;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_msg_set(mrp_msg_t *msg, uint16_t tag, ...)
+{
+    mrp_msg_field_t *of, *nf;
+    va_list          ap;
+
+    of = mrp_msg_find(msg, tag);
+
+    if (of != NULL) {
+        va_start(ap, tag);
+        nf = create_field(tag, &ap);
+        va_end(ap);
+
+        if (nf != NULL) {
+            mrp_list_append(&of->hook, &nf->hook);
+            destroy_field(of);
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+int mrp_msg_iterate(mrp_msg_t *msg, void **it, uint16_t *tagp, uint16_t *typep,
+                    mrp_msg_value_t *valp, size_t *sizep)
+{
+    mrp_list_hook_t *p = *(mrp_list_hook_t **)it;
+    mrp_msg_field_t *f;
+
+    if (p == NULL)
+        p = msg->fields.next;
+
+    if (p == &msg->fields)
+        return FALSE;
+
+    f = mrp_list_entry(p, typeof(*f), hook);
+
+    *tagp  = f->tag;
+    *typep = f->type;
+
+    switch (f->type) {
+#define HANDLE_TYPE(type, member)                       \
+        case MRP_MSG_FIELD_##type:                      \
+            valp->member = f->member;                   \
+            if (sizep != NULL)                          \
+                *sizep = sizeof(typeof(f->member));     \
+            break
+
+        HANDLE_TYPE(BOOL  , bln);
+        HANDLE_TYPE(UINT8 , u8);
+        HANDLE_TYPE(SINT8 , s8);
+        HANDLE_TYPE(UINT16, u16);
+        HANDLE_TYPE(SINT16, s16);
+        HANDLE_TYPE(UINT32, u32);
+        HANDLE_TYPE(SINT32, s32);
+        HANDLE_TYPE(UINT64, u64);
+        HANDLE_TYPE(SINT64, s64);
+        HANDLE_TYPE(DOUBLE, dbl);
+
+    case MRP_MSG_FIELD_STRING:
+        valp->str = f->str;
+        if (sizep != NULL)
+            *sizep = strlen(f->str);
+        break;
+
+    case MRP_MSG_FIELD_BLOB:
+        valp->blb = f->blb;
+        if (sizep != NULL)
+            *sizep = (size_t)f->size[0];
+        break;
+
+    default:
+        if (f->type & MRP_MSG_FIELD_ARRAY) {
+            valp->aany = f->aany;
+            if (sizep != NULL)
+                *sizep = f->size[0];
+        }
+        else
+            return FALSE;
+#undef HANDLE_TYPE
+    }
+
+    *it = p->next;
+
+    return TRUE;
+}
+
+
+mrp_msg_field_t *mrp_msg_find(mrp_msg_t *msg, uint16_t tag)
+{
+    mrp_msg_field_t *f;
+    mrp_list_hook_t *p, *n;
+
+    mrp_list_foreach(&msg->fields, p, n) {
+        f = mrp_list_entry(p, typeof(*f), hook);
+        if (f->tag == tag)
+            return f;
+    }
+
+    return NULL;
+}
+
+
+int mrp_msg_get(mrp_msg_t *msg, ...)
+{
+#define HANDLE_TYPE(_type, _member)                       \
+    case MRP_MSG_FIELD_##_type:                           \
+        valp          = va_arg(ap, typeof(valp));         \
+        valp->_member = f->_member;                       \
+        break
+
+#define HANDLE_ARRAY(_type, _member)                      \
+    case MRP_MSG_FIELD_##_type:                           \
+        cntp          = va_arg(ap, typeof(cntp));         \
+        valp          = va_arg(ap, typeof(valp));         \
+        *cntp         = f->size[0];                       \
+        valp->_member = f->_member;                       \
+        break
+
+
+    mrp_msg_field_t *f;
+    mrp_msg_value_t *valp;
+    uint32_t        *cntp;
+    mrp_list_hook_t *start, *p;
+    uint16_t         tag, type;
+    int              found;
+    va_list          ap;
+
+    va_start(ap, msg);
+
+    /*
+     * Okay... this might look a bit weird at first sight. This is
+     * mostly because we don't use the standard list iterating macros
+     * in the inner loop. There is a good reason for that: we want to
+     * minimise the number of times we scan the message which is just
+     * a linked list of fields. We do this by arranging the nested
+     * loops below in such a way that if the order of fields to fetch
+     * in the argument list matches the order of fields in the message
+     * we end up running the outer and inner loops in a 'phase lock'.
+     * So if the caller fetches the fields in the correct order we end
+     * up scanning the message at most once but only up to the last
+     * field to fetch.
+     */
+
+    start = msg->fields.next;
+    found = FALSE;
+
+    while ((tag = va_arg(ap, unsigned int)) != MRP_MSG_FIELD_INVALID) {
+        type  = va_arg(ap, unsigned int);
+        found = FALSE;
+
+        for (p = start; p != start->prev; p = p->next) {
+            if (p == &msg->fields)
+                continue;
+
+            f = mrp_list_entry(p, typeof(*f), hook);
+
+            if (f->tag != tag)
+                continue;
+
+            if (f->type != type)
+                goto out;
+
+            switch (type) {
+                HANDLE_TYPE(STRING, str);
+                HANDLE_TYPE(BOOL  , bln);
+                HANDLE_TYPE(UINT8 , u8 );
+                HANDLE_TYPE(SINT8 , s8 );
+                HANDLE_TYPE(UINT16, u16);
+                HANDLE_TYPE(SINT16, s16);
+                HANDLE_TYPE(UINT32, u32);
+                HANDLE_TYPE(SINT32, s32);
+                HANDLE_TYPE(UINT64, u64);
+                HANDLE_TYPE(SINT64, s64);
+                HANDLE_TYPE(DOUBLE, dbl);
+            default:
+                if (type & MRP_MSG_FIELD_ARRAY) {
+                    switch (type & ~MRP_MSG_FIELD_ARRAY) {
+                        HANDLE_ARRAY(STRING, astr);
+                        HANDLE_ARRAY(BOOL  , abln);
+                        HANDLE_ARRAY(UINT8 , au8 );
+                        HANDLE_ARRAY(SINT8 , as8 );
+                        HANDLE_ARRAY(UINT16, au16);
+                        HANDLE_ARRAY(SINT16, as16);
+                        HANDLE_ARRAY(UINT32, au32);
+                        HANDLE_ARRAY(SINT32, as32);
+                        HANDLE_ARRAY(UINT64, au64);
+                        HANDLE_ARRAY(SINT64, as64);
+                        HANDLE_ARRAY(DOUBLE, adbl);
+                    default:
+                        goto out;
+
+                    }
+                }
+                else
+                    goto out;
+            }
+
+            start = p->next;
+            found = TRUE;
+            break;
+        }
+
+        if (!found)
+            break;
+    }
+
+ out:
+    va_end(ap);
+
+    return found;
+
+#undef HANDLE_TYPE
+#undef HANDLE_ARRAY
+
+}
+
+
+int mrp_msg_iterate_get(mrp_msg_t *msg, void **it, ...)
+{
+#define HANDLE_TYPE(_type, _member)                       \
+    case MRP_MSG_FIELD_##_type:                           \
+        valp          = va_arg(ap, typeof(valp));         \
+        valp->_member = f->_member;                       \
+        break
+
+#define HANDLE_ARRAY(_type, _member)                      \
+    case MRP_MSG_FIELD_##_type:                           \
+        cntp          = va_arg(ap, typeof(cntp));         \
+        valp          = va_arg(ap, typeof(valp));         \
+        *cntp         = f->size[0];                       \
+        valp->_member = f->_member;                       \
+        break
+
+#define ANY_TYPE(_type, _member)                \
+    case MRP_MSG_FIELD_##_type:                 \
+        valp->_member = f->_member;             \
+        break
+
+    mrp_msg_field_t *f;
+    mrp_msg_value_t *valp;
+    uint32_t        *cntp;
+    mrp_list_hook_t *start, *p;
+    uint16_t         tag, type, *typep;
+    int              found;
+    va_list          ap;
+
+    va_start(ap, it);
+
+    /*
+     * Okay... this might look a bit weird at first sight. This is
+     * mostly because we don't use the standard list iterating macros
+     * in the inner loop. There is a good reason for that: we want to
+     * minimise the number of times we scan the message which is just
+     * a linked list of fields. We do this by arranging the nested
+     * loops below in such a way that if the order of fields to fetch
+     * in the argument list matches the order of fields in the message
+     * we end up running the outer and inner loops in a 'phase lock'.
+     * So if the caller fetches the fields in the correct order we end
+     * up scanning the message at most once but only up to the last
+     * field to fetch.
+     */
+
+    start = (*it) ? (mrp_list_hook_t *)*it : msg->fields.next;
+    found = FALSE;
+
+    while ((tag = va_arg(ap, unsigned int)) != MRP_MSG_FIELD_INVALID) {
+        type  = va_arg(ap, unsigned int);
+        found = FALSE;
+
+        if (type == MRP_MSG_FIELD_ANY) {
+            typep = va_arg(ap, uint16_t *);
+            valp  = va_arg(ap, mrp_msg_value_t *);
+        }
+        else {
+            typep = NULL;
+            valp  = NULL;
+        }
+
+        for (p = start; p != start->prev; p = p->next) {
+            if (p == &msg->fields)
+                continue;
+
+            f = mrp_list_entry(p, typeof(*f), hook);
+
+            if (f->tag != tag)
+                continue;
+
+            if (type == MRP_MSG_FIELD_ANY) {
+                *typep = f->type;
+                switch (f->type) {
+                ANY_TYPE(STRING, str);
+                ANY_TYPE(BOOL  , bln);
+                ANY_TYPE(UINT8 , u8 );
+                ANY_TYPE(SINT8 , s8 );
+                ANY_TYPE(UINT16, u16);
+                ANY_TYPE(SINT16, s16);
+                ANY_TYPE(UINT32, u32);
+                ANY_TYPE(SINT32, s32);
+                ANY_TYPE(UINT64, u64);
+                ANY_TYPE(SINT64, s64);
+                ANY_TYPE(DOUBLE, dbl);
+                default:
+                    mrp_log_error("XXX TODO: currently cannot fetch array "
+                                  "message fields with iterators.");
+                }
+
+                goto next;
+            }
+
+            if (f->type != type)
+                goto out;
+
+            switch (type) {
+                HANDLE_TYPE(STRING, str);
+                HANDLE_TYPE(BOOL  , bln);
+                HANDLE_TYPE(UINT8 , u8 );
+                HANDLE_TYPE(SINT8 , s8 );
+                HANDLE_TYPE(UINT16, u16);
+                HANDLE_TYPE(SINT16, s16);
+                HANDLE_TYPE(UINT32, u32);
+                HANDLE_TYPE(SINT32, s32);
+                HANDLE_TYPE(UINT64, u64);
+                HANDLE_TYPE(SINT64, s64);
+                HANDLE_TYPE(DOUBLE, dbl);
+            default:
+                if (type & MRP_MSG_FIELD_ARRAY) {
+                    switch (type & ~MRP_MSG_FIELD_ARRAY) {
+                        HANDLE_ARRAY(STRING, astr);
+                        HANDLE_ARRAY(BOOL  , abln);
+                        HANDLE_ARRAY(UINT8 , au8 );
+                        HANDLE_ARRAY(SINT8 , as8 );
+                        HANDLE_ARRAY(UINT16, au16);
+                        HANDLE_ARRAY(SINT16, as16);
+                        HANDLE_ARRAY(UINT32, au32);
+                        HANDLE_ARRAY(SINT32, as32);
+                        HANDLE_ARRAY(UINT64, au64);
+                        HANDLE_ARRAY(SINT64, as64);
+                        HANDLE_ARRAY(DOUBLE, adbl);
+                    default:
+                        goto out;
+
+                    }
+                }
+                else
+                    goto out;
+            }
+
+        next:
+            start = p->next;
+            found = TRUE;
+            break;
+        }
+
+        if (!found)
+            break;
+    }
+
+ out:
+    va_end(ap);
+
+    if (found)
+        *it = start;
+
+    return found;
+
+#undef HANDLE_TYPE
+#undef HANDLE_ARRAY
+
+}
+
+
+static const char *field_type_name(uint16_t type)
+{
+#define BASIC(t, n) [MRP_MSG_FIELD_##t] = n
+#define ARRAY(t, n) [MRP_MSG_FIELD_##t] = "array of "n"s"
+    static const char *basic[] = {
+        BASIC(STRING, "string" ),
+        BASIC(BOOL  , "boolean"),
+        BASIC(UINT8 , "uint8"  ),
+        BASIC(SINT8 , "sint8"  ),
+        BASIC(UINT16, "uint16" ),
+        BASIC(SINT16, "sint16" ),
+        BASIC(UINT32, "uint32" ),
+        BASIC(SINT32, "sint32" ),
+        BASIC(UINT64, "uint64" ),
+        BASIC(SINT64, "sint64" ),
+        BASIC(DOUBLE, "double" ),
+        BASIC(BLOB  , "blob"   )
+    };
+
+    static const char *array[] = {
+        ARRAY(STRING, "string" ),
+        ARRAY(BOOL  , "boolean"),
+        ARRAY(UINT8 , "uint8"  ),
+        ARRAY(SINT8 , "sint8"  ),
+        ARRAY(UINT16, "uint16" ),
+        ARRAY(SINT16, "sint16" ),
+        ARRAY(UINT32, "uint32" ),
+        ARRAY(SINT32, "sint32" ),
+        ARRAY(UINT64, "uint64" ),
+        ARRAY(SINT64, "sint64" ),
+        ARRAY(DOUBLE, "double" ),
+        ARRAY(BLOB  , "blob"   )
+    };
+#undef BASIC
+#undef ARRAY
+
+    uint16_t base;
+
+    if (MRP_MSG_FIELD_INVALID < type && type <= MRP_MSG_FIELD_MAX)
+        return basic[type];
+    else {
+        if (type & MRP_MSG_FIELD_ARRAY) {
+            base = type & ~MRP_MSG_FIELD_ARRAY;
+
+            if (MRP_MSG_FIELD_INVALID < base && base <= MRP_MSG_FIELD_MAX)
+                return array[base];
+        }
+    }
+
+    return "unknown type";
+}
+
+
+int mrp_msg_dump(mrp_msg_t *msg, FILE *fp)
+{
+    mrp_msg_field_t *f;
+    mrp_list_hook_t *p, *n;
+    int              l;
+    uint32_t         i;
+    uint16_t         base;
+    const char      *tname;
+
+    if (msg == NULL)
+        return fprintf(fp, "{\n    <no message>\n}\n");
+
+    l = fprintf(fp, "{\n");
+    mrp_list_foreach(&msg->fields, p, n) {
+        f = mrp_list_entry(p, typeof(*f), hook);
+
+        l += fprintf(fp, "    0x%x ", f->tag);
+
+#define DUMP(_indent, _fmt, _typename, _val)                              \
+        l += fprintf(fp, "%*.*s= <%s> "_fmt"\n", _indent, _indent, "",    \
+                     _typename, _val)
+
+        tname = field_type_name(f->type);
+        switch (f->type) {
+        case MRP_MSG_FIELD_STRING:
+            DUMP(0, "'%s'", tname, f->str);
+            break;
+        case MRP_MSG_FIELD_BOOL:
+            DUMP(0, "%s", tname, f->bln ? "true" : "false");
+            break;
+        case MRP_MSG_FIELD_UINT8:
+            DUMP(0, "%u", tname, f->u8);
+            break;
+        case MRP_MSG_FIELD_SINT8:
+            DUMP(0, "%d", tname, f->s8);
+            break;
+        case MRP_MSG_FIELD_UINT16:
+            DUMP(0, "%u", tname, f->u16);
+            break;
+        case MRP_MSG_FIELD_SINT16:
+            DUMP(0, "%d", tname, f->s16);
+            break;
+        case MRP_MSG_FIELD_UINT32:
+            DUMP(0, "%u", tname, f->u32);
+            break;
+        case MRP_MSG_FIELD_SINT32:
+            DUMP(0, "%d", tname, f->s32);
+            break;
+        case MRP_MSG_FIELD_UINT64:
+            DUMP(0, "%Lu", tname, (long long unsigned)f->u64);
+            break;
+        case MRP_MSG_FIELD_SINT64:
+            DUMP(0, "%Ld", tname, (long long signed)f->s64);
+            break;
+        case MRP_MSG_FIELD_DOUBLE:
+            DUMP(0, "%f", tname, f->dbl);
+            break;
+        case MRP_MSG_FIELD_BLOB: {
+            char     *p;
+            uint32_t  i;
+
+            fprintf(fp, "= <%s> <%u bytes, ", tname, f->size[0]);
+
+            for (i = 0, p = f->blb; i < f->size[0]; i++, p++) {
+                if (isprint(*p) && *p != '\n' && *p != '\t' && *p != '\r')
+                    fprintf(fp, "%c", *p);
+                else
+                    fprintf(fp, ".");
+            }
+            fprintf(fp, ">\n");
+        }
+            break;
+
+        default:
+            if (f->type & MRP_MSG_FIELD_ARRAY) {
+                base  = f->type & ~MRP_MSG_FIELD_ARRAY;
+                tname = field_type_name(base);
+
+                fprintf(fp, "\n");
+                for (i = 0; i < f->size[0]; i++) {
+                    switch (base) {
+                    case MRP_MSG_FIELD_STRING:
+                        DUMP(8, "'%s'", tname, f->astr[i]);
+                        break;
+                    case MRP_MSG_FIELD_BOOL:
+                        DUMP(8, "%s", tname, f->abln[i] ? "true" : "false");
+                        break;
+                    case MRP_MSG_FIELD_UINT8:
+                        DUMP(8, "%u", tname, f->au8[i]);
+                        break;
+                    case MRP_MSG_FIELD_SINT8:
+                        DUMP(8, "%d", tname, f->as8[i]);
+                        break;
+                    case MRP_MSG_FIELD_UINT16:
+                        DUMP(8, "%u", tname, f->au16[i]);
+                        break;
+                    case MRP_MSG_FIELD_SINT16:
+                        DUMP(8, "%d", tname, f->as16[i]);
+                        break;
+                    case MRP_MSG_FIELD_UINT32:
+                        DUMP(8, "%u", tname, f->au32[i]);
+                        break;
+                    case MRP_MSG_FIELD_SINT32:
+                        DUMP(8, "%d", tname, f->as32[i]);
+                        break;
+                    case MRP_MSG_FIELD_UINT64:
+                        DUMP(8, "%Lu", tname,
+                             (unsigned long long)f->au64[i]);
+                        break;
+                    case MRP_MSG_FIELD_SINT64:
+                        DUMP(8, "%Ld", tname,
+                             (long long)f->as64[i]);
+                        break;
+                    case MRP_MSG_FIELD_DOUBLE:
+                        DUMP(8, "%f", tname, f->adbl[i]);
+                        break;
+                    default:
+                        fprintf(fp, "%*.*s= <%s>\n", 8, 8, "", tname);
+                        break;
+                    }
+                }
+            }
+            else
+                fprintf(fp, "= <%s>\n", tname);
+        }
+    }
+    l += fprintf(fp, "}\n");
+
+    return l;
+#undef DUMP
+}
+
+
+#define MSG_MIN_CHUNK 32
+
+ssize_t mrp_msg_default_encode(mrp_msg_t *msg, void **bufp)
+{
+    mrp_msg_field_t *f;
+    mrp_list_hook_t *p, *n;
+    mrp_msgbuf_t     mb;
+    uint32_t         len, asize, i;
+    uint16_t         type;
+    size_t           size;
+
+    size = msg->nfield * (2 * sizeof(uint16_t) + sizeof(uint64_t));
+
+    if (mrp_msgbuf_write(&mb, size)) {
+        MRP_MSGBUF_PUSH(&mb, htobe16(MRP_MSG_TAG_DEFAULT), 1, nomem);
+        MRP_MSGBUF_PUSH(&mb, htobe16(msg->nfield), 1, nomem);
+
+        mrp_list_foreach(&msg->fields, p, n) {
+            f = mrp_list_entry(p, typeof(*f), hook);
+
+            MRP_MSGBUF_PUSH(&mb, htobe16(f->tag) , 1, nomem);
+            MRP_MSGBUF_PUSH(&mb, htobe16(f->type), 1, nomem);
+
+            switch (f->type) {
+            case MRP_MSG_FIELD_STRING:
+                len = strlen(f->str) + 1;
+                MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem);
+                MRP_MSGBUF_PUSH_DATA(&mb, f->str, len, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_BOOL:
+                MRP_MSGBUF_PUSH(&mb, htobe32(f->bln ? TRUE : FALSE), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT8:
+                MRP_MSGBUF_PUSH(&mb, f->u8, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT8:
+                MRP_MSGBUF_PUSH(&mb, f->s8, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT16:
+                MRP_MSGBUF_PUSH(&mb, htobe16(f->u16), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT16:
+                MRP_MSGBUF_PUSH(&mb, htobe16(f->s16), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT32:
+                MRP_MSGBUF_PUSH(&mb, htobe32(f->u32), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT32:
+                MRP_MSGBUF_PUSH(&mb, htobe32(f->s32), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT64:
+                MRP_MSGBUF_PUSH(&mb, htobe64(f->u64), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT64:
+                MRP_MSGBUF_PUSH(&mb, htobe64(f->s64), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_DOUBLE:
+                MRP_MSGBUF_PUSH(&mb, f->dbl, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_BLOB:
+                len   = f->size[0];
+                MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem);
+                MRP_MSGBUF_PUSH_DATA(&mb, f->blb, len, 1, nomem);
+                break;
+
+            default:
+                if (f->type & MRP_MSG_FIELD_ARRAY) {
+                    type  = f->type & ~(MRP_MSG_FIELD_ARRAY);
+                    asize = f->size[0];
+                    MRP_MSGBUF_PUSH(&mb, htobe32(asize), 1, nomem);
+
+                    for (i = 0; i < asize; i++) {
+                        switch (type) {
+                        case MRP_MSG_FIELD_STRING:
+                            len = strlen(f->astr[i]) + 1;
+                            MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem);
+                            MRP_MSGBUF_PUSH_DATA(&mb, f->astr[i], len,
+                                                 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_BOOL:
+                            MRP_MSGBUF_PUSH(&mb, htobe32(f->abln[i]?TRUE:FALSE),
+                                            1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT8:
+                            MRP_MSGBUF_PUSH(&mb, f->au8[i], 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT8:
+                            MRP_MSGBUF_PUSH(&mb, f->as8[i], 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT16:
+                            MRP_MSGBUF_PUSH(&mb, htobe16(f->au16[i]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT16:
+                            MRP_MSGBUF_PUSH(&mb, htobe16(f->as16[i]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT32:
+                            MRP_MSGBUF_PUSH(&mb, htobe32(f->au32[i]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT32:
+                            MRP_MSGBUF_PUSH(&mb, htobe32(f->as32[i]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT64:
+                            MRP_MSGBUF_PUSH(&mb, htobe64(f->au64[i]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT64:
+                            MRP_MSGBUF_PUSH(&mb, htobe64(f->as64[i]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_DOUBLE:
+                            MRP_MSGBUF_PUSH(&mb, f->adbl[i], 1, nomem);
+                            break;
+
+                        default:
+                            goto invalid_type;
+                        }
+                    }
+                }
+                else {
+                invalid_type:
+                    errno = EINVAL;
+                    mrp_msgbuf_cancel(&mb);
+                nomem:
+                    *bufp = NULL;
+                    return -1;
+                }
+            }
+        }
+    }
+
+    *bufp = mb.buf;
+    return mb.p - mb.buf;
+}
+
+
+mrp_msg_t *mrp_msg_default_decode(void *buf, size_t size)
+{
+    mrp_msg_t       *msg;
+    mrp_msgbuf_t     mb;
+    mrp_msg_value_t  v;
+    void            *value;
+    uint16_t         nfield, tag, type, base;
+    uint32_t         len, n, i, j;
+
+    msg = mrp_msg_create_empty();
+
+    if (msg == NULL)
+        return NULL;
+
+    mrp_msgbuf_read(&mb, buf, size);
+
+    nfield = be16toh(MRP_MSGBUF_PULL(&mb, typeof(nfield), 1, nodata));
+
+    for (i = 0; i < nfield; i++) {
+        tag  = be16toh(MRP_MSGBUF_PULL(&mb, typeof(tag) , 1, nodata));
+        type = be16toh(MRP_MSGBUF_PULL(&mb, typeof(type), 1, nodata));
+
+        switch (type) {
+        case MRP_MSG_FIELD_STRING:
+            len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata));
+            if (len > 0)
+                value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+            else
+                value = "";
+            if (!mrp_msg_append(msg, tag, type, value))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_BOOL:
+            v.bln = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.bln))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_UINT8:
+            v.u8 = MRP_MSGBUF_PULL(&mb, typeof(v.u8), 1, nodata);
+            if (!mrp_msg_append(msg, tag, type, v.u8))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_SINT8:
+            v.s8 = MRP_MSGBUF_PULL(&mb, typeof(v.s8), 1, nodata);
+            if (!mrp_msg_append(msg, tag, type, v.s8))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_UINT16:
+            v.u16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.u16), 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.u16))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_SINT16:
+            v.s16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.s16), 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.s16))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_UINT32:
+            v.u32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.u32), 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.u32))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_SINT32:
+            v.s32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.s32), 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.s32))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_UINT64:
+            v.u64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.u64), 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.u64))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_SINT64:
+            v.s64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.s64), 1, nodata));
+            if (!mrp_msg_append(msg, tag, type, v.s64))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_DOUBLE:
+            v.dbl = MRP_MSGBUF_PULL(&mb, typeof(v.dbl), 1, nodata);
+            if (!mrp_msg_append(msg, tag, type, v.dbl))
+                goto fail;
+            break;
+
+        case MRP_MSG_FIELD_BLOB:
+            len   = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata));
+            value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+            if (!mrp_msg_append(msg, tag, type, len, value))
+                goto fail;
+            break;
+
+        default:
+            if (!(type & MRP_MSG_FIELD_ARRAY)) {
+                errno = EINVAL;
+                goto fail;
+            }
+
+            base  = type & ~MRP_MSG_FIELD_ARRAY;
+            n     = be32toh(MRP_MSGBUF_PULL(&mb, typeof(n), 1, nodata));
+            {
+                char    *astr[n];
+                bool     abln[n];
+                uint8_t  au8 [n];
+                int8_t   as8 [n];
+                uint16_t au16[n];
+                int16_t  as16[n];
+                uint32_t au32[n];
+                int32_t  as32[n];
+                uint64_t au64[n];
+                int64_t  as64[n];
+                double   adbl[n];
+
+                for (j = 0; j < n; j++) {
+
+                    switch (base) {
+                    case MRP_MSG_FIELD_STRING:
+                        len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len),
+                                                      1, nodata));
+                        if (len > 0)
+                            astr[j] = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+                        else
+                            astr[j] = "";
+                        break;
+
+                    case MRP_MSG_FIELD_BOOL:
+                        abln[j] = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1,
+                                                          nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_UINT8:
+                        au8[j] = MRP_MSGBUF_PULL(&mb, typeof(v.u8), 1, nodata);
+                        break;
+
+                    case MRP_MSG_FIELD_SINT8:
+                        as8[j] = MRP_MSGBUF_PULL(&mb, typeof(v.s8), 1, nodata);
+                        break;
+
+                    case MRP_MSG_FIELD_UINT16:
+                        au16[j] = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.u16),
+                                                          1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_SINT16:
+                        as16[j] = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v.s16),
+                                                          1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_UINT32:
+                        au32[j] = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.u32),
+                                                          1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_SINT32:
+                        as32[j] = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v.s32),
+                                                          1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_UINT64:
+                        au64[j] = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.u64),
+                                                          1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_SINT64:
+                        as64[j] = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v.s64),
+                                                          1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_DOUBLE:
+                        adbl[j] = MRP_MSGBUF_PULL(&mb, typeof(v.dbl),
+                                                  1, nodata);
+                    break;
+
+                    default:
+                        errno = EINVAL;
+                        goto fail;
+                    }
+                }
+
+#define HANDLE_TYPE(_type, _var)                                          \
+                case _type:                                               \
+                    if (!mrp_msg_append(msg, tag,                         \
+                                        MRP_MSG_FIELD_ARRAY |_type,       \
+                                        n, _var))                         \
+                        goto fail;                                        \
+                    break
+
+                switch (base) {
+                    HANDLE_TYPE(MRP_MSG_FIELD_STRING, astr);
+                    HANDLE_TYPE(MRP_MSG_FIELD_BOOL  , abln);
+                    HANDLE_TYPE(MRP_MSG_FIELD_UINT8 , au8 );
+                    HANDLE_TYPE(MRP_MSG_FIELD_SINT8 , as8 );
+                    HANDLE_TYPE(MRP_MSG_FIELD_UINT16, au16);
+                    HANDLE_TYPE(MRP_MSG_FIELD_SINT16, as16);
+                    HANDLE_TYPE(MRP_MSG_FIELD_UINT32, au32);
+                    HANDLE_TYPE(MRP_MSG_FIELD_SINT32, as32);
+                    HANDLE_TYPE(MRP_MSG_FIELD_UINT64, au64);
+                    HANDLE_TYPE(MRP_MSG_FIELD_SINT64, as64);
+                    HANDLE_TYPE(MRP_MSG_FIELD_DOUBLE, adbl);
+                default:
+                    errno = EINVAL;
+                    goto fail;
+                }
+#undef HANDLE_TYPE
+            }
+        }
+    }
+
+    return msg;
+
+
+ fail:
+ nodata:
+    mrp_msg_unref(msg);
+    return NULL;
+}
+
+
+static int guarded_array_size(void *data, mrp_data_member_t *array)
+{
+#define MAX_ITEMS (32 * 1024)
+    uint16_t  base;
+    void     *value, *guard;
+    size_t    size;
+    int       cnt;
+
+    if (array->type & MRP_MSG_FIELD_ARRAY) {
+        base = array->type & ~MRP_MSG_FIELD_ARRAY;
+
+        switch (base) {
+        case MRP_MSG_FIELD_STRING: size = sizeof(array->str);  break;
+        case MRP_MSG_FIELD_BOOL:   size = sizeof(array->bln);  break;
+        case MRP_MSG_FIELD_UINT8:  size = sizeof(array->u8);   break;
+        case MRP_MSG_FIELD_SINT8:  size = sizeof(array->s8);   break;
+        case MRP_MSG_FIELD_UINT16: size = sizeof(array->u16);  break;
+        case MRP_MSG_FIELD_SINT16: size = sizeof(array->s16);  break;
+        case MRP_MSG_FIELD_UINT32: size = sizeof(array->u32);  break;
+        case MRP_MSG_FIELD_SINT32: size = sizeof(array->s32);  break;
+        case MRP_MSG_FIELD_UINT64: size = sizeof(array->u64);  break;
+        case MRP_MSG_FIELD_SINT64: size = sizeof(array->s64);  break;
+        case MRP_MSG_FIELD_DOUBLE: size = sizeof(array->dbl);  break;
+        default:                                               return -1;
+        }
+
+        guard = &array->str;
+        value = *(void **)(data + array->offs);
+        for (cnt = 0; cnt < MAX_ITEMS; cnt++, value += size) {
+            if (!memcmp(value, guard, size))
+                return cnt + 1;
+        }
+    }
+
+    return -1;
+#undef MAX_ITEMS
+}
+
+
+static int counted_array_size(void *data, mrp_data_member_t *cnt)
+{
+    void *val = data + cnt->offs;
+
+    switch (cnt->type) {
+    case MRP_MSG_FIELD_UINT8:  return (int)*(uint8_t  *)val;
+    case MRP_MSG_FIELD_SINT8:  return (int)*( int8_t  *)val;
+    case MRP_MSG_FIELD_UINT16: return (int)*(uint16_t *)val;
+    case MRP_MSG_FIELD_SINT16: return (int)*( int16_t *)val;
+    case MRP_MSG_FIELD_UINT32: return (int)*(uint32_t *)val;
+    case MRP_MSG_FIELD_SINT32: return (int)*( int32_t *)val;
+    }
+
+    return -1;
+}
+
+
+static int get_array_size(void *data, mrp_data_descr_t *type, int idx)
+{
+    mrp_data_member_t *arr;
+
+    if (0 < idx && idx < type->nfield) {
+        arr = type->fields + idx;
+
+        if (arr->type & MRP_MSG_FIELD_ARRAY) {
+            if (arr->guard)
+                return guarded_array_size(data, arr);
+            else {
+                if ((int)arr->u32 < type->nfield)
+                    return counted_array_size(data, type->fields + arr->u32);
+            }
+        }
+    }
+
+    return -1;
+}
+
+
+int mrp_data_get_array_size(void *data, mrp_data_descr_t *type, int idx)
+{
+    return get_array_size(data, type, idx);
+}
+
+
+static int get_blob_size(void *data, mrp_data_descr_t *type, int idx)
+{
+    mrp_data_member_t *blb, *cnt;
+    void              *val;
+
+    if (0 < idx && idx < type->nfield) {
+        blb = type->fields + idx;
+
+        if ((int)blb->u32 < type->nfield) {
+            cnt = type->fields + blb->u32;
+            val = data + cnt->offs;
+
+            switch (cnt->type) {
+            case MRP_MSG_FIELD_UINT8:  return (int)*(uint8_t  *)val;
+            case MRP_MSG_FIELD_SINT8:  return (int)*( int8_t  *)val;
+            case MRP_MSG_FIELD_UINT16: return (int)*(uint16_t *)val;
+            case MRP_MSG_FIELD_SINT16: return (int)*( int16_t *)val;
+            case MRP_MSG_FIELD_UINT32: return (int)*(uint32_t *)val;
+            case MRP_MSG_FIELD_SINT32: return (int)*( int32_t *)val;
+            }
+        }
+    }
+
+    return -1;
+}
+
+
+int mrp_data_get_blob_size(void *data, mrp_data_descr_t *type, int idx)
+{
+    return get_blob_size(data, type, idx);
+}
+
+
+static int check_and_init_array_descr(mrp_data_descr_t *type, int idx)
+{
+    mrp_data_member_t *array, *cnt, *m;
+    int                i;
+
+    array = type->fields + idx;
+
+    if (!array->guard) {
+        cnt = NULL;
+
+        for (i = 0, m = type->fields; i < type->nfield; i++, m++) {
+            if (m->offs == array->u32) {
+                cnt = m;
+                break;
+            }
+        }
+
+        if (cnt == NULL || cnt >= array)
+            return FALSE;
+
+        if (cnt->type < MRP_MSG_FIELD_UINT8 || cnt->type > MRP_MSG_FIELD_SINT32)
+            return FALSE;
+
+        array->u32 = i;
+
+        return TRUE;
+    }
+    else {
+        return TRUE;
+    }
+}
+
+
+int mrp_msg_register_type(mrp_data_descr_t *type)
+{
+    mrp_data_member_t *f;
+    int                idx, i;
+
+    if (direct_types == NULL) {
+        direct_types = mrp_allocz_array(typeof(*direct_types), NDIRECT_TYPE);
+
+        if (direct_types == NULL)
+            return FALSE;
+    }
+
+    if (type->tag == MRP_MSG_TAG_DEFAULT) {
+        errno = EINVAL;
+        return FALSE;
+    }
+
+    mrp_list_init(&type->allocated);
+
+    /* enumerate fields, check arrays, collect extra allocations */
+    for (i = 0, f = type->fields; i < type->nfield; i++, f++) {
+        f->tag = (uint16_t)i + 1;
+
+        if (f->type & MRP_MSG_FIELD_ARRAY) {
+            if (!check_and_init_array_descr(type, i))
+                return FALSE;
+
+            mrp_list_append(&type->allocated, &f->hook);
+        }
+        else {
+            switch (f->type) {
+            case MRP_MSG_FIELD_STRING:
+            case MRP_MSG_FIELD_BLOB:
+                mrp_list_append(&type->allocated, &f->hook);
+            }
+        }
+    }
+
+    if (type->tag <= NDIRECT_TYPE) {
+        idx = type->tag - 1;
+
+        if (direct_types[idx] == NULL)
+            direct_types[idx] = type;
+        else
+            return FALSE;
+    }
+    else {
+        if (mrp_reallocz(other_types, nother_type, nother_type + 1) != NULL)
+            other_types[nother_type++] = type;
+        else
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+mrp_data_descr_t *mrp_msg_find_type(uint16_t tag)
+{
+    int i;
+
+    if (MRP_UNLIKELY(tag == MRP_MSG_TAG_DEFAULT))
+        return NULL;
+
+    if (tag <= NDIRECT_TYPE)
+        return direct_types[tag - 1];
+    else {
+        for (i = 0; i < nother_type; i++) {
+            if (other_types[i] != NULL && other_types[i]->tag == tag)
+                return other_types[i];
+        }
+    }
+
+    return NULL;
+}
+
+
+static __attribute__((destructor)) void cleanup_types(void)
+{
+    mrp_free(direct_types);
+    mrp_free(other_types);
+    nother_type = 0;
+}
+
+
+size_t mrp_data_encode(void **bufp, void *data, mrp_data_descr_t *descr,
+                       size_t reserve)
+{
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    uint16_t           type;
+    mrp_msgbuf_t       mb;
+    mrp_msg_value_t   *v;
+    uint32_t           len, asize, blblen, j;
+    int                i, cnt;
+    size_t             size;
+
+    fields = descr->fields;
+    nfield = descr->nfield;
+    size   = reserve + nfield * (2 * sizeof(uint16_t) + sizeof(uint64_t));
+
+    if (mrp_msgbuf_write(&mb, size)) {
+        if (reserve)
+            mrp_msgbuf_reserve(&mb, reserve, 1);
+
+        for (i = 0, f = fields; i < nfield; i++, f++) {
+            MRP_MSGBUF_PUSH(&mb, htobe16(f->tag) , 1, nomem);
+
+            v = (mrp_msg_value_t *)(data + f->offs);
+
+            switch (f->type) {
+            case MRP_MSG_FIELD_STRING:
+                len = strlen(v->str) + 1;
+                MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem);
+                MRP_MSGBUF_PUSH_DATA(&mb, v->str, len, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_BOOL:
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->bln ? TRUE : FALSE), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT8:
+                MRP_MSGBUF_PUSH(&mb, v->u8, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT8:
+                MRP_MSGBUF_PUSH(&mb, v->s8, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT16:
+                MRP_MSGBUF_PUSH(&mb, htobe16(v->u16), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT16:
+                MRP_MSGBUF_PUSH(&mb, htobe16(v->s16), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT32:
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->u32), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT32:
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->s32), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT64:
+                MRP_MSGBUF_PUSH(&mb, htobe64(v->u64), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT64:
+                MRP_MSGBUF_PUSH(&mb, htobe64(v->s64), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_DOUBLE:
+                MRP_MSGBUF_PUSH(&mb, v->dbl, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_BLOB:
+                blblen = (uint32_t)get_blob_size(data, descr, i);
+
+                if (blblen == (uint32_t)-1)
+                    goto invalid_type;
+
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->u32), 1, nomem);
+                MRP_MSGBUF_PUSH_DATA(&mb, v->blb, blblen, 1, nomem);
+                break;
+
+            default:
+                if (f->type & MRP_MSG_FIELD_ARRAY) {
+                    type  = f->type & ~(MRP_MSG_FIELD_ARRAY);
+                    cnt   = get_array_size(data, descr, i);
+
+                    if (cnt < 0)
+                        goto invalid_type;
+
+                    asize = (uint32_t)cnt;
+                    MRP_MSGBUF_PUSH(&mb, htobe32(asize), 1, nomem);
+
+                    for (j = 0; j < asize; j++) {
+                        switch (type) {
+                        case MRP_MSG_FIELD_STRING:
+                            len = strlen(v->astr[j]) + 1;
+                            MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem);
+                            MRP_MSGBUF_PUSH_DATA(&mb, v->astr[j], len,
+                                                 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_BOOL:
+                            MRP_MSGBUF_PUSH(&mb, htobe32(v->abln[j]?TRUE:FALSE),
+                                            1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT8:
+                            MRP_MSGBUF_PUSH(&mb, v->au8[j], 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT8:
+                            MRP_MSGBUF_PUSH(&mb, v->as8[j], 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT16:
+                            MRP_MSGBUF_PUSH(&mb, htobe16(v->au16[j]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT16:
+                            MRP_MSGBUF_PUSH(&mb, htobe16(v->as16[j]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT32:
+                            MRP_MSGBUF_PUSH(&mb, htobe32(v->au32[j]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT32:
+                            MRP_MSGBUF_PUSH(&mb, htobe32(v->as32[j]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_UINT64:
+                            MRP_MSGBUF_PUSH(&mb, htobe64(v->au64[j]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_SINT64:
+                            MRP_MSGBUF_PUSH(&mb, htobe64(v->as64[j]), 1, nomem);
+                            break;
+
+                        case MRP_MSG_FIELD_DOUBLE:
+                            MRP_MSGBUF_PUSH(&mb, v->adbl[j], 1, nomem);
+                            break;
+
+                        default:
+                            goto invalid_type;
+                        }
+                    }
+                }
+                else {
+                invalid_type:
+                    errno = EINVAL;
+                    mrp_msgbuf_cancel(&mb);
+                nomem:
+                    *bufp = NULL;
+                    return 0;
+                }
+            }
+        }
+    }
+
+    *bufp = mb.buf;
+    return (size_t)(mb.p - mb.buf);
+}
+
+
+static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield,
+                                      uint16_t tag)
+{
+    mrp_data_member_t *f;
+    int                i;
+
+    for (i = 0, f = fields; i < nfield; i++, f++)
+        if (f->tag == tag)
+            return f;
+
+    return NULL;
+}
+
+
+void *mrp_data_decode(void **bufp, size_t *sizep, mrp_data_descr_t *descr)
+{
+    void              *data;
+    mrp_data_member_t *fields, *f;
+    int                nfield;
+    mrp_msgbuf_t       mb;
+    uint16_t           tag, base;
+    mrp_msg_value_t   *v;
+    void              *value;
+    uint32_t           len, n, j, size;
+    int                i;
+
+    fields = descr->fields;
+    nfield = descr->nfield;
+    data   = mrp_allocz(descr->size);
+
+    if (MRP_UNLIKELY(data == NULL))
+        return NULL;
+
+    mrp_msgbuf_read(&mb, *bufp, *sizep);
+
+    for (i = 0; i < nfield; i++) {
+        tag = be16toh(MRP_MSGBUF_PULL(&mb, typeof(tag) , 1, nodata));
+        f   = member_type(fields, nfield, tag);
+
+        if (MRP_UNLIKELY(f == NULL))
+            goto unknown_field;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (f->type) {
+        case MRP_MSG_FIELD_STRING:
+            len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata));
+            if (len > 0)
+                value  = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+            else
+                value = "";
+            v->str = mrp_strdup((char *)value);
+            if (v->str == NULL)
+                goto nomem;
+            break;
+
+        case MRP_MSG_FIELD_BOOL:
+            v->bln = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_UINT8:
+            v->u8 = MRP_MSGBUF_PULL(&mb, typeof(v->u8), 1, nodata);
+            break;
+
+        case MRP_MSG_FIELD_SINT8:
+            v->s8 = MRP_MSGBUF_PULL(&mb, typeof(v->s8), 1, nodata);
+            break;
+
+        case MRP_MSG_FIELD_UINT16:
+            v->u16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->u16), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_SINT16:
+            v->s16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->s16), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_UINT32:
+            v->u32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->u32), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_SINT32:
+            v->s32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->s32), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_UINT64:
+            v->u64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->u64), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_SINT64:
+            v->s64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->s64), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_DOUBLE:
+            v->dbl = MRP_MSGBUF_PULL(&mb, typeof(v->dbl), 1, nodata);
+            break;
+
+        case MRP_MSG_FIELD_BLOB:
+            len    = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata));
+            value  = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+            v->blb = mrp_datadup(value, len);
+            if (v->blb == NULL)
+                goto nomem;
+            break;
+
+        default:
+            if (!(f->type & MRP_MSG_FIELD_ARRAY)) {
+            unknown_field:
+                errno = EINVAL;
+                goto fail;
+            }
+
+            base  = f->type & ~MRP_MSG_FIELD_ARRAY;
+            n     = be32toh(MRP_MSGBUF_PULL(&mb, typeof(n), 1, nodata));
+
+            if (!f->guard && get_array_size(data, descr, i) != (int)n) {
+                errno = EINVAL;
+                goto fail;
+            }
+
+            size = n;
+
+            switch (base) {
+            case MRP_MSG_FIELD_STRING: size *= sizeof(*v->astr); break;
+            case MRP_MSG_FIELD_BOOL:   size *= sizeof(*v->abln); break;
+            case MRP_MSG_FIELD_UINT8:  size *= sizeof(*v->au8);  break;
+            case MRP_MSG_FIELD_SINT8:  size *= sizeof(*v->as8);  break;
+            case MRP_MSG_FIELD_UINT16: size *= sizeof(*v->au16); break;
+            case MRP_MSG_FIELD_SINT16: size *= sizeof(*v->as16); break;
+            case MRP_MSG_FIELD_UINT32: size *= sizeof(*v->au32); break;
+            case MRP_MSG_FIELD_SINT32: size *= sizeof(*v->as32); break;
+            case MRP_MSG_FIELD_UINT64: size *= sizeof(*v->au64); break;
+            case MRP_MSG_FIELD_SINT64: size *= sizeof(*v->as64); break;
+            case MRP_MSG_FIELD_DOUBLE: size *= sizeof(*v->adbl); break;
+            default:
+                errno = EINVAL;
+                goto fail;
+            }
+
+            v->aany = mrp_allocz(size);
+            if (v->aany == NULL)
+                goto nomem;
+
+            for (j = 0; j < n; j++) {
+                switch (base) {
+                case MRP_MSG_FIELD_STRING:
+                    len = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len),
+                                                  1, nodata));
+                    if (len > 0)
+                        value = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+                    else
+                        value = "";
+
+                    v->astr[j] = mrp_strdup(value);
+                    if (v->astr[j] == NULL)
+                        goto nomem;
+                    break;
+
+                    case MRP_MSG_FIELD_BOOL:
+                        v->abln[j] = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t,
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_UINT8:
+                        v->au8[j] = MRP_MSGBUF_PULL(&mb, typeof(v->u8),
+                                                    1, nodata);
+                        break;
+
+                    case MRP_MSG_FIELD_SINT8:
+                        v->as8[j] = MRP_MSGBUF_PULL(&mb, typeof(v->s8),
+                                                    1, nodata);
+                        break;
+
+                    case MRP_MSG_FIELD_UINT16:
+                        v->au16[j] = be16toh(MRP_MSGBUF_PULL(&mb,
+                                                             typeof(v->u16),
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_SINT16:
+                        v->as16[j] = be16toh(MRP_MSGBUF_PULL(&mb,
+                                                             typeof(v->s16),
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_UINT32:
+                        v->au32[j] = be32toh(MRP_MSGBUF_PULL(&mb,
+                                                             typeof(v->u32),
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_SINT32:
+                        v->as32[j] = be32toh(MRP_MSGBUF_PULL(&mb,
+                                                             typeof(v->s32),
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_UINT64:
+                        v->au64[j] = be64toh(MRP_MSGBUF_PULL(&mb,
+                                                             typeof(v->u64),
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_SINT64:
+                        v->as64[j] = be64toh(MRP_MSGBUF_PULL(&mb,
+                                                             typeof(v->s64),
+                                                             1, nodata));
+                        break;
+
+                    case MRP_MSG_FIELD_DOUBLE:
+                        v->adbl[j] = MRP_MSGBUF_PULL(&mb, typeof(v->dbl),
+                                                     1, nodata);
+                        break;
+
+                    default:
+                        errno = EINVAL;
+                        goto fail;
+                }
+            }
+        }
+    }
+
+    *bufp   = mb.buf;
+    *sizep -= mb.p - mb.buf;
+    return data;
+
+ nodata:
+ nomem:
+ fail:
+    if (data != NULL) {
+        for (i = 0, f = fields; i < nfield; i++, f++) {
+            switch (f->type) {
+            case MRP_MSG_FIELD_STRING:
+            case MRP_MSG_FIELD_BLOB:
+                mrp_free(*(void **)(data + f->offs));
+            }
+        }
+
+        mrp_free(data);
+    }
+
+    return NULL;
+}
+
+
+int mrp_data_dump(void *data, mrp_data_descr_t *descr, FILE *fp)
+{
+#define DUMP(_indent, _fmt, _typename, _val)                              \
+        l += fprintf(fp, "%*.*s= <%s> "_fmt"\n", _indent, _indent, "",    \
+                     _typename, _val)
+
+    mrp_data_member_t *dm;
+    mrp_msg_value_t   *v;
+    uint16_t           base;
+    int                i, j, l, cnt;
+    const char        *tname;
+
+
+    l = fprintf(fp, "{\n");
+    for (i = 0, dm = descr->fields; i < descr->nfield; i++, dm++) {
+        l     += fprintf(fp, "    @%d ", dm->offs);
+        v      = (mrp_msg_value_t *)(data + dm->offs);
+        tname  = field_type_name(dm->type);
+
+        switch (dm->type) {
+        case MRP_MSG_FIELD_STRING:
+            DUMP(0, "'%s'", tname, v->str);
+            break;
+        case MRP_MSG_FIELD_BOOL:
+            DUMP(0, "%s", tname, v->bln ? "true" : "false");
+            break;
+        case MRP_MSG_FIELD_UINT8:
+            DUMP(0, "%u", tname, v->u8);
+            break;
+        case MRP_MSG_FIELD_SINT8:
+            DUMP(0, "%d", tname, v->s8);
+            break;
+        case MRP_MSG_FIELD_UINT16:
+            DUMP(0, "%u", tname, v->u16);
+            break;
+        case MRP_MSG_FIELD_SINT16:
+            DUMP(0, "%d", tname, v->s16);
+            break;
+        case MRP_MSG_FIELD_UINT32:
+            DUMP(0, "%u", tname, v->u32);
+            break;
+        case MRP_MSG_FIELD_SINT32:
+            DUMP(0, "%d", tname, v->s32);
+            break;
+        case MRP_MSG_FIELD_UINT64:
+            DUMP(0, "%Lu", tname, (long long unsigned)v->u64);
+            break;
+        case MRP_MSG_FIELD_SINT64:
+            DUMP(0, "%Ld", tname, (long long signed)v->s64);
+            break;
+        case MRP_MSG_FIELD_DOUBLE:
+            DUMP(0, "%f", tname, v->dbl);
+            break;
+        default:
+            if (dm->type & MRP_MSG_FIELD_ARRAY) {
+                base  = dm->type & ~MRP_MSG_FIELD_ARRAY;
+                cnt   = get_array_size(data, descr, i);
+
+                if (cnt < 0) {
+                    fprintf(fp, "= <%s> ???\n", tname);
+                    continue;
+                }
+
+                fprintf(fp, "= <%s> (%d)\n", tname, cnt);
+                tname = field_type_name(base);
+
+                for (j = 0; j < cnt; j++) {
+                    switch (base) {
+                    case MRP_MSG_FIELD_STRING:
+                        DUMP(8, "'%s'", tname, v->astr[j]);
+                        break;
+                    case MRP_MSG_FIELD_BOOL:
+                        DUMP(8, "%s", tname, v->abln[j] ? "true" : "false");
+                        break;
+                    case MRP_MSG_FIELD_UINT8:
+                        DUMP(8, "%u", tname, v->au8[j]);
+                        break;
+                    case MRP_MSG_FIELD_SINT8:
+                        DUMP(8, "%d", tname, v->as8[j]);
+                        break;
+                    case MRP_MSG_FIELD_UINT16:
+                        DUMP(8, "%u", tname, v->au16[j]);
+                        break;
+                    case MRP_MSG_FIELD_SINT16:
+                        DUMP(8, "%d", tname, v->as16[j]);
+                        break;
+                    case MRP_MSG_FIELD_UINT32:
+                        DUMP(8, "%u", tname, v->au32[j]);
+                        break;
+                    case MRP_MSG_FIELD_SINT32:
+                        DUMP(8, "%d", tname, v->as32[j]);
+                        break;
+                    case MRP_MSG_FIELD_UINT64:
+                        DUMP(8, "%Lu", tname, (long long unsigned)v->au64[j]);
+                        break;
+                    case MRP_MSG_FIELD_SINT64:
+                        DUMP(8, "%Ld", tname, (long long signed)v->as64[j]);
+                        break;
+                    case MRP_MSG_FIELD_DOUBLE:
+                        DUMP(8, "%f", tname, v->adbl[j]);
+                        break;
+                    default:
+                        fprintf(fp, "%*.*s<%s>\n", 8, 8, "", tname);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    l += fprintf(fp, "}\n");
+
+    return l;
+}
+
+
+int mrp_data_free(void *data, uint16_t tag)
+{
+    mrp_data_descr_t  *type;
+    mrp_list_hook_t   *p, *n;
+    mrp_data_member_t *f;
+    void              *ptr;
+    int                i, idx, cnt;
+
+    if (data == NULL)
+        return TRUE;
+
+    type = mrp_msg_find_type(tag);
+
+    if (type != NULL) {
+        mrp_list_foreach(&type->allocated, p, n) {
+            f   = mrp_list_entry(p, typeof(*f), hook);
+            ptr = *(void **)(data + f->offs);
+
+            if (f->type == (MRP_MSG_FIELD_ARRAY | MRP_MSG_FIELD_STRING)) {
+                idx = f - type->fields;
+                cnt = get_array_size(data, type, idx);
+
+                for (i = 0; i < cnt; i++)
+                    mrp_free(((char **)ptr)[i]);
+            }
+
+            mrp_free(ptr);
+        }
+
+        mrp_free(data);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+void *mrp_msgbuf_write(mrp_msgbuf_t *mb, size_t size)
+{
+    mrp_clear(mb);
+
+    mb->buf = mrp_allocz(size);
+
+    if (mb->buf != NULL) {
+        mb->size = size;
+        mb->p    = mb->buf;
+        mb->l    = size;
+
+        return mb->p;
+    }
+    else
+        return NULL;
+}
+
+
+void mrp_msgbuf_read(mrp_msgbuf_t *mb, void *buf, size_t size)
+{
+    mb->buf  = mb->p = buf;
+    mb->size = mb->l = size;
+}
+
+
+void mrp_msgbuf_cancel(mrp_msgbuf_t *mb)
+{
+    mrp_free(mb->buf);
+    mb->buf = mb->p = NULL;
+}
+
+
+void *mrp_msgbuf_ensure(mrp_msgbuf_t *mb, size_t size)
+{
+    int diff;
+
+    if (MRP_UNLIKELY(size > mb->l)) {
+        diff = size - mb->l;
+
+        if (diff < MSG_MIN_CHUNK)
+            diff = MSG_MIN_CHUNK;
+
+        mb->p -= (ptrdiff_t)mb->buf;
+
+        if (mrp_realloc(mb->buf, mb->size + diff)) {
+            memset(mb->buf + mb->size, 0, diff);
+            mb->size += diff;
+            mb->p    += (ptrdiff_t)mb->buf;
+            mb->l    += diff;
+        }
+        else
+            mrp_msgbuf_cancel(mb);
+    }
+
+    return mb->p;
+}
+
+
+void *mrp_msgbuf_reserve(mrp_msgbuf_t *mb, size_t size, size_t align)
+{
+    void      *reserved;
+    ptrdiff_t  offs, pad;
+    size_t     len;
+
+    len  = size;
+    offs = mb->p - mb->buf;
+
+    if (offs % align != 0) {
+        pad  = align - (offs % align);
+        len += pad;
+    }
+    else
+        pad = 0;
+
+    if (mrp_msgbuf_ensure(mb, len)) {
+        if (pad != 0)
+            memset(mb->p, 0, pad);
+
+        reserved = mb->p + pad;
+
+        mb->p += len;
+        mb->l -= len;
+    }
+    else
+        reserved = NULL;
+
+    return reserved;
+}
+
+
+void *mrp_msgbuf_pull(mrp_msgbuf_t *mb, size_t size, size_t align)
+{
+    void      *pulled;
+    ptrdiff_t  offs, pad;
+    size_t     len;
+
+    len  = size;
+    offs = mb->p - mb->buf;
+
+    if (offs % align != 0) {
+        pad  = align - (offs % align);
+        len += pad;
+    }
+    else
+        pad = 0;
+
+    if (mb->l >= len) {
+        pulled = mb->p + pad;
+
+        mb->p += len;
+        mb->l -= len;
+    }
+    else
+        pulled = NULL;
+
+    return pulled;
+}
diff --git a/src/common/msg.h b/src/common/msg.h
new file mode 100644 (file)
index 0000000..e61805b
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_MSG_H__
+#define __MURPHY_MSG_H__
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <stdint.h>
+
+#include <murphy/common/list.h>
+#include <murphy/common/refcnt.h>
+
+MRP_CDECL_BEGIN
+
+/*
+ * message field types
+ */
+
+#define A(t) MRP_MSG_FIELD_##t
+typedef enum {
+    MRP_MSG_FIELD_INVALID  = 0x00,       /* defined invalid type */
+    MRP_MSG_FIELD_STRING   = 0x01,       /* mqi_varchar */
+    MRP_MSG_FIELD_INTEGER  = 0x02,       /* mqi_integer */
+    MRP_MSG_FIELD_UNSIGNED = 0x03,       /* mqi_unsignd */
+    MRP_MSG_FIELD_DOUBLE   = 0x04,       /* mqi_floating */
+    MRP_MSG_FIELD_BOOL     = 0x05,       /* boolean */
+    MRP_MSG_FIELD_UINT8    = 0x06,       /* unsigned 8-bit integer */
+    MRP_MSG_FIELD_SINT8    = 0x07,       /* signed 8-bit integer */
+    MRP_MSG_FIELD_INT8     = A(SINT8),   /* alias for SINT8 */
+    MRP_MSG_FIELD_UINT16   = 0x08,       /* unsigned 16-bit integer */
+    MRP_MSG_FIELD_SINT16   = 0x09,       /* signed 16-bit integer */
+    MRP_MSG_FIELD_INT16    = A(SINT16),  /* alias for SINT16 */
+    MRP_MSG_FIELD_UINT32   = 0x0a,       /* unsigned 32-bit integer */
+    MRP_MSG_FIELD_SINT32   = 0x0b,       /* signed 32-bit integer */
+    MRP_MSG_FIELD_INT32    = A(SINT32),  /* alias for SINT32 */
+    MRP_MSG_FIELD_UINT64   = 0x0c,       /* unsigned 64-bit integer */
+    MRP_MSG_FIELD_SINT64   = 0x0d,       /* signed 64-bit integer */
+    MRP_MSG_FIELD_INT64    = A(SINT64),  /* alias for SINT64 */
+    MRP_MSG_FIELD_BLOB     = 0x0e,       /* a blob (not allowed in arrays) */
+    MRP_MSG_FIELD_MAX      = 0x0e,
+    MRP_MSG_FIELD_ANY      = 0x0f,       /* any type of field when querying */
+
+    MRP_MSG_FIELD_ARRAY    = 0x80,       /* bit-mask to mark arrays */
+} mrp_msg_field_type_t;
+#undef A
+
+#define MRP_MSG_END ((char *)MRP_MSG_FIELD_INVALID) /* NULL */
+
+#define MRP_MSG_FIELD_ARRAY_OF(t)   (MRP_MSG_FIELD_ARRAY | MRP_MSG_FIELD_##t)
+#define MRP_MSG_FIELD_IS_ARRAY(t)   ((t) & MRP_MSG_FIELD_ARRAY)
+#define MRP_MSG_FIELD_ARRAY_TYPE(t) ((t) & ~MRP_MSG_FIELD_ARRAY)
+
+#define MRP_MSG_TAG_STRING(tag, arg) (tag), MRP_MSG_FIELD_STRING, (arg)
+#define MRP_MSG_TAG_BOOL(tag, arg)   (tag), MRP_MSG_FIELD_BOOL  , (arg)
+#define MRP_MSG_TAG_UINT8(tag, arg)  (tag), MRP_MSG_FIELD_UINT8 , (arg)
+#define MRP_MSG_TAG_SINT8(tag, arg)  (tag), MRP_MSG_FIELD_SINT8 , (arg)
+#define MRP_MSG_TAG_UINT16(tag, arg) (tag), MRP_MSG_FIELD_UINT16, (arg)
+#define MRP_MSG_TAG_SINT16(tag, arg) (tag), MRP_MSG_FIELD_SINT16, (arg)
+#define MRP_MSG_TAG_UINT32(tag, arg) (tag), MRP_MSG_FIELD_UINT32, (arg)
+#define MRP_MSG_TAG_SINT32(tag, arg) (tag), MRP_MSG_FIELD_SINT32, (arg)
+#define MRP_MSG_TAG_UINT64(tag, arg) (tag), MRP_MSG_FIELD_UINT64, (arg)
+#define MRP_MSG_TAG_SINT64(tag, arg) (tag), MRP_MSG_FIELD_SINT64, (arg)
+#define MRP_MSG_TAG_DOUBLE(tag, arg) (tag), MRP_MSG_FIELD_DOUBLE, (arg)
+#define MRP_MSG_TAG_BLOB(tag, arg)   (tag), MRP_MSG_FIELD_BLOB  , (arg)
+
+#define MRP_MSG_TAGGED(tag, type, ...) (tag), (type), __VA_ARGS__
+#define MRP_MSG_TAG_ARRAY(tag, type, cnt, arr)                        \
+    (tag), MRP_MSG_FIELD_ARRAY | MRP_MSG_FIELD_##type, (cnt), (arr)
+#define MRP_MSG_TAG_STRING_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), STRING, (cnt), (arr))
+#define MRP_MSG_TAG_BOOL_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), BOOL, (cnt), (arr))
+#define MRP_MSG_TAG_UINT8_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), UINT8, (cnt), (arr))
+#define MRP_MSG_TAG_SINT8_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), SINT8, (cnt), (arr))
+#define MRP_MSG_TAG_UINT16_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), UINT16, (cnt), (arr))
+#define MRP_MSG_TAG_SINT16_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), SINT16, (cnt), (arr))
+#define MRP_MSG_TAG_UINT32_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), UINT32, (cnt), (arr))
+#define MRP_MSG_TAG_SINT32_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), SINT32, (cnt), (arr))
+#define MRP_MSG_TAG_UINT64_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), UINT64, (cnt), (arr))
+#define MRP_MSG_TAG_SINT64_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), SINT64, (cnt), (arr))
+#define MRP_MSG_TAG_DOUBLE_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), DOUBLE, (cnt), (arr))
+#define MRP_MSG_TAG_BLOB_ARRAY(tag, cnt, arr) \
+    MRP_MSG_TAG_ARRAY((tag), BLOB, (cnt), (arr))
+
+#define MRP_MSG_TAG_ANY(tag, typep, valuep) \
+    (tag), MRP_MSG_FIELD_ANY, (typep), (valuep)
+
+
+/** Sentinel to pass in as the last argument to mrp_msg_create. */
+#define MRP_MSG_FIELD_END NULL
+
+
+/*
+ * generic messages
+ *
+ * A generic message is just a collection of message fields. By default
+ * transports are in generic messaging mode in which case they take messages
+ * as input (for transmission) and provide messages as events (for receiption).
+ * A generic message field consists of a field tag, a field type, the actual
+ * type-specific field value, and for certain types a size.
+ *
+ * The field tag is used by the communicating parties to attach semantic
+ * meaning to the field data. One can think of it as the 'name' of the field
+ * within a message. It is not interpreted by the messaging layer in any way.
+ * The field type defines what kind of data the field contains contains and
+ * it must be one of the predefined MRP_MSG_FIELD_* types. The actual field
+ * data then depends on the type. size is only used for those data types that
+ * require a size (blobs and arrays).
+ */
+
+#define MRP_MSG_VALUE_UNION union {                                       \
+        char      *str;                                                   \
+        bool       bln;                                                   \
+        uint8_t    u8;                                                    \
+        int8_t     s8;                                                    \
+        uint16_t   u16;                                                   \
+        int16_t    s16;                                                   \
+        uint32_t   u32;                                                   \
+        int32_t    s32;                                                   \
+        uint64_t   u64;                                                   \
+        int64_t    s64;                                                   \
+        double     dbl;                                                   \
+        void      *blb;                                                   \
+        void      *aany;                                                  \
+        char     **astr;                                                  \
+        bool      *abln;                                                  \
+        uint8_t   *au8;                                                   \
+        int8_t    *as8;                                                   \
+        uint16_t  *au16;                                                  \
+        int16_t   *as16;                                                  \
+        uint32_t  *au32;                                                  \
+        int32_t   *as32;                                                  \
+        uint64_t  *au64;                                                  \
+        int64_t   *as64;                                                  \
+        double    *adbl;                                                  \
+    }
+
+typedef MRP_MSG_VALUE_UNION mrp_msg_value_t;
+
+typedef struct {
+    mrp_list_hook_t hook;                /* hook to list of fields */
+    uint16_t        tag;                 /* message field tag */
+    uint16_t        type;                /* message field type */
+    MRP_MSG_VALUE_UNION;                 /* message field value */
+    uint32_t        size[0];             /* size, if an array or a blob */
+} mrp_msg_field_t;
+
+
+typedef struct {
+    mrp_list_hook_t fields;              /* list of message fields */
+    size_t          nfield;              /* number of fields */
+    mrp_refcnt_t    refcnt;              /* reference count */
+} mrp_msg_t;
+
+
+/** Create a new message. */
+mrp_msg_t *mrp_msg_create(uint16_t tag, ...) MRP_NULLTERM;
+
+/** Create a new message. */
+mrp_msg_t *mrp_msg_createv(uint16_t tag, va_list ap);
+
+/** Macro to create an empty message. */
+#define mrp_msg_create_empty() mrp_msg_create(MRP_MSG_FIELD_INVALID, NULL)
+
+/** Increase refcount of the given message. */
+mrp_msg_t *mrp_msg_ref(mrp_msg_t *msg);
+
+/** Decrease the refcount, free the message if refcount drops to zero. */
+void mrp_msg_unref(mrp_msg_t *msg);
+
+/** Append a field to a message. */
+int mrp_msg_append(mrp_msg_t *msg, uint16_t tag, ...);
+
+/** Prepend a field to a message. */
+int mrp_msg_prepend(mrp_msg_t *msg, uint16_t tag, ...);
+
+/** Set a field in a message to the given value. */
+int mrp_msg_set(mrp_msg_t *msg, uint16_t tag, ...);
+
+/** Iterate through the fields of a message. You must not any of the
+    fields while iterating. */
+int mrp_msg_iterate(mrp_msg_t *msg, void **it, uint16_t *tagp,
+                    uint16_t *typep, mrp_msg_value_t *valp, size_t *sizep);
+
+/** Iterate through the matching fields of a message. You should not delete
+ * any of the fields while iterating through the message. */
+int mrp_msg_iterate_matching(mrp_msg_t *msg, void **it, uint16_t *tagp,
+                             uint16_t *typep, mrp_msg_value_t *valp,
+                             size_t *sizep);
+
+/** Find a field in a message. */
+mrp_msg_field_t *mrp_msg_find(mrp_msg_t *msg, uint16_t tag);
+
+/** Get the given fields (with matching tags and types) from the message. */
+int mrp_msg_get(mrp_msg_t *msg, ...) MRP_NULLTERM;
+
+/** Iterate through the message getting the given fields. */
+int mrp_msg_iterate_get(mrp_msg_t *msg, void **it, ...);
+
+/** Dump a message. */
+int mrp_msg_dump(mrp_msg_t *msg, FILE *fp);
+
+/** Encode the given message using the default message encoder. */
+ssize_t mrp_msg_default_encode(mrp_msg_t *msg, void **bufp);
+
+/** Decode the given message using the default message decoder. */
+mrp_msg_t *mrp_msg_default_decode(void *buf, size_t size);
+
+
+/*
+ * custom data types
+ *
+ * In addition to generic messages, you can instruct the messaging and
+ * transport layers to encode/decode messages directly from/to custom data
+ * structures. To do so you need to describe your data structures and register
+ * them using data descriptors. A descriptor basically consists of a type
+ * tag, structure size, number of members and and array of structure member
+ * descriptors.
+ *
+ * The data type tag is used to identify the descriptor and consequently
+ * the custom data type both during sending and receiving (ie. encoding and
+ * decoding). It is assigned by the registering entity, it must be unique,
+ * and it cannot be MRP_MSG_TAG_DEFAULT (0x0), or else registration will
+ * fail. The size is used to allocate necessary memory for the data on the
+ * receiving end. The member descriptors are used to describe the offset
+ * and types of the members within the custom data type.
+ */
+
+#define MRP_MSG_TAG_DEFAULT 0x0          /* tag for default encode/decoder */
+
+typedef struct {
+    uint16_t        offs;                /* offset within structure */
+    uint16_t        tag;                 /* tag for this member */
+    uint16_t        type;                /* type of this member */
+    bool            guard;               /* whether sentinel-terminated */
+    MRP_MSG_VALUE_UNION;                 /* sentinel or offset of count field */
+    mrp_list_hook_t hook;                /* hook to list of extra allocations */
+} mrp_data_member_t;
+
+
+typedef struct {
+    mrp_refcnt_t       refcnt;           /* reference count */
+    uint16_t           tag;              /* structure tag */
+    size_t             size;             /* size of this structure */
+    int                nfield;           /* number of members */
+    mrp_data_member_t *fields;           /* member descriptors */
+    mrp_list_hook_t    allocated;        /* fields needing extra allocation */
+} mrp_data_descr_t;
+
+
+/** Convenience macro to declare a custom data type (and its members). */
+#define MRP_DATA_DESCRIPTOR(_var, _tag, _type, ...)                       \
+    static mrp_data_member_t _var##_members[] = {                         \
+        __VA_ARGS__                                                       \
+    };                                                                    \
+                                                                          \
+    static mrp_data_descr_t _var = {                                      \
+        .size   = sizeof(_type),                                          \
+        .tag    = _tag,                                                   \
+        .fields = _var##_members,                                         \
+        .nfield = MRP_ARRAY_SIZE(_var##_members)                          \
+ }
+
+/** Convenience macro to declare a data member. */
+#define MRP_DATA_MEMBER(_data_type, _member, _member_type) {              \
+        .offs  = MRP_OFFSET(_data_type, _member),                         \
+        .type  = _member_type,                                            \
+        .guard = FALSE                                                    \
+ }
+
+/** Convenience macro to declare an array data member with a count field. */
+#define MRP_DATA_ARRAY_COUNT(_data_type, _array, _count, _base_type) {    \
+        .offs  = MRP_OFFSET(_data_type, _array),                          \
+        .type  = MRP_MSG_FIELD_ARRAY | _base_type,                        \
+        .guard = FALSE,                                                   \
+      { .u32   = MRP_OFFSET(_data_type, _count) }                         \
+    }
+
+/** Convenience macro to declare an array data member with a sentinel value. */
+#define MRP_DATA_ARRAY_GUARD(_data_type, _array, _guard_member, _guard_val, \
+                             _base_type) {                                  \
+        .offs          = MRP_OFFSET(_data_type, _array),                    \
+        .type          = MRP_MSG_FIELD_ARRAY | _base_type,                  \
+        .guard         = TRUE,                                              \
+      { ._guard_member = _guard_val }                                       \
+    }
+
+/** Convenience macro to declare a blob data member with a count field. */
+#define MRP_DATA_BLOB_MEMBER(_data_type, _blob, _count) {                 \
+        .offs  = MRP_OFFSET(_data_type, _blob),                           \
+        .type  = MRP_MSG_FIELD_BLOB,                                      \
+        .guard = FALSE,                                                   \
+        .u32   = MRP_OFFSET(_data_type, _count)                           \
+    }
+
+
+/** Encode a structure using the given message descriptor. */
+size_t mrp_data_encode(void **bufp, void *data, mrp_data_descr_t *descr,
+                       size_t reserve);
+
+/** Decode a structure using the given message descriptor. */
+void *mrp_data_decode(void **bufp, size_t *sizep, mrp_data_descr_t *descr);
+
+/** Dump the given data buffer. */
+int mrp_data_dump(void *data, mrp_data_descr_t *descr, FILE *fp);
+
+/** Get the size of a data array member. */
+int mrp_data_get_array_size(void *data, mrp_data_descr_t *type, int idx);
+
+/** Get the size of a data blob member. */
+int mrp_data_get_blob_size(void *data, mrp_data_descr_t *type, int idx);
+
+/** Register a new custom data type with the messaging/transport layer. */
+int mrp_msg_register_type(mrp_data_descr_t *type);
+
+/** Look up the data type descriptor corresponding to the given tag. */
+mrp_data_descr_t *mrp_msg_find_type(uint16_t tag);
+
+/** Free the given custom data allocated by the messaging layer. */
+int mrp_data_free(void *data, uint16_t tag);
+
+/*
+ * message encoding/decoding buffer
+ *
+ * This message buffer and the associated functions and macros can be
+ * used to write message encoding/decoding functions for bitpipe-type
+ * transports, ie. for transports where the underlying IPC just provides
+ * a raw data connection between the communication endpoints and does not
+ * impose/expect any structure on/from the data being transmitted.
+ *
+ * Practically all the basic stream and datagram socket transports are
+ * such. They use the default encoding/decoding functions provided by
+ * the messaging layer together with a very simple transport frame scheme,
+ * where each frame consists of the amount a size indicating the size of
+ * the encoded message in the bitpipe and the actual encoded message data.
+ *
+ * Note that at the moment this framing scheme is rather implicit in the
+ * sense that you won't find a data type representing a frame. Rather the
+ * framing is simply done in the sending/receiving code of the individual
+ * transports.
+ */
+
+typedef struct {
+    void   *buf;                         /* buffer to encode to/decode from */
+    size_t  size;                        /* size of the buffer */
+    void   *p;                           /* encoding/decoding pointer */
+    size_t  l;                           /* space left in the buffer */
+} mrp_msgbuf_t;
+
+
+
+/** Initialize the given message buffer for writing. */
+void *mrp_msgbuf_write(mrp_msgbuf_t *mb, size_t size);
+
+/** Initialize the given message buffer for reading. */
+void mrp_msgbuf_read(mrp_msgbuf_t *mb, void *buf, size_t size);
+
+/** Deinitialize the given message buffer, usually due to some error. */
+void mrp_msgbuf_cancel(mrp_msgbuf_t *mb);
+
+/** Reallocate the buffer if needed to accomodate size bytes of data. */
+void *mrp_msgbuf_ensure(mrp_msgbuf_t *mb, size_t size);
+
+/** Reserve the given amount of space from the buffer. */
+void *mrp_msgbuf_reserve(mrp_msgbuf_t *mb, size_t size, size_t align);
+
+/** Pull the given amount of data from the buffer. */
+void *mrp_msgbuf_pull(mrp_msgbuf_t *mb, size_t size, size_t align);
+
+/** Push data with alignment to the buffer, jumping to errlbl on errors. */
+#define MRP_MSGBUF_PUSH(mb, data, align, errlbl) do {                     \
+        size_t        _size = sizeof(data);                               \
+        typeof(data) *_ptr;                                               \
+                                                                          \
+        _ptr  = mrp_msgbuf_reserve((mb), _size, (align));                 \
+                                                                          \
+        if (_ptr != NULL)                                                 \
+            *_ptr = data;                                                 \
+        else                                                              \
+            goto errlbl;                                                  \
+    } while (0)
+
+/** Push aligned data to the buffer, jumping to errlbl on errors. */
+#define MRP_MSGBUF_PUSH_DATA(mb, data, size, align, errlbl) do {          \
+        size_t _size = (size);                                            \
+        void   *_ptr;                                                     \
+                                                                          \
+        _ptr  = mrp_msgbuf_reserve((mb), _size, (align));                 \
+                                                                          \
+        if (_ptr != NULL)                                                 \
+            memcpy(_ptr, data, _size);                                    \
+        else                                                              \
+            goto errlbl;                                                  \
+    } while (0)
+
+/** Pull aligned data of type from the buffer, jump to errlbl on errors. */
+#define MRP_MSGBUF_PULL(mb, type, align, errlbl) ({                       \
+            size_t  _size = sizeof(type);                                 \
+            type   *_ptr;                                                 \
+                                                                          \
+            _ptr = mrp_msgbuf_pull((mb), _size, (align));                 \
+                                                                          \
+            if (_ptr == NULL)                                             \
+                goto errlbl;                                              \
+                                                                          \
+            *_ptr;                                                        \
+        })
+
+/** Pull aligned data of type from the buffer, jump to errlbl on errors. */
+#define MRP_MSGBUF_PULL_DATA(mb, size, align, errlbl) ({                  \
+            size_t  _size = size;                                         \
+            void   *_ptr;                                                 \
+                                                                          \
+            _ptr = mrp_msgbuf_pull((mb), _size, (align));                 \
+                                                                          \
+            if (_ptr == NULL)                                             \
+                goto errlbl;                                              \
+                                                                          \
+            _ptr;                                                         \
+        })
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_MSG_H__ */
diff --git a/src/common/murphy-common.pc.in b/src/common/murphy-common.pc.in
new file mode 100644 (file)
index 0000000..0b75762
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-common
+Description: Murphy policy framework, common library.
+Requires:
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-common @JSON_LIBS@
+Cflags: -I${includedir} @JSON_CFLAGS@
diff --git a/src/common/murphy-dbus-libdbus.pc.in b/src/common/murphy-dbus-libdbus.pc.in
new file mode 100644 (file)
index 0000000..d6efb6f
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-dbus
+Description: Murphy policy framework, libdbus based dbus library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-dbus-libdbus @LIBDBUS_LIBS@
+Cflags: -I${includedir} @LIBDBUS_CFLAGS@
diff --git a/src/common/murphy-dbus-sdbus.pc.in b/src/common/murphy-dbus-sdbus.pc.in
new file mode 100644 (file)
index 0000000..94008ee
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-sd-bus
+Description: Murphy policy framework, systemd-bus based dbus library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-sd-bus @SDBUS_LIBS@
+Cflags: -I${includedir} @SDBUS_CFLAGS@
diff --git a/src/common/murphy-ecore.pc.in b/src/common/murphy-ecore.pc.in
new file mode 100644 (file)
index 0000000..626a07b
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-ecore
+Description: Murphy policy framework, EFL/ecore mainloop glue library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-ecore @ECORE_LIBS@
+Cflags: -I${includedir} @ECORE_CFLAGS@
diff --git a/src/common/murphy-glib.pc.in b/src/common/murphy-glib.pc.in
new file mode 100644 (file)
index 0000000..5193219
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-glib
+Description: Murphy policy framework, GLIB mainloop glue library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-glib @GLIB_LIBS@
+Cflags: -I${includedir} @GLIB_CFLAGS@
diff --git a/src/common/murphy-libdbus.pc.in b/src/common/murphy-libdbus.pc.in
new file mode 100644 (file)
index 0000000..bcda7b4
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-dbus
+Description: Murphy policy framework, dbus library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-libdbus @LIBDBUS_LIBS@
+Cflags: -I${includedir} @LIBDBUS_CFLAGS@
diff --git a/src/common/murphy-pulse.pc.in b/src/common/murphy-pulse.pc.in
new file mode 100644 (file)
index 0000000..2655307
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-pulse
+Description: Murphy policy framework, PulseAudio mainloop glue library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-pulse
+Cflags: -I${includedir} @PULSE_CFLAGS@
diff --git a/src/common/murphy-qt.pc.in b/src/common/murphy-qt.pc.in
new file mode 100644 (file)
index 0000000..95c6aba
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-qt
+Description: Murphy policy framework, Qt mainloop glue library.
+Requires: murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-qt @QTCORE_LIBS@
+Cflags: -I${includedir} @QTCORE_CFLAGS@
diff --git a/src/common/native-types.c b/src/common/native-types.c
new file mode 100644 (file)
index 0000000..d9890e3
--- /dev/null
@@ -0,0 +1,1626 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/tlv.h>
+#include <murphy/common/native-types.h>
+
+
+/*
+ * TLV tags we use when encoding/decoding our native types
+ */
+
+typedef enum {
+    TAG_NONE = MRP_TLV_UNTAGGED,         /* untagged data */
+    TAG_STRUCT,                          /* a native structure */
+    TAG_MEMBER,                          /* a native structure member */
+    TAG_ARRAY,                           /* an array */
+    TAG_NELEM,                           /* size of an array (in elements) */
+} tag_t;
+
+
+/*
+ * extra header we use to keep track of memory while decoding
+ */
+
+typedef struct {
+    mrp_list_hook_t hook;                /* hook to chunk list */
+    char            data[0];             /* user-visible data */
+} chunk_t;
+
+
+static int encode_struct(mrp_tlv_t *tlv, void *data, mrp_native_type_t *t,
+                         mrp_typemap_t *idmap);
+static int decode_struct(mrp_tlv_t *tlv, mrp_list_hook_t **chunks,
+                         void **datap, uint32_t *idp, mrp_typemap_t *idmap);
+static int print_struct(char **buf, size_t *size, int level,
+                        void *data, mrp_native_type_t *t);
+static void free_native(mrp_native_type_t *t);
+
+static void *alloc_chunk(mrp_list_hook_t **chunks, size_t size);
+static void free_chunks(mrp_list_hook_t *chunks);
+
+
+/*
+ * list and table of registered native types
+ */
+
+static MRP_LIST_HOOK(types);
+static int           ntype;
+
+static mrp_native_type_t **typetbl;
+
+
+static mrp_native_member_t *native_member(mrp_native_type_t *t, int idx)
+{
+    if (0 <= idx && idx < (int)t->nmember)
+        return t->members + idx;
+    else {
+        errno = EINVAL;
+        return NULL;
+    }
+}
+
+
+static int member_index(mrp_native_type_t *t, const char *name)
+{
+    mrp_native_member_t *m;
+    size_t               i;
+
+    for (i = 0, m = t->members; i < t->nmember; i++, m++)
+        if (!strcmp(m->any.name, name))
+            return m - t->members;
+
+    return -1;
+}
+
+
+static int copy_member(mrp_native_type_t *t, mrp_native_member_t *m)
+{
+    mrp_native_member_t *tm;
+    size_t               size;
+
+    if ((tm = native_member(t, member_index(t, m->any.name))) != NULL)
+        return tm - t->members;
+    else
+        tm = t->members + t->nmember;
+
+    *tm = *m;
+
+    if (*m->any.name != '"')
+         tm->any.name = mrp_strdup(m->any.name);
+     else {
+         size = strlen(m->any.name) + 1 - 2;
+
+         if ((tm->any.name = mrp_allocz(size)) != NULL)
+             strncpy(tm->any.name, m->any.name + 1, size - 1);
+     }
+
+    if (tm->any.name != NULL) {
+        t->nmember++;
+        return tm - t->members;
+    }
+    else
+        return -1;
+}
+
+
+static mrp_native_type_t *find_type(const char *type_name)
+{
+    mrp_native_type_t *t;
+    mrp_list_hook_t   *p, *n;
+
+    mrp_list_foreach(&types, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        if (!strcmp(t->name, type_name))
+            return t;
+    }
+
+    return NULL;
+}
+
+
+static mrp_native_type_t *lookup_type(uint32_t id)
+{
+    mrp_native_type_t *t;
+    mrp_list_hook_t   *p, *n;
+
+    /* XXX TODO: turn this into a real lookup instead of linear search */
+
+    if (1 <= id && id <= (uint32_t)ntype)
+        if ((t = typetbl[id]) != NULL && t->id == id)
+            return t;
+
+    mrp_log_warning("Type lookup for %u failed, doing linear search...\n", id);
+
+    mrp_list_foreach(&types, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        if (t->id == id)
+            return t;
+    }
+
+    return NULL;
+}
+
+
+static mrp_native_type_t *member_type(mrp_native_member_t *m)
+{
+    mrp_native_type_t *t;
+
+    if (m->any.type != MRP_TYPE_STRUCT)
+        t = lookup_type(m->any.type);
+    else
+        t = lookup_type(m->strct.data_type.id);
+
+    if (t == NULL)
+        errno = EINVAL;
+
+    return t;
+}
+
+
+static inline uint32_t map_type(uint32_t id, mrp_typemap_t *idmap)
+{
+    uint32_t mapped = MRP_INVALID_TYPE;
+
+    if (id < MRP_TYPE_STRUCT || idmap == NULL)
+        mapped = id;
+    else {
+        while (idmap->type_id != MRP_INVALID_TYPE) {
+            if (idmap->type_id == id) {
+                mapped = MRP_TYPE_STRUCT + idmap->mapped;
+                break;
+            }
+            else
+                idmap++;
+        }
+    }
+
+    return mapped;
+}
+
+
+static inline uint32_t mapped_type(uint32_t mapped, mrp_typemap_t *idmap)
+{
+    uint32_t id = MRP_INVALID_TYPE;
+
+    if (mapped < MRP_TYPE_STRUCT || idmap == NULL)
+        id = mapped;
+    else {
+        while (idmap->type_id != MRP_INVALID_TYPE) {
+            if (MRP_TYPE_STRUCT + idmap->mapped == mapped) {
+                id = idmap->type_id;
+                break;
+            }
+            else
+                idmap++;
+        }
+    }
+
+    return id;
+}
+
+
+uint32_t mrp_type_id(const char *type_name)
+{
+    mrp_native_type_t *t;
+
+    if ((t = find_type(type_name)) != NULL)
+        return t->id;
+    else
+        return MRP_INVALID_TYPE;
+}
+
+
+static size_t type_size(uint32_t id)
+{
+    mrp_native_type_t *t = lookup_type(id);
+
+    if (t != NULL)
+        return t->size;
+    else
+        return 0;
+}
+
+
+static int matching_types(mrp_native_type_t *t1, mrp_native_type_t *t2)
+{
+    MRP_UNUSED(t1);
+    MRP_UNUSED(t2);
+
+    /* XXX TODO */
+    return 0;
+}
+
+
+static void register_default_types(void)
+{
+#define DEFAULT_NTYPE (MRP_TYPE_STRUCT + 1)
+
+#define DECLARE_TYPE(_ctype, _mtype)            \
+    static mrp_native_type_t _mtype##_type = {  \
+        .name    = #_ctype,                     \
+        .id      = MRP_TYPE_##_mtype,           \
+        .size    = sizeof(_ctype),              \
+        .members = NULL,                        \
+        .nmember = 0,                           \
+        .hook    = { NULL, NULL }               \
+    }
+
+#define REGISTER_TYPE(_type)                    \
+    mrp_list_init(&(_type)->hook);              \
+    mrp_list_append(&types, &(_type)->hook);    \
+    typetbl[(_type)->id] = (_type)
+
+    if (mrp_reallocz(typetbl, 0, DEFAULT_NTYPE) == NULL) {
+        mrp_log_error("Failed to initialize native type table.");
+        abort();
+    }
+
+    DECLARE_TYPE( int8_t       , INT8  );
+    DECLARE_TYPE(uint8_t       , UINT8 );
+    DECLARE_TYPE(int16_t       , INT16 );
+    DECLARE_TYPE(uint16_t      , UINT16);
+    DECLARE_TYPE(int32_t       , INT32 );
+    DECLARE_TYPE(uint32_t      , UINT32);
+    DECLARE_TYPE(int64_t       , INT64 );
+    DECLARE_TYPE(uint64_t      , UINT64);
+    DECLARE_TYPE(float         , FLOAT );
+    DECLARE_TYPE(double        , DOUBLE);
+    DECLARE_TYPE(bool          , BOOL  );
+    DECLARE_TYPE(int           , INT   );
+    DECLARE_TYPE(unsigned int  , UINT  );
+    DECLARE_TYPE(short         , SHORT );
+    DECLARE_TYPE(unsigned short, USHORT);
+    DECLARE_TYPE(size_t        , SIZET );
+    DECLARE_TYPE(ssize_t       , SSIZET);
+    DECLARE_TYPE(char *       , STRING);
+    DECLARE_TYPE(void *       , BLOB  );
+    DECLARE_TYPE(void *       , ARRAY );
+    DECLARE_TYPE(void *       , STRUCT);
+
+    REGISTER_TYPE(&INT8_type);
+    REGISTER_TYPE(&UINT8_type);
+    REGISTER_TYPE(&INT16_type);
+    REGISTER_TYPE(&UINT16_type);
+    REGISTER_TYPE(&INT32_type);
+    REGISTER_TYPE(&UINT32_type);
+    REGISTER_TYPE(&INT64_type);
+    REGISTER_TYPE(&UINT64_type);
+    REGISTER_TYPE(&FLOAT_type);
+    REGISTER_TYPE(&DOUBLE_type);
+    REGISTER_TYPE(&BOOL_type);
+    REGISTER_TYPE(&INT_type);
+    REGISTER_TYPE(&UINT_type);
+    REGISTER_TYPE(&SHORT_type);
+    REGISTER_TYPE(&USHORT_type);
+    REGISTER_TYPE(&SIZET_type);
+    REGISTER_TYPE(&SSIZET_type);
+    REGISTER_TYPE(&STRING_type);
+    REGISTER_TYPE(&BLOB_type);
+    REGISTER_TYPE(&ARRAY_type);
+    REGISTER_TYPE(&STRUCT_type);
+
+    ntype = DEFAULT_NTYPE;
+
+#undef DECLARE_TYPE
+#undef REGISTER_TYPE
+}
+
+
+uint32_t mrp_register_native(mrp_native_type_t *type)
+{
+    mrp_native_type_t   *existing = find_type(type->name);
+    mrp_native_type_t   *t, *elemt;
+    mrp_native_member_t *s, *d, *m;
+    int                  idx;
+
+    (void)member_type;
+
+    if (existing != NULL && !matching_types(existing, type)) {
+        errno = EEXIST;
+        return MRP_INVALID_TYPE;
+    }
+
+    if (ntype == 0)
+        register_default_types();
+
+    if ((t = mrp_allocz(sizeof(*t))) == NULL)
+        return MRP_INVALID_TYPE;
+
+    mrp_list_init(&t->hook);
+    t->name = mrp_strdup(type->name);
+
+    if (t->name == NULL)
+        goto fail;
+
+    t->size    = type->size;
+    t->members = mrp_allocz_array(mrp_native_member_t, type->nmember);
+
+    if (t->members == NULL && type->nmember != 0)
+        goto fail;
+
+    /*
+     * Notes:
+     *
+     *   While we copy the members, we also take care of reordering them
+     *   so that any member that another one depends on ('size' members)
+     *   get registered (and consequently encoded and decoded) before the
+     *   dependant members.
+     */
+
+    s = type->members;
+    d = t->members;
+    while (t->nmember < type->nmember) {
+        /* make sure there are no duplicate members */
+        if (native_member(type, member_index(type, s->any.name)) != s) {
+            errno = EINVAL;
+            goto fail;
+        }
+
+        /* skip already copied members */
+        while (member_index(t, s->any.name) >= 0)
+            s++;
+
+        switch (s->any.type) {
+        case MRP_TYPE_BLOB:
+            m = native_member(t, member_index(t, s->blob.size.name));
+
+            if (m == NULL) {
+                m = native_member(type,
+                                  member_index(type, s->blob.size.name));
+
+                if (m == NULL)
+                    goto fail;
+                else
+                    idx = copy_member(t, m);
+
+                if (idx < 0)
+                    goto fail;
+            }
+            else
+                idx = m - t->members;
+
+            if (copy_member(t, s) < 0)
+                goto fail;
+
+            d = t->members + t->nmember;
+            d->blob.size.idx = idx;
+
+            break;
+
+        case MRP_TYPE_ARRAY:
+            if (s->array.kind == MRP_ARRAY_SIZE_EXPLICIT) {
+                m = native_member(t, member_index(t, s->array.size.name));
+
+                if (m == NULL) {
+                    m = native_member(type,
+                                      member_index(type, s->array.size.name));
+
+                    if (m == NULL)
+                        goto fail;
+                    else
+                        idx = copy_member(t, m);
+
+                    if (idx < 0)
+                        goto fail;
+
+                }
+                else
+                    idx = m - t->members;
+
+                d = t->members + t->nmember;
+
+                if (copy_member(t, s) < 0)
+                    goto fail;
+
+                d->array.size.idx = idx;
+            }
+            else {
+                d = t->members + t->nmember;
+
+                if (copy_member(t, s) < 0)
+                    goto fail;
+            }
+
+            d->array.elem.id = mrp_type_id(d->array.elem.name);
+
+            if (d->array.elem.id == MRP_INVALID_TYPE)
+                goto fail;
+
+            if (s->array.kind == MRP_ARRAY_SIZE_GUARDED) {
+                elemt = lookup_type(d->array.elem.id);
+
+                if (elemt == NULL)
+                    goto fail;
+
+                if (elemt->id < MRP_TYPE_ARRAY)
+                    idx = 0;
+                else {
+                    idx = member_index(elemt, s->array.size.name);
+                    d->array.size.idx = member_index(elemt, s->array.size.name);
+
+                    if (d->array.size.idx == (uint32_t)-1)
+                        goto fail;
+                }
+            }
+
+            break;
+
+        case MRP_TYPE_STRUCT:
+            d = t->members + t->nmember;
+
+            if (copy_member(t, s) < 0)
+                goto fail;
+
+            d->strct.data_type.id = mrp_type_id(d->strct.data_type.name);
+
+            if (d->strct.data_type.id == MRP_INVALID_TYPE)
+                goto fail;
+            break;
+
+        default:
+            if (copy_member(t, s) < 0)
+                goto fail;
+        }
+    }
+
+    if (mrp_reallocz(typetbl, ntype, ntype + 1) == NULL)
+        goto fail;
+
+    t->id = ntype;
+    mrp_list_append(&types, &t->hook);
+    typetbl[ntype] = t;
+    ntype++;
+
+    return t->id;
+
+ fail:
+    free_native(t);
+
+    return MRP_INVALID_TYPE;
+}
+
+
+static void free_native(mrp_native_type_t *t)
+{
+    mrp_native_member_t *m;
+    size_t               i;
+
+    if (t == NULL)
+        return;
+
+    mrp_list_delete(&t->hook);
+
+    mrp_free(t->name);
+    for (i = 0, m = t->members; i < t->nmember; i++, m++)
+        mrp_free(m->any.name);
+    mrp_free(t);
+}
+
+
+static int encode_basic(mrp_tlv_t *tlv, mrp_type_t type, mrp_value_t *v)
+{
+    switch (type) {
+    case MRP_TYPE_INT8:    return mrp_tlv_push_int8  (tlv, TAG_NONE, v->s8);
+    case MRP_TYPE_UINT8:   return mrp_tlv_push_uint8 (tlv, TAG_NONE, v->u8);
+    case MRP_TYPE_INT16:   return mrp_tlv_push_int16 (tlv, TAG_NONE, v->s16);
+    case MRP_TYPE_UINT16:  return mrp_tlv_push_uint16(tlv, TAG_NONE, v->u16);
+    case MRP_TYPE_INT32:   return mrp_tlv_push_int32 (tlv, TAG_NONE, v->s32);
+    case MRP_TYPE_UINT32:  return mrp_tlv_push_uint32(tlv, TAG_NONE, v->u32);
+    case MRP_TYPE_INT64:   return mrp_tlv_push_int64 (tlv, TAG_NONE, v->s64);
+    case MRP_TYPE_UINT64:  return mrp_tlv_push_uint64(tlv, TAG_NONE, v->u64);
+    case MRP_TYPE_FLOAT:   return mrp_tlv_push_float (tlv, TAG_NONE, v->flt);
+    case MRP_TYPE_DOUBLE:  return mrp_tlv_push_double(tlv, TAG_NONE, v->dbl);
+    case MRP_TYPE_BOOL:    return mrp_tlv_push_bool  (tlv, TAG_NONE, v->bln);
+    case MRP_TYPE_STRING:  return mrp_tlv_push_string(tlv, TAG_NONE, v->str);
+
+    case MRP_TYPE_INT:
+        return mrp_tlv_push_int32 (tlv, TAG_NONE, (int32_t)v->i);
+    case MRP_TYPE_UINT:
+        return mrp_tlv_push_uint32(tlv, TAG_NONE, (uint32_t)v->ui);
+    case MRP_TYPE_SHORT:
+        return mrp_tlv_push_int32 (tlv, TAG_NONE, (int32_t)v->si);
+    case MRP_TYPE_USHORT:
+        return mrp_tlv_push_uint32(tlv, TAG_NONE, (uint32_t)v->usi);
+    case MRP_TYPE_SIZET:
+        return mrp_tlv_push_uint32(tlv, TAG_NONE, (uint32_t)v->sz);
+    case MRP_TYPE_SSIZET:
+        return mrp_tlv_push_int32 (tlv, TAG_NONE, (int32_t)v->ssz);
+
+    default:
+        return -1;
+    }
+}
+
+
+static inline int get_blob_size(void *base, mrp_native_type_t *t,
+                                mrp_native_blob_t *m, size_t *sizep)
+{
+    mrp_native_member_t *sizem;
+    mrp_value_t         *v;
+
+    if ((sizem = native_member(t, m->size.idx)) == NULL)
+        return -1;
+
+    if (sizem->any.layout == MRP_LAYOUT_INDIRECT)
+        v = *(void **)base;
+    else
+        v = base;
+
+    switch (sizem->any.type) {
+    case MRP_TYPE_INT8:   *sizep = v->s8;          return 0;
+    case MRP_TYPE_UINT8:  *sizep = v->u8;          return 0;
+    case MRP_TYPE_INT16:  *sizep = v->s16;         return 0;
+    case MRP_TYPE_UINT16: *sizep = v->u16;         return 0;
+    case MRP_TYPE_INT32:  *sizep = v->s32;         return 0;
+    case MRP_TYPE_UINT32: *sizep = v->u32;         return 0;
+    case MRP_TYPE_INT64:  *sizep = (size_t)v->s32; return 0;
+    case MRP_TYPE_UINT64: *sizep = (size_t)v->u32; return 0;
+    default:
+        errno = EINVAL;
+        return -1;
+    }
+}
+
+
+static int guard_offset_and_size(mrp_native_array_t *m, size_t *offsp,
+                                 size_t *sizep)
+{
+    mrp_native_type_t   *t = lookup_type(m->elem.id);
+    mrp_native_member_t *g;
+
+    if (t == NULL)
+        return -1;
+
+    switch (t->id) {
+    case MRP_TYPE_INT8:
+    case MRP_TYPE_UINT8:
+    case MRP_TYPE_INT16:
+    case MRP_TYPE_UINT16:
+    case MRP_TYPE_INT32:
+    case MRP_TYPE_UINT32:
+    case MRP_TYPE_INT64:
+    case MRP_TYPE_UINT64:
+    case MRP_TYPE_FLOAT:
+    case MRP_TYPE_DOUBLE:
+    case MRP_TYPE_BOOL:
+    case MRP_TYPE_STRING:
+    case MRP_TYPE_INT:
+    case MRP_TYPE_UINT:
+    case MRP_TYPE_SHORT:
+    case MRP_TYPE_USHORT:
+    case MRP_TYPE_SIZET:
+    case MRP_TYPE_SSIZET:
+        *offsp = 0;
+        *sizep = t->size;
+        return 0;
+
+    default:
+        if ((g = native_member(t, m->size.idx)) == NULL)
+            return -1;
+
+        *offsp = g->any.offs;
+        *sizep = type_size(g->any.type);
+        return 0;
+    }
+}
+
+
+static inline int get_explicit_array_size(void *base, mrp_native_type_t *t,
+                                          mrp_native_array_t *m)
+{
+    mrp_native_member_t *nelemm;
+    mrp_value_t         *v;
+    int                  n;
+
+    if ((nelemm = native_member(t, m->size.idx)) == NULL)
+        return -1;
+    if (nelemm->any.layout == MRP_LAYOUT_INDIRECT)
+        v = *(void **)(base + nelemm->any.offs);
+    else
+        v = base + nelemm->any.offs;
+
+    switch (nelemm->any.type) {
+    case MRP_TYPE_INT8:   n = v->s8;       break;
+    case MRP_TYPE_UINT8:  n = v->u8;       break;
+    case MRP_TYPE_INT16:  n = v->s16;      break;
+    case MRP_TYPE_UINT16: n = v->u16;      break;
+    case MRP_TYPE_INT32:  n = v->s32;      break;
+    case MRP_TYPE_UINT32: n = v->u32;      break;
+    case MRP_TYPE_INT64:  n = (int)v->s64; break;
+    case MRP_TYPE_UINT64: n = (int)v->u64; break;
+
+    case MRP_TYPE_INT:    n = (int)           v->i;   break;
+    case MRP_TYPE_UINT:   n = (unsigned int)  v->ui;  break;
+    case MRP_TYPE_SHORT:  n = (short)         v->si;  break;
+    case MRP_TYPE_USHORT: n = (unsigned short)v->usi; break;
+    case MRP_TYPE_SIZET:  n = (size_t)        v->sz;  break;
+    case MRP_TYPE_SSIZET: n = (ssize_t)       v->ssz; break;
+
+    default:
+        errno = EINVAL;
+        return -1;
+    }
+
+    return n;
+}
+
+
+static inline int get_guarded_array_size(void *arrp, mrp_native_array_t *m)
+{
+    mrp_value_t *guard;
+    size_t       goffs, gsize, esize;
+    int          n;
+
+    if ((esize = type_size(m->elem.id)) == 0)
+        return -1;
+
+    if (guard_offset_and_size(m, &goffs, &gsize) < 0)
+        return -1;
+
+    guard = &m->sentinel;
+
+    for (n = 0; memcmp(arrp + n * esize + goffs, guard, gsize); n++)
+            ;
+    return n;
+}
+
+
+static int get_array_size(void *base, mrp_native_type_t *t, void *arrp,
+                          mrp_native_array_t *m, size_t *nelemp,
+                          size_t *esizep)
+{
+    int n;
+
+    if ((*esizep = type_size(m->elem.id)) == 0)
+        return -1;
+
+    switch (m->kind) {
+    case MRP_ARRAY_SIZE_FIXED:
+        *nelemp = m->size.nelem;
+        return 0;
+
+    case MRP_ARRAY_SIZE_EXPLICIT:
+        if ((n = get_explicit_array_size(base, t, m)) < 0)
+            return -1;
+
+        *nelemp = (size_t)n;
+        return 0;
+
+    case MRP_ARRAY_SIZE_GUARDED:
+        if ((n = get_guarded_array_size(arrp, m)) < 0)
+            return -1;
+
+        *nelemp = (size_t)n;
+        return 0;
+
+    default:
+        return -1;
+    }
+}
+
+
+static int terminate_guarded_array(void *elem, mrp_native_array_t *m,
+                                   mrp_native_type_t *mt)
+{
+    mrp_native_member_t *g;
+
+    if (m->elem.id <= MRP_TYPE_STRING)
+        memcpy(elem, &m->sentinel, mt->size);
+    else if (m->elem.id > MRP_TYPE_STRUCT) {
+        if ((g = native_member(mt, m->size.idx)) == NULL)
+            return -1;
+
+        memcpy(elem + g->any.offs, &m->sentinel, type_size(g->any.type));
+    }
+
+    return 0;
+}
+
+
+static int encode_array(mrp_tlv_t *tlv, void *arrp, mrp_native_array_t *m,
+                        size_t nelem, size_t elem_size, mrp_typemap_t *idmap)
+{
+    mrp_native_type_t *t;
+    mrp_value_t       *v;
+    void              *elem;
+    size_t             i;
+
+    if (mrp_tlv_push_uint32(tlv, TAG_ARRAY, map_type(m->elem.id, idmap)) < 0)
+        return -1;
+
+    if (mrp_tlv_push_uint32(tlv, TAG_NELEM, nelem) < 0)
+        return -1;
+
+    if ((t = lookup_type(m->elem.id)) == NULL)
+        return -1;
+
+    for (i = 0, elem = arrp; i < nelem; i++, elem += elem_size) {
+        v = elem;
+
+        switch (t->id) {
+        case MRP_TYPE_STRING:
+            v = *(void **)elem;
+        case MRP_TYPE_INT8:
+        case MRP_TYPE_UINT8:
+        case MRP_TYPE_INT16:
+        case MRP_TYPE_UINT16:
+        case MRP_TYPE_INT32:
+        case MRP_TYPE_UINT32:
+        case MRP_TYPE_INT64:
+        case MRP_TYPE_UINT64:
+        case MRP_TYPE_FLOAT:
+        case MRP_TYPE_DOUBLE:
+        case MRP_TYPE_BOOL:
+        case MRP_TYPE_INT:
+        case MRP_TYPE_UINT:
+        case MRP_TYPE_SHORT:
+        case MRP_TYPE_USHORT:
+        case MRP_TYPE_SIZET:
+        case MRP_TYPE_SSIZET:
+            if (encode_basic(tlv, t->id, v) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_BLOB: /* XXX TODO implement blobs */
+            return -1;
+
+        case MRP_TYPE_ARRAY:
+            return -1;
+
+        default:
+            /* an MRP_TYPE_STRUCT */
+            if (encode_struct(tlv, elem, t, idmap) < 0)
+                return -1;
+            break;
+        }
+    }
+
+    return 0;
+}
+
+
+static int encode_struct(mrp_tlv_t *tlv, void *data, mrp_native_type_t *t,
+                         mrp_typemap_t *idmap)
+{
+    mrp_native_member_t *m;
+    mrp_native_type_t   *mt;
+    mrp_value_t         *v;
+    uint32_t             idx;
+    size_t               size, nelem;
+
+    if (t == NULL)
+        return -1;
+
+    if (mrp_tlv_push_uint32(tlv, TAG_STRUCT, map_type(t->id, idmap)) < 0)
+        return -1;
+
+    for (idx = 0, m = t->members; idx < t->nmember; idx++, m++) {
+        if (mrp_tlv_push_uint32(tlv, TAG_MEMBER, idx) < 0)
+            return -1;
+
+        if (m->any.layout == MRP_LAYOUT_INDIRECT)
+            v = *(void **)(data + m->any.offs);
+        else
+            v = data + m->any.offs;
+
+        switch (m->any.type) {
+        case MRP_TYPE_INT8:
+        case MRP_TYPE_UINT8:
+        case MRP_TYPE_INT16:
+        case MRP_TYPE_UINT16:
+        case MRP_TYPE_INT32:
+        case MRP_TYPE_UINT32:
+        case MRP_TYPE_INT64:
+        case MRP_TYPE_UINT64:
+        case MRP_TYPE_FLOAT:
+        case MRP_TYPE_DOUBLE:
+        case MRP_TYPE_BOOL:
+        case MRP_TYPE_STRING:
+        case MRP_TYPE_INT:
+        case MRP_TYPE_UINT:
+        case MRP_TYPE_SHORT:
+        case MRP_TYPE_USHORT:
+        case MRP_TYPE_SIZET:
+        case MRP_TYPE_SSIZET:
+            if (encode_basic(tlv, m->any.type, v) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_BLOB: /* XXX TODO implement blobs */
+            if (get_blob_size(data, t, &m->blob, &size) < 0)
+                return -1;
+            return -1;
+
+        case MRP_TYPE_ARRAY:
+            if (get_array_size(data, t, v->ptr, &m->array, &nelem, &size) < 0)
+                return -1;
+            if (encode_array(tlv, v->ptr, &m->array, nelem,
+                             size, idmap) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_STRUCT:
+            if ((mt = lookup_type(m->strct.data_type.id)) == NULL)
+                return -1;
+            if (encode_struct(tlv, v->ptr, mt, idmap) < 0)
+                return -1;
+            break;
+
+        default:
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+int mrp_encode_native(void *data, uint32_t id, size_t reserve, void **bufp,
+                      size_t *sizep, mrp_typemap_t *idmap)
+{
+    mrp_native_type_t *t = lookup_type(id);
+    mrp_tlv_t          tlv;
+
+    *bufp  = NULL;
+    *sizep = 0;
+
+    if (t == NULL)
+        return -1;
+
+    if (mrp_tlv_setup_write(&tlv, reserve + 4096) < 0)
+        return -1;
+
+    if (reserve > 0)
+        if (mrp_tlv_reserve(&tlv, reserve, 1) == NULL)
+            goto fail;
+
+    if (encode_struct(&tlv, data, t, idmap) < 0)
+        goto fail;
+
+    mrp_tlv_trim(&tlv);
+    mrp_tlv_steal(&tlv, bufp, sizep);
+
+    return 0;
+
+ fail:
+    mrp_tlv_cleanup(&tlv);
+    return -1;
+}
+
+
+static void *allocate_indirect(mrp_list_hook_t **chunks, mrp_value_t *v,
+                               mrp_native_member_t *m, mrp_typemap_t *idmap)
+{
+    size_t size;
+
+    switch (m->any.type) {
+    case MRP_TYPE_INT8:
+    case MRP_TYPE_UINT8:
+        return (v->ptr = alloc_chunk(chunks, sizeof(int8_t)));
+    case MRP_TYPE_INT16:
+    case MRP_TYPE_UINT16:
+        return (v->ptr = alloc_chunk(chunks, sizeof(int16_t)));
+    case MRP_TYPE_INT32:
+    case MRP_TYPE_UINT32:
+        return (v->ptr = alloc_chunk(chunks, sizeof(int32_t)));
+    case MRP_TYPE_INT64:
+    case MRP_TYPE_UINT64:
+        return (v->ptr = alloc_chunk(chunks, sizeof(int64_t)));
+    case MRP_TYPE_FLOAT:
+        return (v->ptr = alloc_chunk(chunks, sizeof(float)));
+    case MRP_TYPE_DOUBLE:
+        return (v->ptr = alloc_chunk(chunks, sizeof(double)));
+    case MRP_TYPE_BOOL:
+        return (v->ptr = alloc_chunk(chunks, sizeof(bool)));
+    case MRP_TYPE_STRING:
+        return v;                        /* will be allocated by TLV pull */
+    case MRP_TYPE_BLOB:
+        return v;                        /* will be allocated by decoder */
+    case MRP_TYPE_ARRAY:
+        return v;                        /* will be allocated by decoder */
+    case MRP_TYPE_STRUCT:
+        if ((size = type_size(mapped_type(m->strct.data_type.id, idmap))) == 0)
+            return NULL;
+        return (v->ptr = alloc_chunk(chunks, size));
+    default:
+        return NULL;
+    }
+}
+
+
+static void *alloc_str_chunk(size_t size, void *chunksp)
+{
+    return alloc_chunk((mrp_list_hook_t **)chunksp, size);
+}
+
+
+static int decode_basic(mrp_tlv_t *tlv, mrp_list_hook_t **chunks,
+                        mrp_type_t type, mrp_value_t *v)
+{
+    int32_t  i;
+    uint32_t u;
+
+    switch (type) {
+    case MRP_TYPE_INT8:    return mrp_tlv_pull_int8  (tlv, TAG_NONE, &v->s8);
+    case MRP_TYPE_UINT8:   return mrp_tlv_pull_uint8 (tlv, TAG_NONE, &v->u8);
+    case MRP_TYPE_INT16:   return mrp_tlv_pull_int16 (tlv, TAG_NONE, &v->s16);
+    case MRP_TYPE_UINT16:  return mrp_tlv_pull_uint16(tlv, TAG_NONE, &v->u16);
+    case MRP_TYPE_INT32:   return mrp_tlv_pull_int32 (tlv, TAG_NONE, &v->s32);
+    case MRP_TYPE_UINT32:  return mrp_tlv_pull_uint32(tlv, TAG_NONE, &v->u32);
+    case MRP_TYPE_INT64:   return mrp_tlv_pull_int64 (tlv, TAG_NONE, &v->s64);
+    case MRP_TYPE_UINT64:  return mrp_tlv_pull_uint64(tlv, TAG_NONE, &v->u64);
+    case MRP_TYPE_FLOAT:   return mrp_tlv_pull_float (tlv, TAG_NONE, &v->flt);
+    case MRP_TYPE_DOUBLE:  return mrp_tlv_pull_double(tlv, TAG_NONE, &v->dbl);
+    case MRP_TYPE_BOOL:    return mrp_tlv_pull_bool  (tlv, TAG_NONE, &v->bln);
+    case MRP_TYPE_STRING:
+        return mrp_tlv_pull_string(tlv, TAG_NONE, &v->strp,
+                                   -1, alloc_str_chunk, chunks);
+
+    case MRP_TYPE_INT:
+        if (mrp_tlv_pull_int32(tlv, TAG_NONE, &i) < 0)
+            return -1;
+        v->i = (int)i;
+        return 0;
+
+    case MRP_TYPE_UINT:
+        if (mrp_tlv_pull_uint32(tlv, TAG_NONE, &u) < 0)
+            return -1;
+        v->ui = (unsigned int)u;
+        return 0;
+
+    case MRP_TYPE_SHORT:
+        if (mrp_tlv_pull_int32(tlv, TAG_NONE, &i) < 0)
+            return -1;
+        v->si = (short)i;
+        return 0;
+
+    case MRP_TYPE_USHORT:
+        if (mrp_tlv_pull_uint32(tlv, TAG_NONE, &u) < 0)
+            return -1;
+        v->usi = (unsigned short)u;
+        return 0;
+
+    case MRP_TYPE_SIZET:
+        if (mrp_tlv_pull_uint32(tlv, TAG_NONE, &u) < 0)
+            return -1;
+        v->sz = (size_t)u;
+        return 0;
+
+    case MRP_TYPE_SSIZET:
+        if (mrp_tlv_pull_int32(tlv, TAG_NONE, &i) < 0)
+            return -1;
+        v->ssz = (ssize_t)i;
+        return 0;
+
+    default:
+        return -1;
+    }
+}
+
+
+static int decode_array(mrp_tlv_t *tlv, mrp_list_hook_t **chunks,
+                        void **arrp, mrp_native_array_t *m,
+                        void *data, mrp_native_type_t *t,
+                        mrp_typemap_t *idmap)
+{
+    mrp_native_type_t *mt;
+    mrp_value_t       *v;
+    void              *elem, *base;
+    size_t             elem_size, i;
+    uint32_t           id, nelem;
+    int                n, guard;
+
+    if (mrp_tlv_pull_uint32(tlv, TAG_ARRAY, &id) < 0)
+        return -1;
+
+    if ((id = mapped_type(id, idmap)) != m->elem.id)
+        return -1;
+
+    if ((elem_size = type_size(id)) == 0)
+        return -1;
+
+    if (mrp_tlv_pull_uint32(tlv, TAG_NELEM, &nelem) < 0)
+        return -1;
+
+    if ((mt = lookup_type(m->elem.id)) == NULL)
+        return -1;
+
+    switch (m->kind) {
+    case MRP_ARRAY_SIZE_EXPLICIT:
+        if ((n = get_explicit_array_size(data, t, m)) < 0)
+            return -1;
+        guard = 0;
+        break;
+    case MRP_ARRAY_SIZE_FIXED:
+        n     = m->size.nelem;
+        guard = 0;
+        break;
+    case MRP_ARRAY_SIZE_GUARDED:
+        n     = nelem;
+        guard = 1;
+        break;
+    default:
+        return -1;
+    }
+
+    if (n != (int)nelem)
+        return -1;
+
+    switch (m->layout) {
+    case MRP_LAYOUT_INLINED:
+        base = (void *)arrp;
+        break;
+    case MRP_LAYOUT_INDIRECT:
+    case MRP_LAYOUT_DEFAULT:
+        if ((*arrp = alloc_chunk(chunks, (nelem + guard) * elem_size)) == NULL)
+            return (nelem + guard) ? -1 : 0;
+        base = *arrp;
+        break;
+    default:
+        return -1;
+    }
+
+    for (i = 0, elem = base; i < nelem; i++, elem += elem_size) {
+        v = elem;
+
+        switch (mt->id) {
+        case MRP_TYPE_INT8:
+        case MRP_TYPE_UINT8:
+        case MRP_TYPE_INT16:
+        case MRP_TYPE_UINT16:
+        case MRP_TYPE_INT32:
+        case MRP_TYPE_UINT32:
+        case MRP_TYPE_INT64:
+        case MRP_TYPE_UINT64:
+        case MRP_TYPE_FLOAT:
+        case MRP_TYPE_DOUBLE:
+        case MRP_TYPE_BOOL:
+        case MRP_TYPE_STRING:
+        case MRP_TYPE_INT:
+        case MRP_TYPE_UINT:
+        case MRP_TYPE_SHORT:
+        case MRP_TYPE_USHORT:
+        case MRP_TYPE_SIZET:
+        case MRP_TYPE_SSIZET:
+            if (decode_basic(tlv, chunks, mt->id, v) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_BLOB: /* XXX TODO implement blobs */
+            return -1;
+
+        case MRP_TYPE_ARRAY:
+            return -1;
+
+        default:
+            /* an MRP_TYPE_STRUCT */
+            if (decode_struct(tlv, chunks, &elem, &id, idmap) < 0)
+                return -1;
+        }
+    }
+
+    if (guard) {
+        if (terminate_guarded_array(elem, m, mt) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+
+static int decode_struct(mrp_tlv_t *tlv, mrp_list_hook_t **chunks,
+                         void **datap, uint32_t *idp, mrp_typemap_t *idmap)
+{
+    mrp_native_type_t   *t;
+    mrp_native_member_t *m;
+    mrp_value_t         *v;
+    char                *str, **strp;
+    size_t               max, i;
+    uint32_t             idx, id;
+
+    if (datap == NULL) {
+        errno = EFAULT;
+        return -1;
+    }
+
+    if (mrp_tlv_pull_uint32(tlv, TAG_STRUCT, &id) < 0)
+        return -1;
+    else
+        id = mapped_type(id, idmap);
+
+    if (*idp) {
+        if (*idp != id) {
+            errno = EINVAL;
+            return -1;
+        }
+    }
+    else
+        *idp = id;
+
+    if ((t = lookup_type(id)) == NULL)
+        return -1;
+
+    if (*datap == NULL)
+        if ((*datap = alloc_chunk(chunks, t->size)) == NULL)
+            return -1;
+
+    for (i = 0, m = t->members; i < t->nmember; i++, m++) {
+        if (mrp_tlv_pull_uint32(tlv, TAG_MEMBER, &idx) < 0)
+            return -1;
+
+        v = *datap + m->any.offs;
+
+        if (m->any.layout == MRP_LAYOUT_INDIRECT) {
+            if ((v = allocate_indirect(chunks, v, m, idmap)) == NULL)
+                return -1;
+        }
+
+        switch (m->any.type) {
+        case MRP_TYPE_INT8:
+        case MRP_TYPE_UINT8:
+        case MRP_TYPE_INT16:
+        case MRP_TYPE_UINT16:
+        case MRP_TYPE_INT32:
+        case MRP_TYPE_UINT32:
+        case MRP_TYPE_INT64:
+        case MRP_TYPE_UINT64:
+        case MRP_TYPE_FLOAT:
+        case MRP_TYPE_DOUBLE:
+        case MRP_TYPE_BOOL:
+        case MRP_TYPE_INT:
+        case MRP_TYPE_UINT:
+        case MRP_TYPE_SHORT:
+        case MRP_TYPE_USHORT:
+        case MRP_TYPE_SIZET:
+        case MRP_TYPE_SSIZET:
+            if (decode_basic(tlv, chunks, m->any.type, v) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_STRING:
+            if (m->any.layout == MRP_LAYOUT_INLINED) {
+                max  = m->str.size;
+                str  = v->str;
+                strp = &str;
+            }
+            else {
+                max  = (size_t)-1;
+                strp = &v->strp;
+            }
+            if (mrp_tlv_pull_string(tlv, TAG_NONE, strp, max,
+                                    alloc_str_chunk, chunks) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_BLOB: /* XXX TODO implement blobs */
+            return -1;
+
+        case MRP_TYPE_ARRAY:
+            if (decode_array(tlv, chunks, &v->ptr, &m->array,
+                             *datap, t, idmap) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_STRUCT:
+            id = m->strct.data_type.id;
+            if (decode_struct(tlv, chunks, &v->ptr, &id, idmap) < 0)
+                return -1;
+            break;
+
+        default:
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+int mrp_decode_native(void **bufp, size_t *sizep, void **datap, uint32_t *idp,
+                      mrp_typemap_t *idmap)
+{
+    mrp_tlv_t        tlv;
+    mrp_list_hook_t *chunks;
+    void            *data;
+    size_t           diff;
+
+    chunks = NULL;
+    data   = NULL;
+
+    if (mrp_tlv_setup_read(&tlv, *bufp, *sizep) < 0)
+        return -1;
+
+    if (decode_struct(&tlv, &chunks, &data, idp, idmap) == 0) {
+        diff = mrp_tlv_offset(&tlv);
+
+        if (diff <= *sizep) {
+            *bufp  += diff;
+            *sizep -= diff;
+            *datap  = data;
+
+            return 0;
+        }
+    }
+
+    free_chunks(chunks);
+
+    return -1;
+}
+
+
+void mrp_free_native(void *data, uint32_t id)
+{
+    mrp_list_hook_t *chunks;
+
+    MRP_UNUSED(id);
+
+    if (data != NULL) {
+        chunks = ((void *)data) - MRP_OFFSET(chunk_t, data);
+        free_chunks(chunks);
+    }
+}
+
+
+#define INDENT(_level, _fmt) "%*.*s"_fmt, _level * 4, _level * 4, ""
+
+#define PRINT(_l, _p, _size, fmt, args...) do {                     \
+        ssize_t _n;                                                 \
+        _n = snprintf((_p), (_size), INDENT(_l, fmt), ## args);     \
+        if (_n >= (ssize_t)(_size))                                 \
+            return -1;                                              \
+        (_p)    += _n;                                              \
+        (_size) -= _n;                                              \
+    } while (0)
+
+
+static int print_basic(int level, char **bufp, size_t *sizep, int type,
+                       const char *name, mrp_value_t *v)
+{
+#define NAME name ? name : "", name ? " = " : ""
+    char    *p    = *bufp;
+    size_t   size = *sizep;
+
+    if (type >= MRP_TYPE_BLOB)
+        return -1;
+
+    switch (type) {
+    case MRP_TYPE_INT8:
+        PRINT(level, p, size, "%s%s%d\n", NAME, v->s8);
+        break;
+    case MRP_TYPE_UINT8:
+        PRINT(level, p, size, "%s%s%u\n", NAME, v->u8);
+        break;
+
+    case MRP_TYPE_INT16:
+        PRINT(level, p, size, "%s%s%d\n", NAME, v->s16);
+        break;
+    case MRP_TYPE_UINT16:
+        PRINT(level, p, size, "%s%s%u\n", NAME, v->u16);
+        break;
+
+    case MRP_TYPE_INT32:
+        PRINT(level, p, size, "%s%s%d\n", NAME, v->s32);
+        break;
+    case MRP_TYPE_UINT32:
+        PRINT(level, p, size, "%s%s%u\n", NAME, v->u32);
+        break;
+
+    case MRP_TYPE_INT64:
+        PRINT(level, p, size, "%s%s%lld\n", NAME, (long long)v->s64);
+        break;
+    case MRP_TYPE_UINT64:
+        PRINT(level, p, size, "%s%s%llu\n", NAME,
+              (unsigned long long)v->s64);
+        break;
+
+    case MRP_TYPE_FLOAT:
+        PRINT(level, p, size, "%s%s%f\n", NAME, v->flt);
+        break;
+    case MRP_TYPE_DOUBLE:
+        PRINT(level, p, size, "%s%s%f\n", NAME, v->dbl);
+        break;
+
+    case MRP_TYPE_BOOL:
+        PRINT(level, p, size, "%s%s%s\n", NAME,
+              v->bln ? "<true>" : "<false>");
+        break;
+
+    case MRP_TYPE_STRING:
+        PRINT(level, p, size, "%s%s%s\n", NAME,
+              v->str ? v->str : "<null>");
+        break;
+
+    case MRP_TYPE_INT:
+        PRINT(level, p, size, "%s%s%d\n", NAME, v->i);
+        break;
+    case MRP_TYPE_UINT:
+        PRINT(level, p, size, "%s%s%u\n", NAME, v->ui);
+        break;
+
+    case MRP_TYPE_SHORT:
+        PRINT(level, p, size, "%s%s%hd\n", NAME, v->si);
+        break;
+    case MRP_TYPE_USHORT:
+        PRINT(level, p, size, "%s%s%hu\n", NAME, v->usi);
+        break;
+
+    case MRP_TYPE_SIZET:
+        PRINT(level, p, size, "%s%s%zu\n", NAME, v->sz);
+        break;
+    case MRP_TYPE_SSIZET:
+        PRINT(level, p, size, "%s%s%zd\n", NAME, v->ssz);
+        break;
+
+    default:
+        PRINT(level, p, size, "%s%s%s\n", NAME, "<unknown>");
+    }
+
+    *bufp  = p;
+    *sizep = size;
+
+    return 0;
+
+#undef NAME
+}
+
+
+static int print_array(char **bufp, size_t *sizep, int level,
+                       void *arrp, mrp_native_array_t *a, size_t nelem,
+                       size_t elem_size)
+{
+    mrp_native_type_t *et;
+    mrp_value_t       *v;
+    void              *elem;
+    size_t             i;
+    char              *p;
+    size_t             size;
+
+    p    = *bufp;
+    size = *sizep;
+
+    if ((et = lookup_type(a->elem.id)) == NULL)
+        return -1;
+
+    PRINT(level, p, size, "%s = [%s", a->name, nelem == 0 ? "]" : "\n");
+    level++;
+
+    for (i = 0, elem = arrp; i < nelem; i++, elem += elem_size) {
+        v = elem;
+
+        switch (et->id) {
+        case MRP_TYPE_STRING:
+            v = *(void **)elem;
+        case MRP_TYPE_INT8:
+        case MRP_TYPE_UINT8:
+        case MRP_TYPE_INT16:
+        case MRP_TYPE_UINT16:
+        case MRP_TYPE_INT32:
+        case MRP_TYPE_UINT32:
+        case MRP_TYPE_INT64:
+        case MRP_TYPE_UINT64:
+        case MRP_TYPE_FLOAT:
+        case MRP_TYPE_DOUBLE:
+        case MRP_TYPE_BOOL:
+        case MRP_TYPE_INT:
+        case MRP_TYPE_UINT:
+        case MRP_TYPE_SHORT:
+        case MRP_TYPE_USHORT:
+        case MRP_TYPE_SIZET:
+        case MRP_TYPE_SSIZET:
+            if (print_basic(level, &p, &size, et->id, NULL, v) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_BLOB:
+            PRINT(level, p, size, "<blob>\n");
+            break;
+
+        case MRP_TYPE_ARRAY:
+            return -1;
+
+        default:
+            /* an MRP_TYPE_STRUCT */
+            if (print_struct(&p, &size, level, elem, et) < 0)
+                return -1;
+            break;
+        }
+    }
+
+    level--;
+    PRINT(level, p, size, "%s\n", nelem == 0 ? "" : "]");
+
+    *bufp  = p;
+    *sizep = size;
+
+    return 0;
+}
+
+
+static int print_struct(char **bufp, size_t *sizep, int level,
+                        void *data, mrp_native_type_t *t)
+{
+    mrp_native_member_t *m;
+    mrp_native_type_t   *mt;
+    mrp_value_t         *v;
+    uint32_t             idx;
+    size_t               esize, nelem;
+    char                *p;
+    size_t               size;
+
+    if (data == NULL) {
+        **bufp = '\0';
+
+        return 0;
+    }
+
+    if (t == NULL)
+        return -1;
+
+    p    = *bufp;
+    size = *sizep;
+    PRINT(level, p, size, "{\n");
+    level++;
+
+    for (idx = 0, m = t->members; idx < t->nmember; idx++, m++) {
+        if (m->any.layout == MRP_LAYOUT_INDIRECT)
+            v = *(void **)(data + m->any.offs);
+        else
+            v = data + m->any.offs;
+
+        switch (m->any.type) {
+        case MRP_TYPE_INT8:
+        case MRP_TYPE_UINT8:
+        case MRP_TYPE_INT16:
+        case MRP_TYPE_UINT16:
+        case MRP_TYPE_INT32:
+        case MRP_TYPE_UINT32:
+        case MRP_TYPE_INT64:
+        case MRP_TYPE_UINT64:
+        case MRP_TYPE_FLOAT:
+        case MRP_TYPE_DOUBLE:
+        case MRP_TYPE_BOOL:
+        case MRP_TYPE_INT:
+        case MRP_TYPE_UINT:
+        case MRP_TYPE_SHORT:
+        case MRP_TYPE_USHORT:
+        case MRP_TYPE_SIZET:
+        case MRP_TYPE_SSIZET:
+        case MRP_TYPE_STRING:
+            if (print_basic(level, &p, &size, m->any.type, m->any.name, v) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_BLOB: /* XXX TODO implement blobs */
+            PRINT(level, p, size, "%s = <blob>\n", m->any.name);
+            break;
+
+        case MRP_TYPE_ARRAY:
+            if (get_array_size(data, t, v->ptr, &m->array, &nelem, &esize) < 0)
+                return -1;
+            if (print_array(&p, &size, level, v->ptr, &m->array,
+                            nelem, esize) < 0)
+                return -1;
+            break;
+
+        case MRP_TYPE_STRUCT:
+            if ((mt = lookup_type(m->strct.data_type.id)) == NULL)
+                return -1;
+            if (print_struct(&p, &size, level, v->ptr, mt) < 0)
+                return -1;
+            break;
+
+        default:
+            return -1;
+        }
+    }
+
+    level--;
+    PRINT(level, p, size, "}\n");
+
+    *bufp  = p;
+    *sizep = size;
+
+    return 0;
+}
+
+
+ssize_t mrp_print_native(char *buf, size_t size, void *data, uint32_t id)
+{
+    mrp_native_type_t *t;
+    char              *p;
+
+    p = buf;
+
+    if (id < MRP_TYPE_STRUCT || (t = lookup_type(id)) == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (print_struct(&p, &size, 0, data, t) == 0)
+        return (ssize_t)(p - buf);
+    else
+        return -1;
+}
+
+
+static inline size_t chunk_size(size_t size)
+{
+    return MRP_OFFSET(chunk_t, data[size]);
+}
+
+
+static void *alloc_chunk(mrp_list_hook_t **chunks, size_t size)
+{
+    chunk_t *chunk;
+
+    if (size == 0)
+        return NULL;
+
+    if (*chunks == NULL) {
+        if ((*chunks = mrp_allocz(sizeof(*chunks))) == NULL)
+            return NULL;
+        else
+            mrp_list_init(*chunks);
+    }
+
+    if ((chunk = mrp_allocz(chunk_size(size))) == NULL)
+        return NULL;
+
+    mrp_list_init(&chunk->hook);
+    mrp_list_append(*chunks, &chunk->hook);
+
+    return &chunk->data[0];
+}
+
+
+static void free_chunks(mrp_list_hook_t *chunks)
+{
+    mrp_list_hook_t *p, *n;
+
+    if (chunks != NULL) {
+        mrp_list_foreach(chunks, p, n) {
+            mrp_list_delete(p);
+
+            if (p != chunks)
+                mrp_free(p);
+        }
+
+        mrp_free(chunks);
+    }
+}
diff --git a/src/common/native-types.h b/src/common/native-types.h
new file mode 100644 (file)
index 0000000..ac6dacf
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_COMMON_NATIVE_TYPES_H__
+#define __MURPHY_COMMON_NATIVE_TYPES_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/list.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_INVALID_TYPE ((uint32_t)-1)
+
+
+/**
+ * pre-defined native type ids
+ */
+
+typedef enum {
+    MRP_TYPE_UNKNOWN = 0,
+    MRP_TYPE_INT8,
+    MRP_TYPE_UINT8,
+    MRP_TYPE_INT16,
+    MRP_TYPE_UINT16,
+    MRP_TYPE_INT32,
+    MRP_TYPE_UINT32,
+    MRP_TYPE_INT64,
+    MRP_TYPE_UINT64,
+    MRP_TYPE_FLOAT,
+    MRP_TYPE_DOUBLE,
+    MRP_TYPE_BOOL,
+    MRP_TYPE_INT,
+    MRP_TYPE_UINT,
+    MRP_TYPE_SHORT,
+    MRP_TYPE_USHORT,
+    MRP_TYPE_SIZET,
+    MRP_TYPE_SSIZET,
+    MRP_TYPE_STRING,
+    MRP_TYPE_BLOB,
+    MRP_TYPE_ARRAY,
+    MRP_TYPE_STRUCT,
+    MRP_TYPE_MAX
+} mrp_type_t;
+
+
+/**
+ * data type values
+ */
+
+typedef union {
+    int8_t    s8;
+    int8_t   *s8p;
+    uint8_t   u8;
+    uint8_t  *u8p;
+    int16_t   s16;
+    int16_t  *s16p;
+    uint16_t  u16;
+    uint16_t *u16p;
+    int32_t   s32;
+    int32_t  *s32p;
+    uint32_t  u32;
+    uint32_t *u32p;
+    int64_t   s64;
+    int64_t  *s64p;
+    uint64_t  u64;
+    uint64_t *u64p;
+    float     flt;
+    float    *fltp;
+    double    dbl;
+    double   *dblp;
+    bool      bln;
+    bool     *blnp;
+    void     *blb;
+    char      str[0];
+    char     *strp;
+    int             i;
+    int            *ip;
+    unsigned int    ui;
+    unsigned int   *uip;
+    short           si;
+    short          *sip;
+    unsigned short  usi;
+    unsigned short *usip;
+    size_t          sz;
+    size_t         *szp;
+    ssize_t         ssz;
+    ssize_t        *sszp;
+    void     *ptr;
+    void    **ptrp;
+} mrp_value_t;
+
+
+/**
+ * type id map (for transport-specific mapping of type ids)
+ */
+
+typedef struct {
+    uint32_t type_id;                    /* native type id */
+    uint32_t mapped;                     /* mapped type id */
+} mrp_typemap_t;
+
+
+/** Macro to initialize a typemap entry. */
+#define MRP_TYPEMAP(_mapped_id, _type_id)               \
+    { .type_id = _type_id, .mapped = _mapped_id }
+
+/** Macro to set a typemap termination entry. */
+#define MRP_TYPEMAP_END                                 \
+    { MRP_INVALID_TYPE, MRP_INVALID_TYPE }
+
+/**
+ * type and member descriptors
+ */
+
+typedef enum {
+    MRP_LAYOUT_DEFAULT = 0,              /* default, type-specific layout */
+    MRP_LAYOUT_INLINED,                  /* inlined/embedded layout */
+    MRP_LAYOUT_INDIRECT,                 /* indirect layout */
+} mrp_layout_t;
+
+#define MRP_NATIVE_COMMON_FIELDS         /* fields common to all members */ \
+    char              *name;             /* name of this member */          \
+    uint32_t           type;             /* type id of this member */       \
+    size_t             offs;             /* offset from base pointer */     \
+    mrp_layout_t       layout            /* member layout */
+
+typedef struct {
+    MRP_NATIVE_COMMON_FIELDS;            /* common fields to all members */
+} mrp_native_any_t;
+
+typedef struct {                         /* a blob member */
+    MRP_NATIVE_COMMON_FIELDS;            /* common member fields */
+    union {                              /* size-indicating member */
+        char     *name;                  /*     name */
+        uint32_t  idx;                   /*     or index */
+    } size;
+} mrp_native_blob_t;
+
+typedef enum {
+    MRP_ARRAY_SIZE_EXPLICIT,             /* explicitly sized array */
+    MRP_ARRAY_SIZE_GUARDED,              /* sentinel-guarded array */
+    MRP_ARRAY_SIZE_FIXED,                /* a fixed size array */
+} mrp_array_size_t;
+
+typedef struct {
+    MRP_NATIVE_COMMON_FIELDS;            /* common member fields */
+    size_t size;                         /* inlined buffer size */
+} mrp_native_string_t;
+
+typedef struct {                         /* an array member */
+    MRP_NATIVE_COMMON_FIELDS;            /* common member fields */
+    mrp_array_size_t kind;               /* which kind of array */
+    union {                              /* contained element type */
+        char     *name;                  /*     name */
+        uint32_t  id;                    /*     or type id */
+    } elem;
+    union {                              /* size or guard member */
+        char     *name;                  /*     name */
+        uint32_t  idx;                   /*     or index */
+        size_t    nelem;                 /*     or number of elements */
+    } size;
+    mrp_value_t sentinel;                /* sentinel value, if guarded */
+} mrp_native_array_t;
+
+typedef struct {                         /* member of type struct */
+    MRP_NATIVE_COMMON_FIELDS;            /* common member fields */
+    union {                              /* struct type */
+        char     *name;                  /*     name */
+        uint32_t  id;                    /*     or type id */
+    } data_type;
+} mrp_native_struct_t;
+
+typedef union {
+    mrp_native_any_t    any;
+    mrp_native_string_t str;
+    mrp_native_blob_t   blob;
+    mrp_native_array_t  array;
+    mrp_native_struct_t strct;
+} mrp_native_member_t;
+
+typedef struct {
+    char                *name;           /* name of this type */
+    uint32_t             id;             /* assigned id for this type */
+    size_t               size;           /* size of this type */
+    mrp_native_member_t *members;        /* members of this type if any */
+    size_t               nmember;        /* number of members */
+    mrp_list_hook_t      hook;           /* to list of registered types */
+} mrp_native_type_t;
+
+
+/** Helper macro to initialize native member fields. */
+#define __MRP_MEMBER_INIT(_objtype, _member, _type)                     \
+        .name   = #_member,                                             \
+        .type   = _type,                                                \
+        .offs   = MRP_OFFSET(_objtype, _member)
+
+/** Helper macro to declare a native member with a given type an layout. */
+#define __MRP_MEMBER(_objtype, _type, _member, _layout)                 \
+    {                                                                   \
+        .any = {                                                        \
+            __MRP_MEMBER_INIT(_objtype, _member, _type),                \
+            .layout = MRP_LAYOUT_##_layout,                             \
+        }                                                               \
+    }
+
+/** Declare an indirect string member of the native type. */
+#define MRP_INDIRECT_STRING(_objtype, _member, _size)                   \
+    __MRP_MEMBER(_objtype, _member, MRP_TYPE_STRING, INDIRECT)
+
+/** Declare an inlined string member of the native type. */
+#define MRP_INLINED_STRING(_objtype, _member, _size)                    \
+    {                                                                   \
+        .str = {                                                        \
+            __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_STRING),      \
+            .layout = MRP_LAYOUT_INLINED,                               \
+            .size   = _size,                                            \
+        }                                                               \
+    }
+
+/** By default declare a string members indirect. */
+#define MRP_DEFAULT_STRING(_objtype, _member, _size)                    \
+    __MRP_MEMBER(_objtype, MRP_TYPE_STRING, _member, INDIRECT)
+
+/** Declare an explicitly sized array member of the native typet. */
+#define MRP_SIZED_ARRAY(_objtype, _member, _layout, _type, _size)       \
+    {                                                                   \
+        .array = {                                                      \
+            __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_ARRAY),       \
+            .layout  = MRP_LAYOUT_##_layout,                            \
+            .kind    = MRP_ARRAY_SIZE_EXPLICIT,                         \
+            .elem    = { .name = #_type, },                             \
+            .size    = { .name = #_size, },                             \
+        }                                                               \
+    }
+
+/** Declare a sentinel-guarded array member of the native type. */
+#define MRP_GUARDED_ARRAY(_objtype, _member, _layout, _type, _guard,    \
+                          ...)                                          \
+    {                                                                   \
+        .array = {                                                      \
+            __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_ARRAY),       \
+            .layout   = MRP_LAYOUT_##_layout,                           \
+            .kind     = MRP_ARRAY_SIZE_GUARDED,                         \
+            .elem     = { .name = #_type, },                            \
+            .size     = { .name = #_guard, },                           \
+            .sentinel = { __VA_ARGS__ },                                \
+        }                                                               \
+    }
+
+/** Declare a fixed array member of the native type. */
+#define MRP_FIXED_ARRAY(_objtype, _member, _layout, _type)              \
+    {                                                                   \
+        .array = {                                                      \
+            __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_ARRAY),       \
+            .layout   = MRP_LAYOUT_##_layout,                           \
+            .kind     = MRP_ARRAY_SIZE_FIXED,                           \
+            .elem     = { .name = #_type, },                            \
+            .size     = {                                               \
+                .nelem = MRP_ARRAY_SIZE(((_objtype *)0x0)->_member)     \
+            },                                                          \
+        }                                                               \
+    }
+
+/** Declare a struct member of the native type. */
+#define MRP_STRUCT(_objtype, _member, _layout, _type)                   \
+    {                                                                   \
+        .strct = {                                                      \
+            __MRP_MEMBER_INIT(_objtype, _member, MRP_TYPE_STRUCT),      \
+            .data_type = { .name = #_type },                            \
+        }                                                               \
+    }
+
+/** Macros for declaring basic members of the native type. */
+#define MRP_INT8(_ot, _m, _l)   __MRP_MEMBER(_ot, MRP_TYPE_INT8  , _m, _l)
+#define MRP_UINT8(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_UINT8 , _m, _l)
+#define MRP_INT16(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_INT16 , _m, _l)
+#define MRP_UINT16(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT16, _m, _l)
+#define MRP_INT32(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_INT32 , _m, _l)
+#define MRP_UINT32(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT32, _m, _l)
+#define MRP_INT64(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_INT64 , _m, _l)
+#define MRP_UINT64(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_UINT64, _m, _l)
+#define MRP_FLOAT(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_FLOAT , _m, _l)
+#define MRP_DOUBLE(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_DOUBLE, _m, _l)
+#define MRP_BOOL(_ot, _m, _l)   __MRP_MEMBER(_ot, MRP_TYPE_BOOL  , _m, _l)
+
+#define MRP_INT(_ot, _m, _l)    __MRP_MEMBER(_ot, MRP_TYPE_INT   , _m, _l)
+#define MRP_UINT(_ot, _m, _l)   __MRP_MEMBER(_ot, MRP_TYPE_UINT  , _m, _l)
+#define MRP_SHORT(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_SHORT , _m, _l)
+#define MRP_USHORT(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_USHORT, _m, _l)
+#define MRP_SIZET(_ot, _m, _l)  __MRP_MEMBER(_ot, MRP_TYPE_SIZET , _m, _l)
+#define MRP_SSIZET(_ot, _m, _l) __MRP_MEMBER(_ot, MRP_TYPE_SSIZET, _m, _l)
+
+/** Macro for declaring string members of the native type. */
+#define MRP_STRING(_objtype, _member, _layout)                  \
+    MRP_##_layout##_STRING(_objtype, _member,                   \
+                           sizeof(((_objtype *)0x0)->_member))
+
+/** Macro for declaring array members of the native type. */
+#define MRP_ARRAY(_objtype, _member, _layout, _kind, ...)       \
+    MRP_##_kind##_ARRAY(_objtype, _member, _layout, __VA_ARGS__)
+
+/** Macro to declare a native type. */
+#define MRP_NATIVE_TYPE(_var, _type, ...)                       \
+    mrp_native_member_t _var##_members[] = {                    \
+        __VA_ARGS__                                             \
+    };                                                          \
+    mrp_native_type_t   _var = {                                \
+        .id      = -1,                                          \
+        .name    = #_type,                                      \
+        .size    = sizeof(_type),                               \
+        .members = _var##_members,                              \
+        .nmember = MRP_ARRAY_SIZE(_var##_members),              \
+        .hook    = { NULL, NULL },                              \
+    }
+
+/** Declare and register the given native type. */
+uint32_t mrp_register_native(mrp_native_type_t *type);
+
+/** Look up the type id of the given native type name. */
+uint32_t mrp_native_id(const char *type_name);
+
+/** Encode data of the given native type. */
+int mrp_encode_native(void *data, uint32_t id, size_t reserve, void **bufp,
+                      size_t *sizep, mrp_typemap_t *idmap);
+
+/** Decode data of (the given) native type (if specified). */
+int mrp_decode_native(void **bufp, size_t *sizep, void **datap, uint32_t *idp,
+                      mrp_typemap_t *idmap);
+
+/** Free data of the given native type, obtained from mrp_decode_native. */
+void mrp_free_native(void *data, uint32_t id);
+
+/** Print data of the given native type. */
+ssize_t mrp_print_native(char *buf, size_t size, void *data, uint32_t id);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_COMMON_NATIVE_TYPES_H__ */
diff --git a/src/common/process.c b/src/common/process.c
new file mode 100644 (file)
index 0000000..71a10e1
--- /dev/null
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (c) 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/inotify.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <linux/cn_proc.h>
+#include <linux/netlink.h>
+#include <linux/connector.h>
+#include <linux/filter.h>
+
+#include <murphy/common/process.h>
+#include <murphy/common.h>
+
+
+#define MURPHY_PROCESS_INOTIFY_DIR "/var/run/murphy/processes"
+
+struct mrp_pid_watch_s {
+    pid_t pid;
+};
+
+typedef struct {
+    char *path; /* file name token */
+    pid_t pid;
+    char *filename;
+
+    /* either this ... */
+    mrp_process_watch_handler_t process_cb;
+    /* ... or this is set. */
+    mrp_pid_watch_handler_t pid_cb;
+
+    void *userdata;
+    mrp_io_watch_t *inotify_cb;
+} i_watch_t;
+
+typedef struct {
+    mrp_pid_watch_handler_t cb;
+    void *user_data;
+    mrp_pid_watch_t *w; /* identify the client */
+
+    mrp_list_hook_t hook;
+} nl_pid_client_t;
+
+typedef struct {
+    pid_t pid;
+    char pid_s[16]; /* memory for hashing */
+
+    mrp_list_hook_t clients;
+    int n_clients;
+    int busy : 1;
+    int dead : 1;
+} nl_pid_watch_t;
+
+/* murphy pid file directory notify */
+static int dir_fd;
+static int i_n_process_watches;
+
+/* inotify */
+static int i_fd;
+static mrp_htbl_t *i_watches;
+static mrp_io_watch_t *i_wd;
+
+/* netlink process listening */
+static int nl_sock;
+static bool subscribed;
+static mrp_io_watch_t *nl_wd;
+static mrp_htbl_t *nl_watches;
+static int nl_n_pid_watches;
+
+static bool id_ok(const char *id)
+{
+    int i, len;
+    /* restrict the input */
+
+    len = strlen(id);
+
+    for (i = 0; i < len; i++) {
+        bool character, number, special;
+
+        character = ((id[i] >= 'A' && id[i] <= 'Z') ||
+                    (id[i] >= 'a' && id[i] <= 'z'));
+
+        number = (id[i] >= '0' && id[i] <= '9');
+
+        special = (id[i] == '-' || id[i] == '_');
+
+        if (!(character || number || special))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static char *path_from_id(const char *id)
+{
+    char buf[PATH_MAX];
+    int ret;
+
+    if (!id || !id_ok(id))
+        return NULL;
+
+    ret = snprintf(buf, PATH_MAX, "%s/%s", MURPHY_PROCESS_INOTIFY_DIR, id);
+
+    if (ret < 0 || ret >= PATH_MAX)
+        return NULL;
+
+    return mrp_strdup(buf);
+}
+
+
+static void htbl_free_nl_watch(void *key, void *object)
+{
+    nl_pid_watch_t *w = (nl_pid_watch_t *) object;
+
+    MRP_UNUSED(key);
+
+    if (!w->busy)
+        mrp_free(w);
+    else
+        w->dead = TRUE;
+}
+
+
+static void htbl_free_i_watch(void *key, void *object)
+{
+    i_watch_t *w = (i_watch_t *) object;
+
+    MRP_UNUSED(key);
+
+    mrp_free(w->path);
+    mrp_free(w->filename);
+    mrp_free(w);
+}
+
+
+static int initialize_dir()
+{
+    /* TODO: check if the directory is present; if not, create it */
+
+    return 0;
+}
+
+
+static void process_change(mrp_io_watch_t *wd, int fd, mrp_io_event_t events,
+                           void *user_data)
+{
+    struct inotify_event *is;
+    int bufsize = sizeof(struct inotify_event) + PATH_MAX + 1;
+    char buf[bufsize];
+    i_watch_t *w;
+    FILE *f;
+
+    MRP_UNUSED(wd);
+    MRP_UNUSED(user_data);
+
+    if (!i_watches)
+        return;
+
+    if (events & MRP_IO_EVENT_IN) {
+        int read_bytes;
+        int processed_bytes = 0;
+
+        read_bytes = read(fd, buf, bufsize - 1);
+
+        if (read_bytes < 0) {
+            mrp_log_error("Failed to read event from inotify: %s",
+                    strerror(errno));
+            return;
+        }
+
+        buf[read_bytes] = '\0';
+
+        while (processed_bytes < read_bytes) {
+            char *filename = NULL;
+
+            /* the kernel doesn't allow to read incomplete events */
+            is = (struct inotify_event *) (buf + processed_bytes);
+
+            processed_bytes += sizeof(struct inotify_event) + is->len;
+
+            if (is->len == 0) {
+                /* no file name */
+                continue;
+            }
+
+            if (is->wd != dir_fd) {
+                /* wrong descriptor? */
+                continue;
+            }
+
+            filename = path_from_id(is->name);
+
+            if (!filename)
+                continue;
+
+            w = (i_watch_t *) mrp_htbl_lookup(i_watches, filename);
+
+            if (w) {
+                f = fopen(filename, "r");
+
+                if (f) {
+                    fclose(f);
+                    mrp_log_info("Received inotify event for %s, READY", w->path);
+                    w->process_cb(w->path, MRP_PROCESS_STATE_READY, w->userdata);
+                }
+                else {
+                    mrp_log_info("Received inotify event for %s, NOT READY", w->path);
+                    w->process_cb(w->path, MRP_PROCESS_STATE_NOT_READY, w->userdata);
+                }
+            }
+            mrp_free(filename);
+        }
+    }
+}
+
+
+static int send_proc_cmd(enum proc_cn_mcast_op cmd)
+{
+    int data_size = sizeof(enum proc_cn_mcast_op);
+
+    /* connector message size */
+    int cn_size = sizeof(struct cn_msg);
+
+    /* total size of bytes we need */
+    int message_size = NLMSG_SPACE(cn_size + data_size);
+
+    /* aligned size */
+    int payload_size = NLMSG_LENGTH(message_size - sizeof(struct nlmsghdr));
+
+    /* helper pointers */
+    struct nlmsghdr *nl;
+    struct cn_msg *cn;
+
+    /* message data */
+    char buf[message_size];
+
+    if (nl_sock <= 0) {
+        mrp_log_error("invalid netlink socket %d", nl_sock);
+        return -1;
+    }
+
+    /* structs point to the aligned memory */
+    nl = (struct nlmsghdr *) buf;
+    cn = (struct cn_msg *) NLMSG_DATA(nl);
+
+    /* fill the structures */
+    nl->nlmsg_len = payload_size;
+    nl->nlmsg_seq = 0;
+    nl->nlmsg_pid = getpid();
+    nl->nlmsg_type = NLMSG_DONE;
+    nl->nlmsg_flags = 0;
+
+    cn->len = data_size;
+    cn->seq = 0;
+    cn->ack = 0;
+    cn->id.idx = CN_IDX_PROC;
+    cn->id.val = CN_VAL_PROC;
+    cn->flags = 0;
+
+    /* all this was done for this data */
+    memcpy(cn->data, &cmd, data_size);
+
+    return send(nl_sock, buf, message_size, 0);
+}
+
+
+static int subscribe_proc_events()
+{
+    int ret = send_proc_cmd(PROC_CN_MCAST_LISTEN);
+
+    if (ret >= 0)
+        subscribed = TRUE;
+
+    return ret;
+}
+
+
+static int unsubscribe_proc_events()
+{
+    int ret = send_proc_cmd(PROC_CN_MCAST_IGNORE);
+
+    if (ret >= 0)
+        subscribed = FALSE;
+
+    return ret;
+}
+
+
+static void nl_watch(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+        void *user_data)
+{
+    char buf[4096];
+    struct nlmsghdr *nl;
+    struct sockaddr_nl addr;
+    unsigned int sockaddr_len;
+    ssize_t len;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(events);
+    MRP_UNUSED(user_data);
+
+    sockaddr_len = sizeof(struct sockaddr_nl);
+
+    len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *) &addr,
+            &sockaddr_len);
+
+    if (len < 0) {
+        mrp_log_error("failed to read data from socket");
+        return;
+    }
+
+    if (addr.nl_pid != 0) {
+        mrp_log_error("message wasn't from the kernel");
+        return;
+    }
+
+    /* set pointer to the first message in the buffer */
+    nl = (struct nlmsghdr *) buf;
+
+    while (NLMSG_OK(nl, (unsigned int) len)) {
+        struct cn_msg *cn;
+
+        /* we are expecting a non-multipart message -- filter errors and
+         * others away */
+        if (nl->nlmsg_type != NLMSG_DONE) {
+            if (nl->nlmsg_type == NLMSG_ERROR) {
+                /* TODO: error processing, resynchronization */
+            }
+            nl = NLMSG_NEXT(nl, len);
+            continue;
+        }
+
+        cn = (struct cn_msg *) NLMSG_DATA(nl);
+
+        if (cn->id.idx == CN_IDX_PROC && cn->id.val == CN_VAL_PROC) {
+            struct proc_event *ev = (struct proc_event *) cn->data;
+
+            switch (ev->what) {
+                case PROC_EVENT_EXIT:
+                {
+                    mrp_list_hook_t *p, *n;
+                    nl_pid_watch_t *nl_w;
+                    char pid_s[16];
+                    int ret;
+
+                    mrp_log_info("process %d exited",
+                            ev->event_data.exit.process_pid);
+
+                    ret = snprintf(pid_s, sizeof(pid_s), "%u",
+                            (unsigned int) ev->event_data.exit.process_pid);
+
+                    if (ret < 0 || ret >= (int) sizeof(pid_s))
+                        break;
+
+                    /* check the pid */
+                    nl_w = (nl_pid_watch_t *) mrp_htbl_lookup(nl_watches, pid_s);
+
+                    if (!nl_w) {
+                        mrp_log_error("pid %s exited but no-one was following it", pid_s);
+                        break;
+                    }
+
+                    nl_w->busy = TRUE;
+                    mrp_list_foreach(&nl_w->clients, p, n) {
+                        nl_pid_client_t *client;
+
+                        client = mrp_list_entry(p, typeof(*client), hook);
+                        client->cb(nl_w->pid, MRP_PROCESS_STATE_NOT_READY,
+                                client->user_data);
+                    }
+                    if (nl_w->dead)
+                        mrp_free(nl_w);
+                    else
+                        nl_w->busy = FALSE;
+
+                    /* TODO: should we automatically free the wathces? Or let
+                     * client do that to preserver symmetricity? */
+                    break;
+                }
+                default:
+                    /* the filter isn't working for some reason */
+                    mrp_log_error("some other message!\n");
+                    break;
+            }
+        }
+
+        nl = NLMSG_NEXT(nl, len);
+    }
+}
+
+
+static int initialize(mrp_mainloop_t *ml, bool process, bool pid)
+{
+    if (process) {
+
+        if (initialize_dir() < 0)
+            goto error;
+
+        if (i_fd <= 0) {
+            i_fd = inotify_init();
+
+            if (i_fd <= 0)
+                goto error;
+        }
+
+        if (dir_fd <= 0) {
+
+            dir_fd = inotify_add_watch(i_fd, MURPHY_PROCESS_INOTIFY_DIR,
+                IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO | IN_MODIFY);
+
+            if (dir_fd < 0)
+                goto error;
+        }
+
+        if (!i_wd) {
+            i_wd = mrp_add_io_watch(ml, i_fd, MRP_IO_EVENT_IN, process_change, NULL);
+
+            if (!i_wd)
+                goto error;
+        }
+
+        if (!i_watches) {
+            mrp_htbl_config_t watches_conf;
+
+            watches_conf.comp = mrp_string_comp;
+            watches_conf.hash = mrp_string_hash;
+            watches_conf.free = htbl_free_i_watch;
+            watches_conf.nbucket = 0;
+            watches_conf.nentry = 10;
+
+            i_watches = mrp_htbl_create(&watches_conf);
+
+            if (!i_watches)
+                goto error;
+        }
+    }
+
+    if (pid) {
+        if (nl_sock <= 0) {
+            struct sockaddr_nl nl_addr;
+            int nl_options = SOCK_NONBLOCK | SOCK_DGRAM | SOCK_CLOEXEC;
+            struct sock_filter block[] = {
+                BPF_STMT(BPF_RET | BPF_K, 0x0),
+            };
+            struct sock_fprog fp;
+
+            /* socket creation */
+
+            nl_sock = socket(PF_NETLINK, nl_options, NETLINK_CONNECTOR);
+
+            if (nl_sock <= 0)
+                goto error;
+
+            memset(&nl_addr, 0, sizeof(struct sockaddr_nl));
+            memset(&fp, 0, sizeof(struct sock_fprog));
+
+            /* bind the socket to the address */
+
+            nl_addr.nl_pid = getpid();
+            nl_addr.nl_family = AF_NETLINK;
+            nl_addr.nl_groups = CN_IDX_PROC;
+
+            if (bind(nl_sock, (struct sockaddr *) &nl_addr,
+                    sizeof(struct sockaddr_nl)) < 0)
+                goto error;
+
+            fp.filter = block;
+            fp.len = 1;
+
+            /* set socket filter that blocks everything */
+            if (setsockopt(nl_sock, SOL_SOCKET, SO_ATTACH_FILTER, &fp,
+                    sizeof(struct sock_fprog)) < 0) {
+                mrp_log_error("setting blocking socket filter failed: %s",
+                        strerror(errno));
+                goto error;
+            }
+
+            nl_wd = mrp_add_io_watch(ml, nl_sock, MRP_IO_EVENT_IN, nl_watch, NULL);
+        }
+
+        if (!nl_watches) {
+            mrp_htbl_config_t watches_conf;
+
+            watches_conf.comp = mrp_string_comp;
+            watches_conf.hash = mrp_string_hash;
+            watches_conf.free = htbl_free_nl_watch;
+            watches_conf.nbucket = 0;
+            watches_conf.nentry = 10;
+
+            nl_watches = mrp_htbl_create(&watches_conf);
+
+            if (!nl_watches)
+                goto error;
+        }
+    }
+
+    return 0;
+
+error:
+    mrp_log_error("initialization error");
+
+    if (process) {
+
+        if (i_watches) {
+            mrp_htbl_destroy(i_watches, FALSE);
+            i_watches = NULL;
+        }
+
+        if (i_wd) {
+            mrp_del_io_watch(i_wd);
+            i_wd = NULL;
+        }
+
+        if (i_fd && dir_fd) {
+            inotify_rm_watch(i_fd, dir_fd);
+            dir_fd = -1;
+        }
+
+        i_n_process_watches = 0;
+    }
+
+    if (pid) {
+
+        if (nl_sock > 0) {
+            close(nl_sock);
+            nl_sock = -1;
+        }
+
+        nl_n_pid_watches = 0;
+    }
+
+    return -1;
+}
+
+
+static int initialize_process(mrp_mainloop_t *ml)
+{
+    return initialize(ml, TRUE, FALSE);
+}
+
+
+static int initialize_pid(mrp_mainloop_t *ml)
+{
+    return initialize(ml, FALSE, TRUE);
+}
+
+
+int mrp_process_set_state(const char *id, mrp_process_state_t state)
+{
+    char *path = NULL;
+    FILE *f;
+    int ret = -1;
+
+    if (initialize_dir() < 0)
+        goto end;
+
+    path = path_from_id(id);
+
+    if (!path)
+        goto end;
+
+    switch (state) {
+        case MRP_PROCESS_STATE_UNKNOWN:
+        case MRP_PROCESS_STATE_NOT_READY:
+            if (unlink(path) < 0) {
+                if (errno != ENOENT) {
+                    goto end;
+                }
+            }
+            break;
+        case MRP_PROCESS_STATE_READY:
+            f = fopen(path, "w");
+            if (!f)
+                goto end;
+
+            fclose(f);
+            break;
+    }
+
+    ret = 0;
+
+end:
+    mrp_free(path);
+    return ret;
+}
+
+
+static void filter_add_pid(struct sock_filter *p, pid_t pid, int proc_offset)
+{
+    int proc_event_data_offset = proc_offset +
+            offsetof(struct proc_event, event_data);
+    /* event_data is an union, leaving out */
+    int proc_event_data_exit_pid_offset = proc_event_data_offset +
+            offsetof(struct exit_proc_event, process_pid);
+
+    struct sock_filter bpf[] = {
+        /* load the pid value ... */
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, proc_event_data_exit_pid_offset),
+
+        /* ... if it is the pid we're comparing it to ... */
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(pid), 0, 1),
+
+        /* ... return success immediately ... */
+        BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
+
+        /* ... otherwise proceed to the next comparison or exit. */
+    };
+
+    mrp_debug("adding pid %d to filter\n", pid);
+
+    memcpy(p, bpf, 3*sizeof(struct sock_filter));
+}
+
+
+static int filter_update(pid_t pids[], int len_pids)
+{
+    int nl_type_offset = offsetof(struct nlmsghdr, nlmsg_type);
+    int cn_offset = NLMSG_LENGTH(0);
+    int cn_id_offset = cn_offset + offsetof(struct cn_msg, id);
+    int cn_idx_offset = cn_id_offset + offsetof(struct cb_id, idx);
+    int cn_val_offset = cn_id_offset + offsetof(struct cb_id, val);
+    int proc_offset = cn_offset + offsetof(struct cn_msg, data);
+    int proc_what_offset = proc_offset + offsetof(struct proc_event, what);
+
+    struct sock_fprog fp;
+    struct sock_filter *bpf, *iter;
+
+    struct sock_filter bpf_header[] = {
+
+        /* check that the message has only one part or is an error
+         * ( NLMSG_DONE || NLMSG_ERROR) -- return error immediately */
+
+        /* load the message type */
+        BPF_STMT(BPF_LD | BPF_H | BPF_ABS, nl_type_offset),
+
+        /* check the error type */
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(NLMSG_ERROR), 0, 1),
+
+        /* return if error */
+        BPF_STMT(BPF_RET | BPF_K, 0xffffffff),
+
+        /* check the done type */
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htons(NLMSG_DONE), 1, 0),
+
+        /* filter the packet if not NLMSG_DONE */
+        BPF_STMT(BPF_RET | BPF_K, 0x0),
+
+        /* check that the message is from the proc connector
+         * ( CN_IDX_PROC && CN_VAL_PROC ) */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, cn_idx_offset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(CN_IDX_PROC), 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, 0x0),
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, cn_val_offset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(CN_VAL_PROC), 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, 0x0),
+
+        /* check that the message is a PROC_EVENT_EXIT message */
+
+        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, proc_what_offset),
+        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(PROC_EVENT_EXIT), 1, 0),
+        BPF_STMT(BPF_RET | BPF_K, 0x0),
+    };
+
+    struct sock_filter bpf_footer[] = {
+        /* if there was no pid match then filter the packet */
+        BPF_STMT (BPF_RET | BPF_K, 0x0),
+    };
+
+    int len_bpf_header = sizeof(bpf_header);
+    int len_bpf_footer = sizeof(bpf_footer);
+    /* three statements */
+    int len_bpf_pids = sizeof(struct sock_filter) * len_pids * 3;
+    int len = len_bpf_header + len_bpf_pids + len_bpf_footer;
+    int i;
+
+    if (nl_sock <= 0) {
+        mrp_log_error("invalid netlink socket %d", nl_sock);
+        goto error;
+    }
+
+    /* build the filter */
+
+    bpf = (struct sock_filter *) mrp_allocz(len);
+
+    if (!bpf)
+        goto error;
+
+    iter = bpf;
+
+    memcpy(iter, bpf_header, len_bpf_header);
+
+    iter = iter + (len_bpf_header / sizeof(struct sock_filter));
+
+    /* check that the PID is one that we are following */
+    for (i = 0; i < len_pids; i++, iter=iter+3) {
+        filter_add_pid(iter, pids[i], proc_offset);
+    }
+
+    memcpy(iter, bpf_footer, len_bpf_footer);
+
+    memset(&fp, 0, sizeof(struct sock_fprog));
+    fp.filter = bpf;
+    fp.len = len / sizeof(struct sock_filter);
+
+    if (setsockopt(nl_sock, SOL_SOCKET, SO_ATTACH_FILTER, &fp,
+            sizeof(struct sock_fprog)) < 0)
+        mrp_log_error("setting socket filter failed: %s", strerror(errno));
+
+    mrp_free(bpf);
+
+error:
+    return -1;
+}
+
+
+struct key_data_s {
+    int index;
+    pid_t *pids;
+};
+
+
+static int gather_pids_cb(void *key, void *object, void *user_data)
+{
+    struct key_data_s *kd = (struct key_data_s *) user_data;
+    nl_pid_watch_t *w = (nl_pid_watch_t *) object;
+
+    MRP_UNUSED(key);
+
+    kd->pids[kd->index] = w->pid;
+    kd->index++;
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static int pid_filter_update()
+{
+    pid_t pids[nl_n_pid_watches];
+
+    struct key_data_s kd;
+
+    kd.index = 0;
+    kd.pids = pids;
+
+    mrp_htbl_foreach(nl_watches, gather_pids_cb, &kd);
+
+    return filter_update(pids, kd.index);
+}
+
+
+mrp_process_state_t mrp_process_query_state(const char *id)
+{
+    char *path;
+    FILE *f;
+
+    if (initialize_dir() < 0)
+        return MRP_PROCESS_STATE_UNKNOWN;
+
+    path = path_from_id(id);
+
+    if (!path)
+        return MRP_PROCESS_STATE_UNKNOWN;
+
+    f = fopen(path, "r");
+
+    mrp_free(path);
+
+    if (f) {
+        fclose(f);
+        return MRP_PROCESS_STATE_READY;
+    }
+
+    return MRP_PROCESS_STATE_NOT_READY;
+}
+
+
+mrp_process_state_t mrp_pid_query_state(pid_t pid)
+{
+    char path[64];
+    struct stat s;
+    mrp_process_state_t state = MRP_PROCESS_STATE_UNKNOWN;
+    int ret;
+
+    ret = snprintf(path, sizeof(path), "/proc/%u", (unsigned int) pid);
+
+    if (ret < 0 || ret >= (int) sizeof(path))
+        goto end;
+
+    ret = stat(path, &s);
+
+    if (ret < 0 && (errno == ENOENT || errno == ENOTDIR)) {
+        state = MRP_PROCESS_STATE_NOT_READY;
+    }
+    else if (ret == 0 && S_ISDIR(s.st_mode)) {
+        state = MRP_PROCESS_STATE_READY;
+    }
+
+end:
+    return state;
+}
+
+
+int mrp_process_set_watch(const char *id, mrp_mainloop_t *ml,
+        mrp_process_watch_handler_t cb, void *userdata)
+{
+    i_watch_t *w = NULL;
+
+    if (initialize_process(ml) < 0)
+        goto error;
+
+    w = (i_watch_t *) mrp_allocz(sizeof(i_watch_t));
+
+    if (!w)
+        goto error;
+
+    w->inotify_cb = i_wd;
+    w->userdata = userdata;
+    w->process_cb = cb;
+
+    w->path = mrp_strdup(id);
+    if (!w->path)
+        goto error;
+
+    w->filename = path_from_id(id);
+    if (!w->filename)
+        goto error;
+
+    if (mrp_htbl_insert(i_watches, w->filename, w) < 0)
+        goto error;
+
+    i_n_process_watches++;
+
+    return 0;
+
+error:
+    if (w) {
+        mrp_free(w->path);
+        mrp_free(w->filename);
+        mrp_free(w);
+    }
+
+    return -1;
+}
+
+
+mrp_pid_watch_t *mrp_pid_set_watch(pid_t pid, mrp_mainloop_t *ml,
+        mrp_pid_watch_handler_t cb, void *userdata)
+{
+    nl_pid_watch_t *nl_w = NULL;
+    nl_pid_client_t *client = NULL;
+    char pid_s[16];
+    bool already_inserted = TRUE;
+    int ret;
+
+    if (initialize_pid(ml) < 0)
+        goto error;
+
+    ret = snprintf(pid_s, sizeof(pid_s), "%u", (unsigned int) pid);
+
+    if (ret < 0 || ret >= (int) sizeof(pid_s))
+        goto error;
+
+    nl_w = (nl_pid_watch_t *) mrp_htbl_lookup(nl_watches, pid_s);
+
+    if (!nl_w) {
+
+        nl_w = (nl_pid_watch_t *) mrp_allocz(sizeof(nl_pid_watch_t));
+
+        if (!nl_w)
+            goto error;
+
+        mrp_list_init(&nl_w->clients);
+        nl_w->pid = pid;
+        memcpy(nl_w->pid_s, pid_s, sizeof(nl_w->pid_s));
+
+        already_inserted = FALSE;
+    }
+
+    client = (nl_pid_client_t *) mrp_allocz(sizeof(nl_pid_client_t));
+
+    if (!client) {
+        if (!already_inserted)
+            mrp_free(nl_w);
+        goto error;
+    }
+
+    client->cb = cb;
+    client->user_data = userdata;
+    client->w = (mrp_pid_watch_t *) mrp_allocz(sizeof(mrp_pid_watch_t));
+
+    if (!client->w) {
+        mrp_free(nl_w);
+        goto error;
+    }
+
+    client->w->pid = pid;
+
+    mrp_list_init(&client->hook);
+    mrp_list_append(&nl_w->clients, &client->hook);
+    nl_w->n_clients++;
+
+    if (!already_inserted) {
+        if (mrp_htbl_insert(nl_watches, nl_w->pid_s, nl_w) < 0) {
+            mrp_list_delete(&client->hook);
+            mrp_free(nl_w);
+            goto error;
+        }
+
+        nl_n_pid_watches++;
+    }
+
+    pid_filter_update();
+
+    if (!subscribed)
+        subscribe_proc_events();
+
+    /* check that the pid is still there -- return error if not */
+
+    if (mrp_pid_query_state(pid) != MRP_PROCESS_STATE_READY)
+        goto error_process;
+
+    return client->w;
+
+error_process:
+    mrp_pid_remove_watch(client->w);
+    client = NULL;
+
+error:
+    if (client) {
+        mrp_free(client);
+        mrp_free(client->w);
+    }
+
+    return NULL;
+}
+
+
+static void update_map()
+{
+    if (i_n_process_watches <= 0) {
+        if (i_fd > 0 && dir_fd > 0) {
+           inotify_rm_watch(i_fd, dir_fd);
+           dir_fd = -1;
+        }
+        i_n_process_watches = 0;
+    }
+
+    if ((i_n_process_watches) == 0) {
+        mrp_htbl_destroy(i_watches, TRUE);
+        i_watches = NULL;
+    }
+}
+
+
+int mrp_process_remove_watch(const char *id)
+{
+    i_watch_t *w;
+    char *filename;
+
+    if (!i_watches)
+        return -1;
+
+    filename = path_from_id(id);
+
+    w = (i_watch_t *) mrp_htbl_lookup(i_watches, (void *)filename);
+
+    if (!w) {
+        mrp_free(filename);
+        return -1;
+    }
+
+    mrp_htbl_remove(i_watches, (void *) filename, TRUE);
+    i_n_process_watches--;
+
+    update_map();
+
+    mrp_free(filename);
+    return 0;
+}
+
+
+int mrp_pid_remove_watch(mrp_pid_watch_t *w)
+{
+    nl_pid_watch_t *nl_w = NULL;
+    nl_pid_client_t *client = NULL;
+    char pid_s[16];
+    mrp_list_hook_t *p, *n;
+    bool found = FALSE;
+    int ret;
+
+    if (!w)
+        goto error;
+
+    ret = snprintf(pid_s, sizeof(pid_s), "%u", (unsigned int) w->pid);
+
+    if (ret < 0 || ret >= (int) sizeof(pid_s))
+        goto error;
+
+    nl_w = (nl_pid_watch_t *) mrp_htbl_lookup(nl_watches, pid_s);
+
+    if (!nl_w) {
+        mrp_log_error("no corresponding pid watch found");
+        goto error;
+    }
+
+    mrp_list_foreach(&nl_w->clients, p, n) {
+        client = mrp_list_entry(p, typeof(*client), hook);
+        if (client->w == w) {
+            found = TRUE;
+            break;
+        }
+    }
+
+    if (!found) {
+        mrp_log_error("not registered to watch the pid");
+        goto error;
+    }
+
+    mrp_list_delete(&client->hook);
+    mrp_free(client);
+    nl_w->n_clients--;
+
+    if (nl_w->n_clients == 0) {
+        /* no-one is interested in this pid anymore */
+        mrp_htbl_remove(nl_watches, pid_s, TRUE);
+        nl_n_pid_watches--;
+
+        pid_filter_update();
+
+        if (nl_n_pid_watches == 0) {
+            /* no-one is following pids anymore */
+            if (subscribed)
+                unsubscribe_proc_events();
+        }
+    }
+
+    mrp_free(w);
+    return 0;
+
+error:
+    mrp_free(w);
+    return -1;
+}
diff --git a/src/common/process.h b/src/common/process.h
new file mode 100644 (file)
index 0000000..956a367
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_PROCESS_H__
+#define __MURPHY_PROCESS_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mainloop.h>
+
+MRP_CDECL_BEGIN
+
+/* functions for the murphy family of processes */
+
+typedef enum {
+    MRP_PROCESS_STATE_UNKNOWN,
+    MRP_PROCESS_STATE_READY,
+    MRP_PROCESS_STATE_NOT_READY
+} mrp_process_state_t;
+
+typedef void (*mrp_process_watch_handler_t)(const char *,
+        mrp_process_state_t, void *);
+
+int mrp_process_set_state(const char *id, mrp_process_state_t state);
+
+mrp_process_state_t mrp_process_query_state(const char *id);
+
+int mrp_process_set_watch(const char *id, mrp_mainloop_t *ml,
+        mrp_process_watch_handler_t cb, void *userdata);
+
+int mrp_process_remove_watch(const char *id);
+
+/* functions to track external processes by pid */
+
+typedef struct mrp_pid_watch_s mrp_pid_watch_t;
+
+typedef void (*mrp_pid_watch_handler_t)(pid_t,
+        mrp_process_state_t, void *);
+
+mrp_process_state_t mrp_pid_query_state(pid_t pid);
+
+mrp_pid_watch_t *mrp_pid_set_watch(pid_t pid, mrp_mainloop_t *ml,
+        mrp_pid_watch_handler_t cb, void *userdata);
+
+int mrp_pid_remove_watch(mrp_pid_watch_t *w);
+
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_PROCESS_H__ */
diff --git a/src/common/pulse-glue.c b/src/common/pulse-glue.c
new file mode 100644 (file)
index 0000000..98dbf24
--- /dev/null
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include <pulse/mainloop-api.h>
+#include <pulse/timeval.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+
+#include <murphy/common/pulse-glue.h>
+
+
+typedef struct {
+    pa_mainloop_api *pa;
+} pulse_glue_t;
+
+
+typedef struct {
+    pa_io_event  *pa_io;
+    void        (*cb)(void *glue_data,
+                      void *id, int fd, mrp_io_event_t events,
+                      void *user_data);
+    void         *user_data;
+    void         *glue_data;
+} io_t;
+
+
+typedef struct {
+    pa_time_event  *pa_t;
+    void          (*cb)(void *glue_data, void *id, void *user_data);
+    void           *user_data;
+    void           *glue_data;
+} tmr_t;
+
+
+typedef struct {
+    pa_defer_event  *pa_d;
+    void           (*cb)(void *glue_data, void *id, void *user_data);
+    void            *user_data;
+    void            *glue_data;
+} dfr_t;
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data);
+static void  del_io(void *glue_data, void *id);
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+static void  del_timer(void *glue_data, void *id);
+static void  mod_timer(void *glue_data, void *id, unsigned int msecs);
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data);
+static void  del_defer(void *glue_data, void *id);
+static void  mod_defer(void *glue_data, void *id, int enabled);
+
+
+static void io_cb(pa_mainloop_api *pa, pa_io_event *e, int fd,
+                  pa_io_event_flags_t mask, void *user_data)
+{
+    io_t           *io     = (io_t *)user_data;
+    mrp_io_event_t  events = MRP_IO_EVENT_NONE;
+
+    MRP_UNUSED(pa);
+    MRP_UNUSED(e);
+
+    if (mask & PA_IO_EVENT_INPUT)  events |= MRP_IO_EVENT_IN;
+    if (mask & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT;
+    if (mask & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP;
+    if (mask & PA_IO_EVENT_ERROR)  events |= MRP_IO_EVENT_ERR;
+
+    io->cb(io->glue_data, io, fd, events, io->user_data);
+}
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data)
+{
+    pulse_glue_t        *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api     *pa   = glue->pa;
+    pa_io_event_flags_t  mask = PA_IO_EVENT_NULL;
+    io_t                *io;
+
+    io = mrp_allocz(sizeof(*io));
+
+    if (io != NULL) {
+        if (events & MRP_IO_EVENT_IN)  mask |= PA_IO_EVENT_INPUT;
+        if (events & MRP_IO_EVENT_OUT) mask |= PA_IO_EVENT_OUTPUT;
+        if (events & MRP_IO_EVENT_HUP) mask |= PA_IO_EVENT_HANGUP;
+        if (events & MRP_IO_EVENT_ERR) mask |= PA_IO_EVENT_ERROR;
+
+        io->pa_io = pa->io_new(pa, fd, mask, io_cb, io);
+
+        if (io->pa_io != NULL) {
+            io->cb        = cb;
+            io->user_data = user_data;
+            io->glue_data = glue_data;
+
+            return io;
+        }
+        else
+            mrp_free(io);
+    }
+
+    return NULL;
+}
+
+
+static void del_io(void *glue_data, void *id)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    io_t            *io   = (io_t *)id;
+
+    pa->io_free(io->pa_io);
+    mrp_free(io);
+}
+
+
+static void timer_cb(pa_mainloop_api *pa, pa_time_event *e,
+                     const struct timeval *tv, void *user_data)
+{
+    tmr_t *t = (tmr_t *)user_data;
+
+    MRP_UNUSED(pa);
+    MRP_UNUSED(e);
+    MRP_UNUSED(tv);
+
+    t->cb(t->glue_data, t, t->user_data);
+}
+
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    struct timeval   tv;
+    tmr_t           *t;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+        pa_gettimeofday(&tv);
+
+        tv.tv_sec  += msecs / 1000;
+        tv.tv_usec += 1000 * (msecs % 1000);
+
+        t->pa_t = pa->time_new(pa, &tv, timer_cb, t);
+
+        if (t->pa_t != NULL) {
+            t->cb        = cb;
+            t->user_data = user_data;
+            t->glue_data = glue_data;
+
+            return t;
+        }
+        else
+            mrp_free(t);
+    }
+
+    return NULL;
+}
+
+
+static void del_timer(void *glue_data, void *id)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    tmr_t           *t    = (tmr_t *)id;
+
+    pa->time_free(t->pa_t);
+    mrp_free(t);
+}
+
+
+static void mod_timer(void *glue_data, void *id, unsigned int msecs)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    tmr_t           *t    = (tmr_t *)id;
+    struct timeval   tv;
+
+    if (t != NULL) {
+        pa_gettimeofday(&tv);
+
+        tv.tv_sec  += msecs / 1000;
+        tv.tv_usec += 1000 * (msecs % 1000);
+
+        pa->time_restart(t->pa_t, &tv);
+    }
+}
+
+
+static void defer_cb(pa_mainloop_api *pa, pa_defer_event *e, void *user_data)
+{
+    dfr_t *d = (dfr_t *)user_data;
+
+    MRP_UNUSED(pa);
+    MRP_UNUSED(e);
+
+    d->cb(d->glue_data, d, d->user_data);
+}
+
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    dfr_t           *d;
+
+    d = mrp_allocz(sizeof(*d));
+
+    if (d != NULL) {
+        d->pa_d = pa->defer_new(pa, defer_cb, d);
+
+        if (d->pa_d != NULL) {
+            d->cb        = cb;
+            d->user_data = user_data;
+            d->glue_data = glue_data;
+
+            return d;
+        }
+        else
+            mrp_free(d);
+    }
+
+    return NULL;
+}
+
+
+static void del_defer(void *glue_data, void *id)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    dfr_t           *d    = (dfr_t *)id;
+
+    pa->defer_free(d->pa_d);
+    mrp_free(d);
+}
+
+
+static void mod_defer(void *glue_data, void *id, int enabled)
+{
+    pulse_glue_t    *glue = (pulse_glue_t *)glue_data;
+    pa_mainloop_api *pa   = glue->pa;
+    dfr_t           *d    = (dfr_t *)id;
+
+    pa->defer_enable(d->pa_d, !!enabled);
+}
+
+
+static void unregister(void *data)
+{
+    pulse_glue_t *glue = (pulse_glue_t *)data;
+
+    mrp_free(glue);
+}
+
+
+static mrp_superloop_ops_t pa_ops = {
+    .add_io     = add_io,
+    .del_io     = del_io,
+    .add_timer  = add_timer,
+    .del_timer  = del_timer,
+    .mod_timer  = mod_timer,
+    .add_defer  = add_defer,
+    .del_defer  = del_defer,
+    .mod_defer  = mod_defer,
+    .unregister = unregister,
+};
+
+
+int mrp_mainloop_register_with_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa)
+{
+    pulse_glue_t *glue;
+
+    glue = mrp_allocz(sizeof(*glue));
+
+    if (glue != NULL) {
+        glue->pa = pa;
+
+        if (mrp_set_superloop(ml, &pa_ops, glue))
+            return TRUE;
+        else
+            mrp_free(glue);
+    }
+
+    return FALSE;
+}
+
+
+int mrp_mainloop_unregister_from_pulse(mrp_mainloop_t *ml)
+{
+    return mrp_mainloop_unregister(ml);
+}
+
+
+
+static mrp_mainloop_t *pulse_ml;
+
+mrp_mainloop_t *mrp_mainloop_pulse_get(pa_mainloop_api *pa)
+{
+    if (pulse_ml == NULL) {
+        pulse_ml = mrp_mainloop_create();
+
+        if (pulse_ml != NULL) {
+            if (!mrp_mainloop_register_with_pulse(pulse_ml, pa)) {
+                mrp_mainloop_destroy(pulse_ml);
+                pulse_ml = NULL;
+            }
+        }
+    }
+
+    return pulse_ml;
+}
diff --git a/src/common/pulse-glue.h b/src/common/pulse-glue.h
new file mode 100644 (file)
index 0000000..24fa3e8
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_PULSE_H__
+#define __MURPHY_PULSE_H__
+
+#include <murphy/common/mainloop.h>
+#include <pulse/mainloop.h>
+
+/** Register the given murphy mainloop with the given pulse mainloop. */
+int mrp_mainloop_register_with_pulse(mrp_mainloop_t *ml, pa_mainloop_api *pa);
+
+/** Unrgister the given murphy mainloop from the given pulse mainloop. */
+int mrp_mainloop_unregister_from_pulse(mrp_mainloop_t *ml);
+
+/** Create a murphy mainloop and set it up with the given pulse mainloop. */
+mrp_mainloop_t *mrp_mainloop_pulse_get(pa_mainloop_api *pa);
+
+#endif /* __MURPHY_PULSE_H__ */
diff --git a/src/common/pulse-subloop.c b/src/common/pulse-subloop.c
new file mode 100644 (file)
index 0000000..7e08b6c
--- /dev/null
@@ -0,0 +1,572 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <sys/time.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/pulse-subloop.h>
+
+
+struct pa_murphy_mainloop {
+    mrp_mainloop_t  *ml;
+    pa_mainloop_api  api;
+    mrp_list_hook_t  io_events;
+    mrp_list_hook_t  time_events;
+    mrp_list_hook_t  defer_events;
+    mrp_list_hook_t  io_dead;
+    mrp_list_hook_t  time_dead;
+    mrp_list_hook_t  defer_dead;
+};
+
+
+struct pa_io_event {
+    pa_murphy_mainloop       *m;
+    int                       fd;
+    mrp_io_watch_t           *w;
+    pa_io_event_cb_t          cb;
+    pa_io_event_destroy_cb_t  destroy;
+    void                     *userdata;
+    mrp_list_hook_t           hook;
+    int                       busy : 1;
+    int                       dead : 1;
+};
+
+
+struct pa_time_event {
+    pa_murphy_mainloop         *m;
+    mrp_timer_t                *t;
+    struct timeval              tv;
+    pa_time_event_cb_t          cb;
+    pa_time_event_destroy_cb_t  destroy;
+    void                       *userdata;
+    mrp_list_hook_t             hook;
+    int                         busy : 1;
+    int                         dead : 1;
+};
+
+
+struct pa_defer_event {
+    pa_murphy_mainloop          *m;
+    mrp_deferred_t              *d;
+    pa_defer_event_cb_t          cb;
+    pa_defer_event_destroy_cb_t  destroy;
+    void                        *userdata;
+    mrp_list_hook_t              hook;
+    int                          busy : 1;
+    int                          dead : 1;
+};
+
+
+pa_murphy_mainloop *pa_murphy_mainloop_new(mrp_mainloop_t *ml)
+{
+    pa_murphy_mainloop *m;
+
+    if (ml == NULL)
+        return NULL;
+
+    m = mrp_allocz(sizeof(*m));
+
+    if (m == NULL)
+        return NULL;
+
+    m->ml = ml;
+    mrp_list_init(&m->io_events);
+    mrp_list_init(&m->time_events);
+    mrp_list_init(&m->defer_events);
+    mrp_list_init(&m->io_dead);
+    mrp_list_init(&m->time_dead);
+    mrp_list_init(&m->defer_dead);
+
+    return m;
+}
+
+
+static void cleanup_io_events(pa_murphy_mainloop *m)
+{
+    mrp_list_hook_t *p, *n;
+    pa_io_event     *io;
+
+    mrp_list_foreach(&m->io_events, p, n) {
+        io = mrp_list_entry(p, typeof(*io), hook);
+
+        mrp_list_delete(&io->hook);
+        mrp_del_io_watch(io->w);
+        io->w = NULL;
+
+        if (io->destroy != NULL) {
+            io->dead = true;
+            io->destroy(&io->m->api, io, io->userdata);
+        }
+
+        mrp_free(io);
+    }
+
+    mrp_list_foreach(&m->io_dead, p, n) {
+        io = mrp_list_entry(p, typeof(*io), hook);
+        mrp_list_delete(&io->hook);
+        mrp_free(io);
+    }
+}
+
+
+static void cleanup_time_events(pa_murphy_mainloop *m)
+{
+    mrp_list_hook_t *p, *n;
+    pa_time_event   *t;
+
+    mrp_list_foreach(&m->time_events, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        mrp_list_delete(&t->hook);
+        mrp_del_timer(t->t);
+        t->t = NULL;
+
+        if (t->destroy != NULL) {
+            t->dead = true;
+            t->destroy(&t->m->api, t, t->userdata);
+        }
+
+        mrp_free(t);
+    }
+
+    mrp_list_foreach(&m->time_dead, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+        mrp_list_delete(&t->hook);
+        mrp_free(t);
+    }
+}
+
+
+static void cleanup_defer_events(pa_murphy_mainloop *m)
+{
+    mrp_list_hook_t *p, *n;
+    pa_defer_event  *d;
+
+    mrp_list_foreach(&m->defer_events, p, n) {
+        d = mrp_list_entry(p, typeof(*d), hook);
+
+        mrp_list_delete(&d->hook);
+        mrp_del_deferred(d->d);
+        d->d = NULL;
+
+        if (d->destroy != NULL) {
+            d->dead = true;
+            d->destroy(&d->m->api, d, d->userdata);
+        }
+
+        mrp_free(d);
+    }
+
+    mrp_list_foreach(&m->defer_dead, p, n) {
+        d = mrp_list_entry(p, typeof(*d), hook);
+        mrp_list_delete(&d->hook);
+        mrp_free(d);
+    }
+}
+
+
+void pa_murphy_mainloop_free(pa_murphy_mainloop *m)
+{
+    if (m == NULL)
+        return;
+
+    cleanup_io_events(m);
+    cleanup_time_events(m);
+    cleanup_defer_events(m);
+}
+
+
+static void io_event_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                        void *userdata)
+{
+    pa_io_event         *io    = (pa_io_event *)userdata;
+    pa_io_event_flags_t  flags = 0;
+
+    MRP_UNUSED(w);
+
+    mrp_debug("PA I/O event 0x%x for watch %p (fd %d)", events, io, fd);
+
+    if (events & MRP_IO_EVENT_IN)  flags |= PA_IO_EVENT_INPUT;
+    if (events & MRP_IO_EVENT_OUT) flags |= PA_IO_EVENT_OUTPUT;
+    if (events & MRP_IO_EVENT_HUP) flags |= PA_IO_EVENT_HANGUP;
+    if (events & MRP_IO_EVENT_ERR) flags |= PA_IO_EVENT_ERROR;
+
+    io->busy = true;
+    io->cb(&io->m->api, io, fd, flags, io->userdata);
+    io->busy = false;
+
+    if (io->dead) {
+        mrp_list_delete(&io->hook);
+        mrp_free(io);
+    }
+}
+
+
+static pa_io_event *io_new(pa_mainloop_api *api, int fd, pa_io_event_flags_t e,
+                           pa_io_event_cb_t cb, void *userdata)
+{
+    pa_murphy_mainloop *m      = (pa_murphy_mainloop *)api->userdata;
+    mrp_io_event_t      events = 0;
+    pa_io_event        *io;
+
+    mrp_debug("PA create I/O watch for fd %d, events 0x%x", fd, e);
+
+    io = mrp_allocz(sizeof(*io));
+
+    if (io == NULL)
+        return NULL;
+
+    mrp_list_init(&io->hook);
+
+    if (e & PA_IO_EVENT_INPUT)  events |= MRP_IO_EVENT_IN;
+    if (e & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT;
+    if (e & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP; /* RDHUP ? */
+    if (e & PA_IO_EVENT_ERROR)  events |= MRP_IO_EVENT_ERR;
+
+    io->m        = m;
+    io->fd       = fd;
+    io->cb       = cb;
+    io->userdata = userdata;
+    io->w        = mrp_add_io_watch(m->ml, fd, events, io_event_cb, io);
+
+    if (io->w != NULL)
+        mrp_list_append(&m->io_events, &io->hook);
+    else {
+        mrp_free(io);
+        io = NULL;
+    }
+
+    return io;
+}
+
+
+static void io_enable(pa_io_event *io, pa_io_event_flags_t e)
+{
+    pa_murphy_mainloop *m      = io->m;
+    mrp_io_event_t      events = 0;
+
+    mrp_debug("PA enable events 0x%x for I/O watch %p (fd %d)", e, io, io->fd);
+
+    mrp_del_io_watch(io->w);
+    io->w = NULL;
+
+    if (e & PA_IO_EVENT_INPUT)  events |= MRP_IO_EVENT_IN;
+    if (e & PA_IO_EVENT_OUTPUT) events |= MRP_IO_EVENT_OUT;
+    if (e & PA_IO_EVENT_HANGUP) events |= MRP_IO_EVENT_HUP; /* RDHUP ? */
+    if (e & PA_IO_EVENT_ERROR)  events |= MRP_IO_EVENT_ERR;
+
+    io->w = mrp_add_io_watch(m->ml, io->fd, events, io_event_cb, io);
+}
+
+
+static void io_free(pa_io_event *io)
+{
+    pa_murphy_mainloop *m = io->m;
+
+    mrp_debug("PA free I/O watch %p (fd %d)", io, io->fd);
+
+    mrp_list_delete(&io->hook);
+    mrp_del_io_watch(io->w);
+    io->w = NULL;
+
+    io->dead = true;
+
+    if (!io->busy && !io->dead) {
+        io->busy = true;
+        if (io->destroy != NULL)
+            io->destroy(&io->m->api, io, io->userdata);
+        mrp_free(io);
+    }
+    else
+        mrp_list_append(&m->io_dead, &io->hook);
+}
+
+
+static void io_set_destroy(pa_io_event *io, pa_io_event_destroy_cb_t cb)
+{
+    mrp_debug("PA set I/O watch destroy callback for %p (fd %d) to %p",
+              io, io->fd, cb);
+
+    io->destroy = cb;
+}
+
+
+
+
+static void time_event_cb(mrp_timer_t *tmr, void *userdata)
+{
+    pa_time_event *t = (pa_time_event *)userdata;
+
+    MRP_UNUSED(tmr);
+
+    mrp_debug("PA time event for timer %p", t);
+
+    mrp_del_timer(t->t);
+    t->t = NULL;
+
+    t->busy = true;
+    t->cb(&t->m->api, t, &t->tv, t->userdata);
+    t->busy = false;
+
+    if (t->dead) {
+        mrp_del_timer(t->t);
+        mrp_list_delete(&t->hook);
+        mrp_free(t);
+    }
+}
+
+
+static unsigned int timeval_diff(const struct timeval *from,
+                                 const struct timeval *to)
+{
+    int msecs, musecs, diff;
+
+    msecs  = (to->tv_sec - from->tv_sec) * 1000;
+    musecs = ((int)to->tv_usec - (int)from->tv_usec) / 1000;
+
+    diff = msecs + musecs;
+
+    if (diff >= 0)
+        return (unsigned int)diff;
+    else
+        return 0;
+}
+
+
+static pa_time_event *time_new(pa_mainloop_api *api, const struct timeval *tv,
+                               pa_time_event_cb_t cb, void *userdata)
+{
+    pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata;
+    pa_time_event      *t;
+    struct timeval      now;
+
+    gettimeofday(&now, NULL);
+
+    mrp_debug("PA create timer for %u msecs", timeval_diff(&now, tv));
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t == NULL)
+        return NULL;
+
+    mrp_list_init(&t->hook);
+
+    t->m        = m;
+    t->cb       = cb;
+    t->userdata = userdata;
+    t->t        = mrp_add_timer(m->ml, timeval_diff(&now, tv), time_event_cb, t);
+
+    if (t->t != NULL)
+        mrp_list_append(&m->time_events, &t->hook);
+    else {
+        mrp_free(t);
+        t = NULL;
+    }
+
+    return t;
+}
+
+
+static void time_restart(pa_time_event *t, const struct timeval *tv)
+{
+    pa_murphy_mainloop *m = t->m;
+    struct timeval      now;
+
+    gettimeofday(&now, NULL);
+
+    mrp_debug("PA restart timer %p with %u msecs", t, timeval_diff(&now, tv));
+
+    mrp_del_timer(t->t);
+    t->t = NULL;
+
+    t->t = mrp_add_timer(m->ml, timeval_diff(&now, tv), time_event_cb, t);
+}
+
+
+static void time_free(pa_time_event *t)
+{
+    pa_murphy_mainloop *m = t->m;
+
+    mrp_debug("PA free timer %p",  t);
+
+    mrp_list_delete(&t->hook);
+    mrp_del_timer(t->t);
+    t->t = NULL;
+
+    t->dead = true;
+
+    if (!t->busy && !t->dead) {
+        t->busy = true;
+        if (t->destroy != NULL)
+            t->destroy(&t->m->api, t, t->userdata);
+        mrp_free(t);
+    }
+    else
+        mrp_list_append(&m->time_dead, &t->hook);
+}
+
+
+static void time_set_destroy(pa_time_event *t, pa_time_event_destroy_cb_t cb)
+{
+    mrp_debug("PA set timer destroy callback for %p to %p", t, cb);
+
+    t->destroy = cb;
+}
+
+
+
+
+static void defer_event_cb(mrp_deferred_t *def, void *userdata)
+{
+    pa_defer_event *d = (pa_defer_event *)userdata;
+
+    MRP_UNUSED(def);
+
+    mrp_debug("PA defer event for %p", d);
+
+    d->busy = true;
+    d->cb(&d->m->api, d, d->userdata);
+    d->busy = false;
+
+    if (d->dead) {
+        mrp_del_deferred(d->d);
+        mrp_list_delete(&d->hook);
+        mrp_free(d);
+    }
+}
+
+
+static pa_defer_event *defer_new(pa_mainloop_api *api, pa_defer_event_cb_t cb,
+                                 void *userdata)
+{
+    pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata;
+    pa_defer_event     *d;
+
+    mrp_debug("PA create defer event");
+
+    d = mrp_allocz(sizeof(*d));
+
+    if (d == NULL)
+        return NULL;
+
+    mrp_list_init(&d->hook);
+
+    d->m        = m;
+    d->cb       = cb;
+    d->userdata = userdata;
+    d->d        = mrp_add_deferred(m->ml, defer_event_cb, d);
+
+    if (d->d != NULL)
+        mrp_list_append(&m->defer_events, &d->hook);
+    else {
+        mrp_free(d);
+        d = NULL;
+    }
+
+    return d;
+}
+
+
+static void defer_enable(pa_defer_event *d, int enable)
+{
+    mrp_debug("PA %s defer event %p", enable ? "enable" : "disable", d);
+
+    if (enable)
+        mrp_enable_deferred(d->d);
+    else
+        mrp_disable_deferred(d->d);
+}
+
+
+static void defer_free(pa_defer_event *d)
+{
+    pa_murphy_mainloop *m = d->m;
+
+    mrp_debug("PA free defer event %p", d);
+
+    mrp_list_delete(&d->hook);
+    mrp_del_deferred(d->d);
+    d->d = NULL;
+
+    d->dead = true;
+
+    if (!d->busy && !d->dead) {
+        d->busy = true;
+        if (d->destroy != NULL)
+            d->destroy(&d->m->api, d, d->userdata);
+        mrp_free(d);
+    }
+    else
+        mrp_list_append(&m->defer_dead, &d->hook);
+}
+
+
+static void defer_set_destroy(pa_defer_event *d, pa_defer_event_destroy_cb_t cb)
+{
+    mrp_debug("PA set defer event destroy callback for %p to %p", d, cb);
+
+    d->destroy = cb;
+}
+
+
+static void quit(pa_mainloop_api *api, int retval)
+{
+    pa_murphy_mainloop *m = (pa_murphy_mainloop *)api->userdata;
+
+    mrp_mainloop_quit(m->ml, retval);
+}
+
+
+
+pa_mainloop_api *pa_murphy_mainloop_get_api(pa_murphy_mainloop *m)
+{
+    pa_mainloop_api api = {
+        .userdata          = m,
+        .io_new            = io_new,
+        .io_enable         = io_enable,
+        .io_free           = io_free,
+        .io_set_destroy    = io_set_destroy,
+        .time_new          = time_new,
+        .time_restart      = time_restart,
+        .time_free         = time_free,
+        .time_set_destroy  = time_set_destroy,
+        .defer_new         = defer_new,
+        .defer_enable      = defer_enable,
+        .defer_free        = defer_free,
+        .defer_set_destroy = defer_set_destroy,
+        .quit              = quit
+    };
+
+    m->api = api;
+
+    return &m->api;
+}
diff --git a/src/common/pulse-subloop.h b/src/common/pulse-subloop.h
new file mode 100644 (file)
index 0000000..f2fbe17
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_PULSE_SUBLOOP_H__
+#define __MURPHY_PULSE_SUBLOOP_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mainloop.h>
+
+#include <pulse/mainloop-api.h>
+
+MRP_CDECL_BEGIN
+
+/** An opaque Murphy main loop object. */
+typedef struct pa_murphy_mainloop pa_murphy_mainloop;
+
+/** Create a new Murphy PA main loop object for the specified main loop. */
+pa_murphy_mainloop *pa_murphy_mainloop_new(mrp_mainloop_t *ml);
+
+/** Free the Murphy PA main loop object. */
+void pa_murphy_mainloop_free(pa_murphy_mainloop *m);
+
+/** Return the abstract main loop API for the PA Murphy main loop object. */
+pa_mainloop_api *pa_murphy_mainloop_get_api(pa_murphy_mainloop *m);
+
+MRP_CDECL_END
+
+#endif /* __PULSE_SUBLOOP_H__ */
diff --git a/src/common/qt-glue-priv.h b/src/common/qt-glue-priv.h
new file mode 100644 (file)
index 0000000..cb31531
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef __MURPHY_QT_GLUE_PRIV_H_
+#define __MURPHY_QT_GLUE_PRIV_H_
+
+#include <murphy/config.h>
+
+#ifdef QT_ENABLED
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <QSocketNotifier>
+#include <QTimer>
+#include <murphy/common/mainloop.h>
+
+class QtGlue : public QObject
+{
+Q_OBJECT
+
+public:
+   QtGlue(QObject *parent = NULL);
+};
+
+class QtIO : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum Event {
+        Read      = 0x01,
+        Write     = 0x02,
+        Exception = 0x04
+    };
+    Q_DECLARE_FLAGS(EventMask, Event)
+
+    QtIO (int fd, EventMask events, QObject *parent = NULL);
+    ~QtIO();
+
+public Q_SLOTS:
+    void readyRead (int fd);
+    void readyWrite (int fd);
+    void exception (int fd);
+
+private:
+    EventMask        m_events;
+    QSocketNotifier *m_fdIn;
+    QSocketNotifier *m_fdOut;
+    QSocketNotifier *m_fdExcep;
+
+public:
+    void (*cb)(void *glue_data,
+               void *id, int fd, mrp_io_event_t events,
+               void *user_data);
+    void *user_data;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS (QtIO::EventMask)
+
+class QtTimer : public QObject
+{
+Q_OBJECT
+
+public:
+    QtTimer (int msecs, QObject *parent = NULL);
+    ~QtTimer ();
+
+    void setInterval (int msecs);
+    void start ();
+    void stop ();
+    void enable ();
+    void disable();
+
+private Q_SLOTS:
+    void timedout();
+
+private:
+   QTimer *m_timer;
+   int     m_interval;
+   bool    m_disabled;
+
+public:
+    void (*cb)(void *glue_data, void *id, void *user_data);
+    void *user_data;
+};
+
+#endif
+
+#endif /* __MURPHY_QT_GLUE_PRIV_H_ */
diff --git a/src/common/qt-glue.cpp b/src/common/qt-glue.cpp
new file mode 100644 (file)
index 0000000..8fbf636
--- /dev/null
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "qt-glue-priv.h"
+#include "qt-glue-priv.moc.h"
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <QObject>
+#include <QSocketNotifier>
+#include <QTimer>
+
+#include <murphy/config.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/qt-glue.h>
+
+static mrp_mainloop_t *qt_ml;
+
+
+/*
+ * QtGlue
+ */
+
+QtGlue::QtGlue(QObject *parent)
+    : QObject(parent)
+{
+}
+
+
+/*
+ * QtIO
+ */
+
+QtIO::QtIO (int fd, EventMask events, QObject *parent)
+    : QObject (parent)
+    , m_events(events), m_fdIn(0), m_fdOut(0), m_fdExcep(0)
+    , cb(0), user_data(0)
+{
+    if (events & Read)  {
+        m_fdIn = new QSocketNotifier(fd, QSocketNotifier::Read, this);
+
+        m_fdIn->setEnabled (true);
+
+        QObject::connect (m_fdIn, SIGNAL(activated(int)), this,
+                          SLOT(readyRead(int)));
+    }
+    if (events & Write) {
+        m_fdOut = new QSocketNotifier(fd, QSocketNotifier::Write, this);
+
+        m_fdOut->setEnabled (true);
+
+        QObject::connect (m_fdOut, SIGNAL(activated(int)), this,
+                          SLOT(readyWrite(int)));
+    }
+    if (events & Exception) {
+        m_fdExcep = new QSocketNotifier(fd, QSocketNotifier::Exception, this);
+
+        m_fdExcep->setEnabled (true);
+
+        QObject::connect (m_fdExcep, SIGNAL(activated(int)), this,
+                          SLOT(exception(int)));
+    }
+}
+
+
+QtIO::~QtIO() {
+    if (m_fdIn)
+        delete m_fdIn;
+    if (m_fdOut)
+        delete m_fdOut;
+    if (m_fdExcep)
+        delete m_fdExcep;
+}
+
+
+void QtIO::readyRead (int fd)
+{
+    if(cb)
+        cb(parent(), this, fd, MRP_IO_EVENT_IN, user_data);
+}
+
+
+void QtIO::readyWrite (int fd)
+{
+    if (cb)
+        cb(parent(), this, fd, MRP_IO_EVENT_OUT, user_data);
+}
+
+
+static bool check_hup(int fd)
+{
+    char    buf[1];
+    ssize_t n;
+
+    n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT | MSG_PEEK);
+
+    if (n == 0)
+        return true;
+    else
+        return false;
+}
+
+
+void QtIO::exception (int fd)
+{
+    mrp_io_event_t events;
+
+    if (!check_hup(fd))
+        events = MRP_IO_EVENT_HUP;
+    else
+        events = (mrp_io_event_t)(MRP_IO_EVENT_ERR | MRP_IO_EVENT_HUP);
+
+    if (cb)
+        cb(parent(), this, fd, events, user_data);
+}
+
+
+/*
+ * QtTimer
+ */
+
+QtTimer::QtTimer (int msecs, QObject *parent)
+    : QObject (parent)
+    , m_timer(new QTimer(this)), m_interval(msecs >= 0 ? msecs : 0)
+    , cb(0), user_data(0)
+{
+    m_timer->setInterval (m_interval);
+    m_timer->setSingleShot (false);
+
+    QObject::connect (m_timer, SIGNAL(timeout()), this, SLOT(timedout()));
+}
+
+
+QtTimer::~QtTimer ()
+{
+    delete m_timer;
+}
+
+
+void QtTimer::setInterval (int msecs)
+{
+    m_interval = (msecs >= 0 ? msecs : 0);
+
+    m_timer->setInterval (m_interval);
+
+    if (m_timer->isActive()) {
+        m_timer->stop ();
+        m_timer->start ();
+    }
+}
+
+
+void QtTimer::start ()
+{
+    if (!m_timer->isActive())
+        m_timer->start ();
+}
+
+
+void QtTimer::stop ()
+{
+    if (m_timer->isActive())
+        m_timer->stop();
+}
+
+
+void QtTimer::disable()
+{
+    if (!m_disabled) {
+        delete m_timer;
+
+        m_timer = 0;
+        m_disabled = true;
+    }
+}
+
+
+void QtTimer::enable()
+{
+    if (m_disabled) {
+        m_timer = new QTimer(this);
+
+        setInterval (m_interval);
+
+        connect (m_timer, SIGNAL(timeout()), this, SLOT(timedout()));
+
+        m_timer->start ();
+        m_disabled = false;
+    }
+}
+
+
+void QtTimer::timedout()
+{
+    mrp_debug("timer %p latched", this);
+
+    if (cb)
+        cb(parent(), this, user_data);
+}
+
+
+static void *add_io(void *glue_data, int fd, mrp_io_event_t events,
+                    void (*cb)(void *glue_data, void *id, int fd,
+                               mrp_io_event_t events, void *user_data),
+                    void *user_data)
+{
+    QtGlue          *qt_glue = (QtGlue *)glue_data;
+    QtIO            *io;
+    QtIO::EventMask  mask;
+
+    mask = 0;
+    if (events & MRP_IO_EVENT_IN)
+        mask |= QtIO::Read;
+    if (events & MRP_IO_EVENT_OUT)
+        mask |= QtIO::Write;
+    if (events & (MRP_IO_EVENT_ERR | MRP_IO_EVENT_HUP))
+        mask |= QtIO::Exception;
+
+    io = new QtIO (fd, mask, qt_glue);
+
+    if (io) {
+        mrp_debug("added I/O watch %p (events 0x%x) on fd %d", io, events, fd);
+
+        io->cb        = cb;
+        io->user_data = user_data;
+    }
+
+    return io;
+}
+
+
+static void del_io(void *glue_data, void *id)
+{
+    QtIO *io = (QtIO *)id;
+
+    MRP_UNUSED(glue_data);
+
+    mrp_debug("deleting I/O watch %p", io);
+
+    delete io;
+}
+
+
+static void *add_timer(void *glue_data, unsigned int msecs,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    QtGlue *qt_glue = (QtGlue *)glue_data;
+    QtTimer *t      = new QtTimer(msecs, qt_glue);
+
+    mrp_debug("created timer %p with %d msecs interval", t, msecs);
+
+    if (t) {
+        t->cb        = cb;
+        t->user_data = user_data;
+        t->start();
+    }
+
+    return t;
+}
+
+
+static void del_timer(void *glue_data, void *id)
+{
+    QtTimer *t = (QtTimer *)id;
+
+    MRP_UNUSED(glue_data);
+
+    mrp_debug("deleting timer %p", t);
+
+    delete t;
+}
+
+
+static void mod_timer(void *glue_data, void *id, unsigned int msecs)
+{
+    QtTimer *t = (QtTimer *)id;
+
+    MRP_UNUSED(glue_data);
+
+    mrp_debug("setting timer %p to %d msecs interval", t, msecs);
+
+    if (t != NULL)
+        t->setInterval(msecs);
+}
+
+
+static void *add_defer(void *glue_data,
+                       void (*cb)(void *glue_data, void *id, void *user_data),
+                       void *user_data)
+{
+    QtGlue  *qt_glue = (QtGlue *)glue_data;
+    QtTimer *t       = new QtTimer(0, qt_glue);
+
+    mrp_debug("created timer %p", t);
+
+    if (t) {
+        t->cb        = cb;
+        t->user_data = user_data;
+        t->start();
+    }
+
+    return t;
+}
+
+
+static void del_defer(void *glue_data, void *id)
+{
+    QtTimer *t = (QtTimer *)id;
+
+    MRP_UNUSED(glue_data);
+
+    mrp_debug("deleting timer %p", t);
+
+    delete t;
+}
+
+
+static void mod_defer(void *glue_data, void *id, int enabled)
+{
+    QtTimer *t = (QtTimer *)id;
+
+    MRP_UNUSED(glue_data);
+
+    mrp_debug("%s timer %p", enabled ? "enabling" : "disabling", t);
+
+    if (enabled)
+        t->enable();
+    else
+        t->disable();
+}
+
+
+static void unregister(void *glue_data)
+{
+    QtGlue *qt_glue = (QtGlue *)glue_data;
+
+    mrp_debug("unregistering mainloop");
+
+    delete qt_glue;
+}
+
+
+int mrp_mainloop_register_with_qt(mrp_mainloop_t *ml)
+{
+    static mrp_superloop_ops_t qt_ops;
+    QtGlue *qt_glue;
+
+    qt_ops.add_io     = add_io;
+    qt_ops.del_io     = del_io;
+    qt_ops.add_timer  = add_timer;
+    qt_ops.del_timer  = del_timer;
+    qt_ops.mod_timer  = mod_timer;
+    qt_ops.add_defer  = add_defer;
+    qt_ops.del_defer  = del_defer;
+    qt_ops.mod_defer  = mod_defer;
+    qt_ops.unregister = unregister;
+
+    qt_glue = new QtGlue ();
+
+    return mrp_set_superloop(ml, &qt_ops, (void *)qt_glue);
+}
+
+
+int mrp_mainloop_unregister_from_qt(mrp_mainloop_t *ml)
+{
+    return mrp_mainloop_unregister(ml);
+}
+
+
+mrp_mainloop_t *mrp_mainloop_qt_get(void)
+{
+    if (qt_ml == NULL) {
+        qt_ml = mrp_mainloop_create();
+
+        if (qt_ml != NULL) {
+            if (!mrp_mainloop_register_with_qt(qt_ml)) {
+                mrp_mainloop_destroy(qt_ml);
+                qt_ml = NULL;
+            }
+        }
+    }
+
+    return qt_ml;
+}
diff --git a/src/common/qt-glue.h b/src/common/qt-glue.h
new file mode 100644 (file)
index 0000000..8b2bda5
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_QT_H__
+#define __MURPHY_QT_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mainloop.h>
+
+MRP_CDECL_BEGIN
+
+/** Register the given murphy mainloop with the qt mainloop. */
+int mrp_mainloop_register_with_qt(mrp_mainloop_t *ml);
+
+/** Unrgister the given murphy mainloop from the qt mainloop. */
+int mrp_mainloop_unregister_from_qt(mrp_mainloop_t *ml);
+
+/** Create a murphy mainloop and set it up with the qt mainloop. */
+mrp_mainloop_t *mrp_mainloop_qt_get(void);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_QT_H__ */
diff --git a/src/common/refcnt.h b/src/common/refcnt.h
new file mode 100644 (file)
index 0000000..dbaf8c2
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_REFCNT_H__
+#define __MURPHY_REFCNT_H__
+
+/*
+ * A place/typeholder, so we can switch easily to atomic type
+ * if/when necessary.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+
+#define __MURPHY_REFCNT_CHECK__
+
+MRP_CDECL_BEGIN
+
+typedef int mrp_refcnt_t;
+
+static inline void *_mrp_ref_obj(void *obj, off_t offs)
+{
+    mrp_refcnt_t *refcnt;
+
+    if (obj != NULL) {
+        refcnt = (mrp_refcnt_t *) ((char *) obj + offs);
+        (*refcnt)++;
+    }
+
+    return obj;
+}
+
+static inline int _mrp_unref_obj(void *obj, off_t offs
+#ifdef __MURPHY_REFCNT_CHECK__
+                                 , const char *file
+                                 , int line
+                                 , const char *func
+#endif
+                                 )
+{
+    mrp_refcnt_t *refcnt;
+
+    if (obj != NULL) {
+        refcnt = (mrp_refcnt_t *) ((char *) obj + offs);
+        --(*refcnt);
+
+        if (*refcnt == 0)
+            return TRUE;
+
+#ifdef __MURPHY_REFCNT_CHECK__
+#  define W mrp_log_error
+
+        if (*refcnt < 0) {
+            W("****************** REFCOUNTING BUG WARNING ******************");
+            W("* Reference-counting bug detected. The reference count of");
+            W("* object %p (@offs %d) has dropped to %d.", obj, (int)offs,
+              (int)*refcnt);
+            W("* The offending unref call was made at:");
+            W("*     %s@%s:%d", func ? func : "<unkown>",
+              file ? file : "<unknown>", line);
+            W("*************************************************************");
+        }
+
+#undef W
+#endif
+    }
+
+    return FALSE;
+}
+
+
+static inline void mrp_refcnt_init(mrp_refcnt_t *refcnt)
+{
+    *refcnt = 1;
+}
+
+#define mrp_ref_obj(obj, member)                                          \
+    (typeof(obj))_mrp_ref_obj(obj, MRP_OFFSET(typeof(*(obj)), member))
+
+#ifndef __MURPHY_REFCNT_CHECK__
+#  define mrp_unref_obj(obj, member)                                      \
+    _mrp_unref_obj(obj, MRP_OFFSET(typeof(*(obj)), member))
+#else
+#  define mrp_unref_obj(obj, member)                                      \
+    _mrp_unref_obj(obj, MRP_OFFSET(typeof(*(obj)), member), __LOC__)
+#endif
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_REFCNT_H__ */
diff --git a/src/common/socket-utils.c b/src/common/socket-utils.c
new file mode 100644 (file)
index 0000000..4f7d957
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+
+#include <murphy/common/macros.h>
+
+static int reject_fd = -1;
+
+
+static inline int reserve_reject_fd(void)
+{
+    if (reject_fd < 0)
+        reject_fd = open("/dev/null", O_RDONLY);
+
+    return reject_fd;
+}
+
+
+static void MRP_INIT reserve_reject(void)
+{
+    reserve_reject_fd();
+}
+
+
+int mrp_reject_connection(int sock, struct sockaddr *addr, socklen_t *alen)
+{
+    struct sockaddr *a, buf;
+    socklen_t       *l, len;
+    int              fd;
+
+    if (addr != NULL) {
+        a = addr;
+        l = alen;
+    }
+    else {
+        len = sizeof(buf);
+        a   = &buf;
+        l   = &len;
+    }
+
+    fd = accept(sock, a, l);
+
+    if (fd >= 0) {
+        close(fd);
+
+        return 0;
+    }
+
+    if (errno != EMFILE)
+        return -1;
+
+    if (reject_fd < 0) {
+        errno = ENOENT;
+
+        return -1;
+    }
+
+    close(reject_fd);
+    reject_fd = -1;
+
+    fd = accept(sock, a, l);
+
+    if (fd >= 0)
+        close(fd);
+
+    reserve_reject_fd();
+
+    return (fd >= 0 ? 0 : -1);
+}
diff --git a/src/common/socket-utils.h b/src/common/socket-utils.h
new file mode 100644 (file)
index 0000000..ad1b998
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SOCKET_UTILS_H__
+#define __MURPHY_SOCKET_UTILS_H__
+
+#include <sys/socket.h>
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+int mrp_reject_connection(int sock, struct sockaddr *addr, socklen_t alen);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_SOCKET_UTILS_H__ */
diff --git a/src/common/stream-transport.c b/src/common/stream-transport.c
new file mode 100644 (file)
index 0000000..d8ee1e6
--- /dev/null
@@ -0,0 +1,853 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+#include <sys/uio.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/fragbuf.h>
+#include <murphy/common/socket-utils.h>
+#include <murphy/common/transport.h>
+
+#ifndef UNIX_PATH_MAX
+#    define UNIX_PATH_MAX sizeof(((struct sockaddr_un *)NULL)->sun_path)
+#endif
+
+#define TCP4  "tcp4"
+#define TCP4L 4
+#define TCP6  "tcp6"
+#define TCP6L 4
+#define UNXS  "unxs"
+#define UNXSL 4
+
+#define DEFAULT_SIZE 128                 /* default input buffer size */
+
+typedef struct {
+    MRP_TRANSPORT_PUBLIC_FIELDS;         /* common transport fields */
+    int             sock;                /* TCP socket */
+    mrp_io_watch_t *iow;                 /* socket I/O watch */
+    mrp_fragbuf_t  *buf;                 /* fragment buffer */
+} strm_t;
+
+
+static void strm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                         void *user_data);
+static int strm_disconnect(mrp_transport_t *mt);
+static int open_socket(strm_t *t, int family);
+
+
+
+static int parse_address(const char *str, int *familyp, char *nodep,
+                         size_t nsize, char **servicep, const char **typep)
+{
+    char       *node, *service;
+    const char *type;
+    int         family;
+    size_t      l, nl;
+
+    node = (char *)str;
+
+    if (!strncmp(node, TCP4":", l=TCP4L+1)) {
+        family = AF_INET;
+        type   = TCP4;
+        node  += l;
+    }
+    else if (!strncmp(node, TCP6":", l=TCP6L+1)) {
+        family = AF_INET6;
+        type   = TCP6;
+        node  += l;
+    }
+    else if (!strncmp(node, UNXS":", l=UNXSL+1)) {
+        family = AF_UNIX;
+        type   = UNXS;
+        node  += l;
+    }
+    else {
+        if      (node[0] == '[') family = AF_INET6;
+        else if (node[0] == '/') family = AF_UNIX;
+        else if (node[0] == '@') family = AF_UNIX;
+        else                     family = AF_UNSPEC;
+
+        type = NULL;
+    }
+
+    switch (family) {
+    case AF_INET:
+        service = strrchr(node, ':');
+        if (service == NULL) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        nl = service - node;
+        service++;
+
+    case AF_INET6:
+        service = strrchr(node, ':');
+
+        if (service == NULL || service == node) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        if (node[0] == '[') {
+            node++;
+
+            if (service[-1] != ']') {
+                errno = EINVAL;
+                return -1;
+            }
+
+            nl = service - node - 1;
+        }
+        else
+            nl = service - node;
+
+        service++;
+        break;
+
+    case AF_UNSPEC:
+        if (!strncmp(node, "tcp:", l=4))
+            node += l;
+        service = strrchr(node, ':');
+
+        if (service == NULL || service == node) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        if (node[0] == '[') {
+            node++;
+            family = AF_INET6;
+
+            if (service[-1] != ']') {
+                errno = EINVAL;
+                return -1;
+            }
+
+            nl = service - node - 1;
+        }
+        else {
+            family = AF_INET;
+            nl = service - node;
+        }
+        service++;
+        break;
+
+    case AF_UNIX:
+        service = NULL;
+        nl      = strlen(node);
+    }
+
+    if (nl >= nsize) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    strncpy(nodep, node, nl);
+    nodep[nl] = '\0';
+    *servicep = service;
+    *familyp  = family;
+    if (typep != NULL)
+        *typep = type;
+
+    return 0;
+}
+
+
+static socklen_t strm_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    struct addrinfo    *ai, hints;
+    struct sockaddr_un *un;
+    char                node[UNIX_PATH_MAX], *port;
+    socklen_t           len;
+
+    mrp_clear(&hints);
+
+    if (parse_address(str, &hints.ai_family, node, sizeof(node),
+                      &port, typep) < 0)
+        return 0;
+
+    switch (hints.ai_family) {
+    case AF_UNIX:
+        un  = &addr->unx;
+        len = MRP_OFFSET(typeof(*un), sun_path) + strlen(node) + 1;
+
+        if (size < len)
+            errno = ENOMEM;
+        else {
+            un->sun_family = AF_UNIX;
+            strncpy(un->sun_path, node, UNIX_PATH_MAX-1);
+            if (un->sun_path[0] == '@')
+                un->sun_path[0] = '\0';
+        }
+
+        /* When binding the socket, we don't need the null at the end */
+        len--;
+
+        break;
+
+    case AF_INET:
+    case AF_INET6:
+    default:
+        if (getaddrinfo(node, port, &hints, &ai) == 0) {
+            if (ai->ai_addrlen <= size) {
+                memcpy(addr, ai->ai_addr, ai->ai_addrlen);
+                len = ai->ai_addrlen;
+            }
+            else
+                len = 0;
+
+            freeaddrinfo(ai);
+        }
+        else
+            len = 0;
+    }
+
+    return len;
+}
+
+
+static int strm_open(mrp_transport_t *mt)
+{
+    strm_t *t = (strm_t *)mt;
+
+    t->sock = -1;
+
+    return TRUE;
+}
+
+
+static int set_nonblocking(int sock, int nonblocking)
+{
+    long nb = (nonblocking ? 1 : 0);
+
+    return fcntl(sock, F_SETFL, O_NONBLOCK, nb);
+}
+
+
+static int set_cloexec(int fd, int cloexec)
+{
+    int on = cloexec ? 1 : 0;
+
+    return fcntl(fd, F_SETFL, O_CLOEXEC, on);
+}
+
+
+static int set_reuseaddr(int sock, int reuseaddr)
+{
+    int on;
+
+    if (reuseaddr) {
+        on = 1;
+        return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+    }
+    else
+        return 0;
+}
+
+
+static int strm_createfrom(mrp_transport_t *mt, void *conn)
+{
+    strm_t           *t = (strm_t *)mt;
+    mrp_io_event_t   events;
+
+    t->sock = *(int *)conn;
+
+    if (t->sock >= 0) {
+        if (mt->flags & MRP_TRANSPORT_REUSEADDR)
+            if (set_reuseaddr(t->sock, true) < 0)
+                return FALSE;
+
+        if (mt->flags & MRP_TRANSPORT_NONBLOCK || t->listened)
+            if (set_nonblocking(t->sock, true) < 0)
+                return FALSE;
+
+        if (t->connected || t->listened) {
+            if (!t->connected ||
+                (t->buf = mrp_fragbuf_create(TRUE, 0)) != NULL) {
+                events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+                t->iow = mrp_add_io_watch(t->ml, t->sock, events,
+                                          strm_recv_cb, t);
+
+                if (t->iow != NULL)
+                    return TRUE;
+
+                mrp_fragbuf_destroy(t->buf);
+                t->buf = NULL;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void strm_close(mrp_transport_t *mt)
+{
+    strm_t *t = (strm_t *)mt;
+
+    mrp_debug("closing transport %p", mt);
+
+    mrp_del_io_watch(t->iow);
+    t->iow = NULL;
+
+    mrp_fragbuf_destroy(t->buf);
+    t->buf = NULL;
+
+    if (t->sock >= 0){
+        close(t->sock);
+        t->sock = -1;
+    }
+}
+
+
+static int strm_bind(mrp_transport_t *mt, mrp_sockaddr_t *addr,
+                     socklen_t addrlen)
+{
+    strm_t *t = (strm_t *)mt;
+
+    if (t->sock != -1 || open_socket(t, addr->any.sa_family)) {
+        if (bind(t->sock, &addr->any, addrlen) == 0) {
+            mrp_debug("transport %p bound", mt);
+            return TRUE;
+        }
+    }
+
+    mrp_debug("failed to bind transport %p", mt);
+    return FALSE;
+}
+
+
+static int strm_listen(mrp_transport_t *mt, int backlog)
+{
+    strm_t *t = (strm_t *)mt;
+
+    if (t->sock != -1 && t->iow != NULL && t->evt.connection != NULL) {
+        if (set_nonblocking(t->sock, true) < 0)
+            return FALSE;
+
+        if (listen(t->sock, backlog) == 0) {
+            mrp_debug("transport %p listening", mt);
+            t->listened = TRUE;
+            return TRUE;
+        }
+    }
+
+    mrp_debug("transport %p failed to listen", mt);
+    return FALSE;
+}
+
+
+static int strm_accept(mrp_transport_t *mt, mrp_transport_t *mlt)
+{
+    strm_t         *t, *lt;
+    mrp_sockaddr_t  addr;
+    socklen_t       addrlen;
+    mrp_io_event_t  events;
+
+    t  = (strm_t *)mt;
+    lt = (strm_t *)mlt;
+
+    if (lt->sock < 0) {
+        errno = EBADF;
+
+        return FALSE;
+    }
+
+    addrlen = sizeof(addr);
+    t->sock = accept(lt->sock, &addr.any, &addrlen);
+
+    if (t->sock >= 0) {
+        if (mt->flags & MRP_TRANSPORT_REUSEADDR)
+            if (set_reuseaddr(t->sock, true) < 0)
+                goto reject;
+
+        if (mt->flags & MRP_TRANSPORT_NONBLOCK)
+            if (set_nonblocking(t->sock, true) < 0)
+                goto reject;
+
+        if (mt->flags & MRP_TRANSPORT_CLOEXEC)
+            if (set_cloexec(t->sock, true) < 0)
+                goto reject;
+
+        t->buf = mrp_fragbuf_create(TRUE, 0);
+        events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+        t->iow = mrp_add_io_watch(t->ml, t->sock, events, strm_recv_cb, t);
+
+        if (t->iow != NULL && t->buf != NULL) {
+            mrp_debug("accepted connection on transport %p/%p", mlt, mt);
+            return TRUE;
+        }
+        else {
+            mrp_fragbuf_destroy(t->buf);
+            t->buf = NULL;
+            close(t->sock);
+            t->sock = -1;
+        }
+    }
+    else {
+    reject:
+        if (mrp_reject_connection(lt->sock, NULL, 0) < 0) {
+            mrp_log_error("%s(): accept failed, closing transport %p (%d: %s).",
+                          __FUNCTION__, mlt, errno, strerror(errno));
+            strm_close(mlt);
+
+            /* Notes:
+             *     Unfortunately we cannot safely emit a closed event here.
+             *     The closed event is semantically attached to an accepted
+             *     tranport being closed and there is no equivalent for a
+             *     listening transport (we should have had a generic error
+             *     event). There for the transport owner expects and treats
+             *     (IOW casts) the associated user_data accordingly. That
+             *     would end up in a disaster... Once we cleanup/rework the
+             *     transport infra, this needs to be done better.
+             */
+        }
+        else
+            mrp_log_error("%s(): rejected connection for transport %p (%d: %s).",
+                          __FUNCTION__, mlt, errno, strerror(errno));
+    }
+
+    return FALSE;
+}
+
+
+static void strm_recv_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                         void *user_data)
+{
+    strm_t          *t  = (strm_t *)user_data;
+    mrp_transport_t *mt = (mrp_transport_t *)t;
+    void            *data, *buf;
+    uint32_t         pending;
+    size_t           size;
+    ssize_t          n;
+    int              error;
+
+    MRP_UNUSED(w);
+
+    mrp_debug("event 0x%x for transport %p", events, t);
+
+    if (events & MRP_IO_EVENT_IN) {
+        if (MRP_UNLIKELY(mt->listened != 0)) {
+            MRP_TRANSPORT_BUSY(mt, {
+                    mrp_debug("connection event on transport %p", mt);
+                    mt->evt.connection(mt, mt->user_data);
+                });
+
+            t->check_destroy(mt);
+            return;
+        }
+
+        while (ioctl(fd, FIONREAD, &pending) == 0 && pending > 0) {
+            buf = mrp_fragbuf_alloc(t->buf, pending);
+
+            if (buf == NULL) {
+                error = ENOMEM;
+            fatal_error:
+                mrp_debug("transport %p closed with error %d", mt, error);
+            closed:
+                strm_disconnect(mt);
+
+                if (t->evt.closed != NULL)
+                    MRP_TRANSPORT_BUSY(mt, {
+                            mt->evt.closed(mt, error, mt->user_data);
+                        });
+
+                t->check_destroy(mt);
+                return;
+            }
+
+            n = read(fd, buf, pending);
+
+            if (n >= 0) {
+                if (n < (ssize_t)pending)
+                    mrp_fragbuf_trim(t->buf, buf, pending, n);
+            }
+
+            if (n < 0 && errno != EAGAIN) {
+                error = EIO;
+                goto fatal_error;
+            }
+        }
+
+        data = NULL;
+        size = 0;
+        while (mrp_fragbuf_pull(t->buf, &data, &size)) {
+            if (t->mode != MRP_TRANSPORT_MODE_JSON)
+                error = t->recv_data(mt, data, size, NULL, 0);
+            else {
+                mrp_json_t *msg = mrp_json_string_to_object(data, size);
+
+                if (msg != NULL) {
+                    error = t->recv_data((mrp_transport_t *)t, msg, 0, NULL, 0);
+                    mrp_json_unref(msg);
+                }
+                else
+                    error = EILSEQ;
+            }
+
+            if (error)
+                goto fatal_error;
+
+            if (t->check_destroy(mt))
+                return;
+        }
+    }
+
+    if (events & MRP_IO_EVENT_HUP) {
+        mrp_debug("transport %p closed by peer", mt);
+        error = 0;
+        goto closed;
+    }
+}
+
+
+static int open_socket(strm_t *t, int family)
+{
+    mrp_io_event_t events;
+
+    t->sock = socket(family, SOCK_STREAM, 0);
+
+    if (t->sock != -1) {
+        if (t->flags & MRP_TRANSPORT_REUSEADDR)
+            if (set_reuseaddr(t->sock, true) < 0)
+                goto fail;
+
+        if (t->flags & MRP_TRANSPORT_NONBLOCK)
+            if (set_nonblocking(t->sock, true) < 0)
+                goto fail;
+
+        if (t->flags & MRP_TRANSPORT_CLOEXEC)
+            if (set_cloexec(t->sock, true) < 0)
+                goto fail;
+
+        events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+        t->iow = mrp_add_io_watch(t->ml, t->sock, events, strm_recv_cb, t);
+
+        if (t->iow != NULL)
+            return TRUE;
+        else {
+        fail:
+            close(t->sock);
+            t->sock = -1;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int strm_connect(mrp_transport_t *mt, mrp_sockaddr_t *addr,
+                        socklen_t addrlen)
+{
+    strm_t         *t    = (strm_t *)mt;
+    mrp_io_event_t  events;
+
+    t->sock = socket(addr->any.sa_family, SOCK_STREAM, 0);
+
+    if (t->sock < 0)
+        goto fail;
+
+    if (connect(t->sock, &addr->any, addrlen) == 0) {
+        if (set_reuseaddr(t->sock, true)   < 0 ||
+            set_nonblocking(t->sock, true) < 0)
+            goto close_and_fail;
+
+        t->buf = mrp_fragbuf_create(TRUE, 0);
+
+        if (t->buf != NULL) {
+            events = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+            t->iow = mrp_add_io_watch(t->ml, t->sock, events, strm_recv_cb, t);
+
+            if (t->iow != NULL) {
+                mrp_debug("connected transport %p", mt);
+
+                return TRUE;
+            }
+
+            mrp_fragbuf_destroy(t->buf);
+            t->buf = NULL;
+        }
+    }
+
+    if (t->sock != -1) {
+    close_and_fail:
+        close(t->sock);
+        t->sock = -1;
+    }
+
+ fail:
+    mrp_debug("failed to connect transport %p", mt);
+
+    return FALSE;
+}
+
+
+static int strm_disconnect(mrp_transport_t *mt)
+{
+    strm_t *t = (strm_t *)mt;
+
+    if (t->connected/* || t->iow != NULL*/) {
+        mrp_del_io_watch(t->iow);
+        t->iow = NULL;
+
+        shutdown(t->sock, SHUT_RDWR);
+
+        mrp_fragbuf_destroy(t->buf);
+        t->buf = NULL;
+
+        mrp_debug("disconnected transport %p", mt);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int strm_send(mrp_transport_t *mt, mrp_msg_t *msg)
+{
+    strm_t        *t = (strm_t *)mt;
+    struct iovec  iov[2];
+    void         *buf;
+    ssize_t       size, n;
+    uint32_t      len;
+
+    if (t->connected) {
+        size = mrp_msg_default_encode(msg, &buf);
+
+        if (size >= 0) {
+            len = htobe32(size);
+            iov[0].iov_base = &len;
+            iov[0].iov_len  = sizeof(len);
+            iov[1].iov_base = buf;
+            iov[1].iov_len  = size;
+
+            n = writev(t->sock, iov, 2);
+            mrp_free(buf);
+
+            if (n == (ssize_t)(size + sizeof(len)))
+                return TRUE;
+            else {
+                if (n == -1 && errno == EAGAIN) {
+                    mrp_log_error("%s(): XXX TODO: this sucks, need to add "
+                                  "output queuing for strm-transport.",
+                                  __FUNCTION__);
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int strm_sendraw(mrp_transport_t *mt, void *data, size_t size)
+{
+    strm_t  *t = (strm_t *)mt;
+    ssize_t  n;
+
+    if (t->connected) {
+        n = write(t->sock, data, size);
+
+        if (n == (ssize_t)size)
+            return TRUE;
+        else {
+            if (n == -1 && errno == EAGAIN) {
+                mrp_log_error("%s(): XXX TODO: this sucks, need to add "
+                              "output queuing for strm-transport.",
+                              __FUNCTION__);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int strm_senddata(mrp_transport_t *mt, void *data, uint16_t tag)
+{
+    strm_t           *t = (strm_t *)mt;
+    mrp_data_descr_t *type;
+    ssize_t           n;
+    void             *buf;
+    size_t            size, reserve, len;
+    uint32_t         *lenp;
+    uint16_t         *tagp;
+
+    if (t->connected) {
+        type = mrp_msg_find_type(tag);
+
+        if (type != NULL) {
+            reserve = sizeof(*lenp) + sizeof(*tagp);
+            size    = mrp_data_encode(&buf, data, type, reserve);
+
+            if (size > 0) {
+                lenp  = buf;
+                len   = size - sizeof(*lenp);
+                tagp  = buf + sizeof(*lenp);
+                *lenp = htobe32(len);
+                *tagp = htobe16(tag);
+
+                n = write(t->sock, buf, len + sizeof(*lenp));
+
+                mrp_free(buf);
+
+                if (n == (ssize_t)(len + sizeof(*lenp)))
+                    return TRUE;
+                else {
+                    if (n == -1 && errno == EAGAIN) {
+                        mrp_log_error("%s(): XXX TODO: this sucks, need to add"
+                                      " output queueing for strm-transport.",
+                                      __FUNCTION__);
+                    }
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int strm_sendnative(mrp_transport_t *mt, void *data, uint32_t type_id)
+{
+    strm_t        *t   = (strm_t *)mt;
+    mrp_typemap_t *map = t->map;
+    void          *buf;
+    size_t         size, reserve;
+    uint32_t      *lenp;
+    ssize_t        n;
+
+    if (t->connected) {
+        reserve = sizeof(*lenp);
+
+        if (mrp_encode_native(data, type_id, reserve, &buf, &size, map) == 0) {
+            lenp  = buf;
+            *lenp = htobe32(size - sizeof(*lenp));
+
+            n = write(t->sock, buf, size);
+
+            mrp_free(buf);
+
+            if (n == (ssize_t)size)
+                return TRUE;
+            else {
+                if (n == -1 && errno == EAGAIN) {
+                    mrp_log_error("%s(): XXX TODO: this sucks, need to add"
+                                  " output queueing for strm-transport.",
+                                  __FUNCTION__);
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int strm_sendjson(mrp_transport_t *mt, mrp_json_t *msg)
+{
+    strm_t       *t = (strm_t *)mt;
+    struct iovec  iov[2];
+    const char   *s;
+    ssize_t       size, n;
+    uint32_t      len;
+
+    if (t->connected && (s = mrp_json_object_to_string(msg)) != NULL) {
+        size = strlen(s);
+        len  = htobe32(size);
+        iov[0].iov_base = &len;
+        iov[0].iov_len  = sizeof(len);
+        iov[1].iov_base = (void *)s;
+        iov[1].iov_len  = size;
+
+        n = writev(t->sock, iov, 2);
+
+        if (n == (ssize_t)(size + sizeof(len)))
+            return TRUE;
+        else {
+            if (n == -1 && errno == EAGAIN) {
+                mrp_log_error("%s(): XXX TODO: this sucks, need to add "
+                              "output queuing for strm-transport.",
+                              __FUNCTION__);
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+MRP_REGISTER_TRANSPORT(tcp4, TCP4, strm_t, strm_resolve,
+                       strm_open, strm_createfrom, strm_close, NULL,
+                       strm_bind, strm_listen, strm_accept,
+                       strm_connect, strm_disconnect,
+                       strm_send, NULL,
+                       strm_sendraw, NULL,
+                       strm_senddata, NULL,
+                       NULL, NULL,
+                       strm_sendnative, NULL,
+                       strm_sendjson, NULL);
+
+MRP_REGISTER_TRANSPORT(tcp6, TCP6, strm_t, strm_resolve,
+                       strm_open, strm_createfrom, strm_close, NULL,
+                       strm_bind, strm_listen, strm_accept,
+                       strm_connect, strm_disconnect,
+                       strm_send, NULL,
+                       strm_sendraw, NULL,
+                       strm_senddata, NULL,
+                       NULL, NULL,
+                       strm_sendnative, NULL,
+                       strm_sendjson, NULL);
+
+MRP_REGISTER_TRANSPORT(unxstrm, UNXS, strm_t, strm_resolve,
+                       strm_open, strm_createfrom, strm_close, NULL,
+                       strm_bind, strm_listen, strm_accept,
+                       strm_connect, strm_disconnect,
+                       strm_send, NULL,
+                       strm_sendraw, NULL,
+                       strm_senddata, NULL,
+                       NULL, NULL,
+                       strm_sendnative, NULL,
+                       strm_sendjson, NULL);
diff --git a/src/common/tests/Makefile.am b/src/common/tests/Makefile.am
new file mode 100644 (file)
index 0000000..0162fad
--- /dev/null
@@ -0,0 +1,144 @@
+AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir)
+
+noinst_PROGRAMS  = mm-test hash-test hash12-test msg-test transport-test \
+                 internal-transport-test process-watch-test native-test \
+                mkdir-test path-test mask-test
+
+if LIBDBUS_ENABLED
+noinst_PROGRAMS += mainloop-test dbus-test
+endif
+
+noinst_PROGRAMS += fragbuf-test
+
+# memory management test
+mm_test_SOURCES = mm-test.c
+mm_test_CFLAGS  = $(AM_CFLAGS)
+mm_test_LDADD   = ../../libmurphy-common.la
+
+# hash table test
+hash_test_SOURCES = hash-test.c
+hash_test_CFLAGS  = $(AM_CFLAGS)
+hash_test_LDADD   = ../../libmurphy-common.la
+
+# hash12-test
+hash12_test_SOURCES = hash12-test.c
+hash12_test_CFLAGS  = $(AM_CFLAGS)
+hash12_test_LDADD   = ../../libmurphy-common.la
+
+# mainloop test
+mainloop_test_SOURCES = mainloop-test.c
+mainloop_test_CFLAGS  = $(AM_CFLAGS) $(GLIB_CFLAGS) $(LIBDBUS_CFLAGS)
+mainloop_test_LDADD   = ../../libmurphy-common.la $(GLIB_LIBS) $(LIBDBUS_LIBS)
+if PULSE_ENABLED
+mainloop_test_CFLAGS += $(PULSE_CFLAGS)
+mainloop_test_LDADD  += ../../libmurphy-pulse.la $(PULSE_LIBS)
+endif
+if ECORE_ENABLED
+mainloop_test_CFLAGS += $(ECORE_CFLAGS)
+mainloop_test_LDADD  += ../../libmurphy-ecore.la $(ECORE_LIBS)
+endif
+if GLIB_ENABLED
+mainloop_test_CFLAGS += $(GLIB_CFLAGS)
+mainloop_test_LDADD  += ../../libmurphy-glib.la $(GLIB_LIBS)
+endif
+
+if QT_ENABLED
+noinst_LTLIBRARIES = libmainloop-qt-test.la
+libmainloop_qt_test_la_SOURCES  = mainloop-qt-test.cpp
+libmainloop_qt_test_la_CPPFLAGS = $(AM_CFLAGS) $(QTCORE_CFLAGS)
+libmainloop_qt_test_la_LIBADD   = ../../libmurphy-common.la \
+                                 ../../libmurphy-qt.la $(QTCORE_LIBS)
+mainloop_test_LDADD            += libmainloop-qt-test.la $(QTCORE_LIBS) -lstdc++
+endif
+
+# msg test
+msg_test_SOURCES = msg-test.c
+msg_test_CFLAGS  = $(AM_CFLAGS)
+msg_test_LDADD   = ../../libmurphy-common.la
+
+# native type test
+native_test_SOURCES = native-test.c
+native_test_CFLAGS  = $(AM_CFLAGS)
+native_test_LDADD   = ../../libmurphy-common.la
+
+# transport test
+transport_test_SOURCES = transport-test.c
+transport_test_CFLAGS  = $(AM_CFLAGS)
+transport_test_LDADD   = ../../libmurphy-common.la
+
+# internal transport test
+internal_transport_test_SOURCES = internal-transport-test.c
+internal_transport_test_CFLAGS  = $(AM_CFLAGS)
+internal_transport_test_LDADD   = ../../libmurphy-common.la
+
+# process watch test
+process_watch_test_SOURCES = process-test.c
+process_watch_test_CFLAGS  = $(AM_CFLAGS)
+process_watch_test_LDADD   = ../../libmurphy-common.la
+
+if LIBDBUS_ENABLED
+transport_test_LDADD  += ../../libmurphy-libdbus.la
+
+noinst_PROGRAMS += mainloop-test
+
+# DBUS tests
+noinst_PROGRAMS    += dbus-test
+dbus_test_SOURCES = dbus-test.c
+dbus_test_CFLAGS  = $(AM_CFLAGS) $(LIBDBUS_CFLAGS)
+dbus_test_LDADD   = ../../libmurphy-libdbus.la ../../libmurphy-common.la $(LIBDBUS_LIBS)
+
+noinst_PROGRAMS    += libdbus-test libdbus-transport-test
+libdbus_test_SOURCES = libdbus-test.c
+libdbus_test_CFLAGS  = $(AM_CFLAGS) $(LIBDBUS_CFLAGS)
+libdbus_test_LDADD   = ../../libmurphy-dbus-libdbus.la ../../libmurphy-common.la $(LIBDBUS_LIBS)
+
+libdbus_transport_test_SOURCES = libdbus-transport-test.c
+libdbus_transport_test_CFLAGS  = $(AM_CFLAGS)
+libdbus_transport_test_LDADD   = ../../libmurphy-common.la \
+                                 ../../libmurphy-dbus-libdbus.la
+endif
+
+if SDBUS_ENABLED
+noinst_PROGRAMS    += sdbus-test dbus-sdbus-test sdbus-transport-test sdbus-error-message
+
+sdbus_test_SOURCES = sdbus-test.c
+sdbus_test_CFLAGS  = $(AM_CFLAGS) $(SDBUS_CFLAGS)
+sdbus_test_LDADD   = ../../libmurphy-common.la $(SDBUS_LIBS)
+
+dbus_sdbus_test_SOURCES = dbus-sdbus-test.c
+dbus_sdbus_test_CFLAGS  = $(AM_CFLAGS) $(SDBUS_CFLAGS)
+dbus_sdbus_test_LDADD   =                              \
+                       ../../libmurphy-common.la       \
+                       ../../libmurphy-dbus-sdbus.la
+
+sdbus_transport_test_SOURCES = libdbus-transport-test.c
+sdbus_transport_test_CFLAGS  = $(AM_CFLAGS)
+sdbus_transport_test_LDADD   =                         \
+                       ../../libmurphy-common.la       \
+                       ../../libmurphy-dbus-sdbus.la
+
+sdbus_error_message_SOURCES = sdbus-error-message.c
+sdbus_error_message_CFLAGS  = $(AM_CFLAGS) $(SDBUS_CFLAGS)
+sdbus_error_message_LDADD   =                          \
+                       ../../libmurphy-common.la       \
+                       ../../libmurphy-dbus-sdbus.la
+endif
+
+# fragbuf test
+fragbuf_test_SOURCES = fragbuf-test.c
+fragbuf_test_CFLAGS  = $(AM_CFLAGS)
+fragbuf_test_LDADD   = ../../libmurphy-common.la
+
+# mkdir-test
+mkdir_test_SOURCES = mkdir-test.c
+mkdir_test_CFLAGS  = $(AM_CFLAGS) -I.
+mkdir_test_LDADD   = ../../libmurphy-common.la
+
+# path-test
+path_test_SOURCES = path-test.c
+path_test_CFLAGS  = $(AM_CFLAGS) -I.
+path_test_LDADD   = ../../libmurphy-common.la
+
+mask_test_SOURCES = mask-test.c
+mask_test_CFLAGS  = $(AM_CFLAGS) -I.
+mask_test_LDADD   = ../../libmurphy-common.la
diff --git a/src/common/tests/dbus-pump.c b/src/common/tests/dbus-pump.c
new file mode 100644 (file)
index 0000000..4dc323d
--- /dev/null
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <dbus/dbus.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+
+typedef struct dbus_glue_s dbus_glue_t;
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_io_watch_t  *mw;
+    DBusWatch       *dw;
+    mrp_list_hook_t  hook;
+} watch_t;
+
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_timer_t     *mt;
+    DBusTimeout     *dt;
+    mrp_list_hook_t  hook;
+} timeout_t;
+
+
+struct dbus_glue_s {
+    DBusConnection  *conn;
+    mrp_mainloop_t  *ml;
+    mrp_list_hook_t  watches;
+    mrp_list_hook_t  timers;
+    mrp_deferred_t  *pump;
+};
+
+
+static dbus_int32_t data_slot = -1;
+
+static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events,
+                           void *user_data)
+{
+    watch_t        *watch = (watch_t *)user_data;
+    DBusConnection *conn  = watch->glue->conn;
+    unsigned int    mask  = 0;
+
+    MRP_UNUSED(mw);
+    MRP_UNUSED(fd);
+
+    if (events & MRP_IO_EVENT_IN)
+        mask |= DBUS_WATCH_READABLE;
+    if (events & MRP_IO_EVENT_OUT)
+        mask |= DBUS_WATCH_WRITABLE;
+    if (events & MRP_IO_EVENT_HUP)
+        mask |= DBUS_WATCH_HANGUP;
+    if (events & MRP_IO_EVENT_ERR)
+        mask |= DBUS_WATCH_ERROR;
+
+    dbus_connection_ref(conn);
+    dbus_watch_handle(watch->dw, mask);
+    dbus_connection_unref(conn);
+}
+
+
+static void watch_freed_cb(void *data)
+{
+    watch_t *watch = (watch_t *)data;
+
+    if (watch != NULL) {
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+        mrp_free(watch);
+    }
+}
+
+
+static dbus_bool_t add_watch(DBusWatch *dw, void *data)
+{
+    dbus_glue_t    *glue = (dbus_glue_t *)data;
+    watch_t        *watch;
+    mrp_io_watch_t *mw;
+    mrp_io_event_t  mask;
+    int             fd;
+    unsigned int    flags;
+
+    if (!dbus_watch_get_enabled(dw))
+        return TRUE;
+
+    fd    = dbus_watch_get_unix_fd(dw);
+    flags = dbus_watch_get_flags(dw);
+    mask  = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR;
+
+    if (flags & DBUS_WATCH_READABLE)
+        mask |= MRP_IO_EVENT_IN;
+    if (flags & DBUS_WATCH_WRITABLE)
+        mask |= MRP_IO_EVENT_OUT;
+
+    if ((watch = mrp_allocz(sizeof(*watch))) != NULL) {
+        mrp_list_init(&watch->hook);
+        mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch);
+
+        if (mw != NULL) {
+            watch->glue = glue;
+            watch->mw   = mw;
+            watch->dw   = dw;
+            dbus_watch_set_data(dw, watch, watch_freed_cb);
+            mrp_list_append(&glue->watches, &watch->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(watch);
+    }
+
+    return FALSE;
+}
+
+
+static void del_watch(DBusWatch *dw, void *data)
+{
+    watch_t *watch = (watch_t *)dbus_watch_get_data(dw);
+
+    MRP_UNUSED(data);
+
+    if (watch != NULL) {
+        mrp_del_io_watch(watch->mw);
+        watch->mw = NULL;
+    }
+}
+
+
+static void toggle_watch(DBusWatch *dw, void *data)
+{
+    if (dbus_watch_get_enabled(dw))
+        add_watch(dw, data);
+    else
+        del_watch(dw, data);
+}
+
+
+static void dispatch_timeout(mrp_timer_t *mt, void *user_data)
+{
+    timeout_t *timer = (timeout_t *)user_data;
+
+    MRP_UNUSED(mt);
+
+    dbus_timeout_handle(timer->dt);
+}
+
+
+static void timeout_freed_cb(void *data)
+{
+    timeout_t *timer = (timeout_t *)data;
+
+    if (timer != NULL) {
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+}
+
+
+static dbus_bool_t add_timeout(DBusTimeout *dt, void *data)
+{
+    dbus_glue_t  *glue = (dbus_glue_t *)data;
+    timeout_t    *timer;
+    mrp_timer_t  *mt;
+    unsigned int  msecs;
+
+    if ((timer = mrp_allocz(sizeof(*timer))) != NULL) {
+        mrp_list_init(&timer->hook);
+        msecs = dbus_timeout_get_interval(dt);
+        mt    = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer);
+
+        if (mt != NULL) {
+            timer->glue = glue;
+            timer->mt   = mt;
+            timer->dt   = dt;
+            dbus_timeout_set_data(dt, timer, timeout_freed_cb);
+            mrp_list_append(&glue->timers, &timer->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(timer);
+    }
+
+    return FALSE;
+}
+
+
+static void del_timeout(DBusTimeout *dt, void *data)
+{
+    timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt);
+
+    MRP_UNUSED(data);
+
+    if (timer != NULL) {
+        mrp_del_timer(timer->mt);
+        timer->mt = NULL;
+    }
+}
+
+
+static void toggle_timeout(DBusTimeout *dt, void *data)
+{
+    if (dbus_timeout_get_enabled(dt))
+        add_timeout(dt, data);
+    else
+        del_timeout(dt, data);
+}
+
+
+static void wakeup_mainloop(void *data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)data;
+
+    mrp_enable_deferred(glue->pump);
+}
+
+
+static void glue_free_cb(void *data)
+{
+    dbus_glue_t     *glue = (dbus_glue_t *)data;
+    mrp_list_hook_t *p, *n;
+    watch_t         *watch;
+    timeout_t       *timer;
+
+    mrp_list_foreach(&glue->watches, p, n) {
+        watch = mrp_list_entry(p, typeof(*watch), hook);
+
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+
+        mrp_free(watch);
+    }
+
+    mrp_list_foreach(&glue->timers, p, n) {
+        timer = mrp_list_entry(p, typeof(*timer), hook);
+
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+
+    mrp_free(glue);
+}
+
+
+static void pump_cb(mrp_deferred_t *d, void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE)
+        mrp_disable_deferred(d);
+}
+
+
+static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status,
+                               void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    MRP_UNUSED(conn);
+
+    switch (status) {
+    case DBUS_DISPATCH_COMPLETE:
+        mrp_disable_deferred(glue->pump);
+        break;
+
+    case DBUS_DISPATCH_DATA_REMAINS:
+    case DBUS_DISPATCH_NEED_MEMORY:
+    default:
+        mrp_enable_deferred(glue->pump);
+        break;
+    }
+}
+
+
+int mrp_setup_dbus_connection(mrp_mainloop_t *ml, DBusConnection *conn)
+{
+    dbus_glue_t *glue;
+
+    if (!dbus_connection_allocate_data_slot(&data_slot))
+        return FALSE;
+
+    if (dbus_connection_get_data(conn, data_slot) != NULL)
+        return FALSE;
+
+    if ((glue = mrp_allocz(sizeof(*glue))) != NULL) {
+        mrp_list_init(&glue->watches);
+        mrp_list_init(&glue->timers);
+        glue->pump = mrp_add_deferred(ml, pump_cb, glue);
+
+        if (glue->pump == NULL) {
+            mrp_free(glue);
+            return FALSE;
+        }
+
+        glue->ml   = ml;
+        glue->conn = conn;
+    }
+    else
+        return FALSE;
+
+    if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb))
+        return FALSE;
+
+    dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb,
+                                                 glue, NULL);
+
+    dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop,
+                                             glue, NULL);
+
+    return
+        dbus_connection_set_watch_functions(conn, add_watch, del_watch,
+                                            toggle_watch, glue, NULL) &&
+            dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout,
+                                              toggle_timeout, glue, NULL);
+}
+
diff --git a/src/common/tests/dbus-sdbus-test.c b/src/common/tests/dbus-sdbus-test.c
new file mode 100644 (file)
index 0000000..a7fcb86
--- /dev/null
@@ -0,0 +1,563 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+#include <murphy/common/dbus-sdbus.h>
+
+#define SERVER_NAME      "org.test.murphy-server"
+#define SERVER_PATH      "/server"
+#define SERVER_INTERFACE "Murphy.Server"
+#define PING             "ping"
+#define CLIENT_NAME      "org.test.murphy-client"
+#define CLIENT_PATH      "/client"
+#define CLIENT_INTERFACE "Murphy.Client"
+#define PONG             "pong"
+
+
+typedef struct {
+    char            *busaddr;
+    char            *srvname;
+    int              server;
+    int              log_mask;
+    const char      *log_target;
+    mrp_mainloop_t  *ml;
+    mrp_timer_t     *timer;
+    uint32_t         seqno;
+    mrp_dbus_t      *dbus;
+    const char      *name;
+    int32_t          cid;
+    int              server_up;
+    int              all_pongs;
+} context_t;
+
+
+static mrp_dbus_msg_t *create_pong_signal(mrp_dbus_t *dbus, const char *dest,
+                                          uint32_t seq)
+{
+    const char     *sig = "u";
+    mrp_dbus_msg_t *msg;
+
+    msg = mrp_dbus_msg_signal(dbus, dest, SERVER_PATH, SERVER_INTERFACE, PONG);
+
+    if (msg != NULL) {
+        if (mrp_dbus_msg_open_container(msg, MRP_DBUS_TYPE_ARRAY, sig) &&
+            mrp_dbus_msg_append_basic(msg, MRP_DBUS_TYPE_UINT32, &seq) &&
+            mrp_dbus_msg_close_container(msg))
+            return msg;
+        else
+            mrp_dbus_msg_unref(msg);
+    }
+
+    return NULL;
+}
+
+
+static uint32_t parse_pong_signal(mrp_dbus_msg_t *msg)
+{
+    const char *sig = "u";
+    uint32_t    seq;
+
+    if (mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_ARRAY, sig) &&
+        mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq) &&
+        mrp_dbus_msg_exit_container(msg))
+        return seq;
+    else
+        return (uint32_t)-1;
+}
+
+
+static int ping_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data)
+{
+    context_t      *c = (context_t *)user_data;
+    mrp_dbus_msg_t *pong;
+    uint32_t        seq;
+    const char     *dest;
+
+    MRP_UNUSED(c);
+
+    if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq))
+        mrp_log_info("-> ping request #%u", seq);
+    else
+        mrp_log_error("-> malformed ping request");
+
+    if (mrp_dbus_reply(dbus, msg, MRP_DBUS_TYPE_UINT32, &seq,
+                        MRP_DBUS_TYPE_INVALID))
+        mrp_log_info("<- ping reply #%u", seq);
+    else
+        mrp_log_error("Failed to send ping reply #%u.", seq);
+
+    if (seq & 0x1)
+        dest = mrp_dbus_msg_sender(msg);
+    else
+        dest = NULL;
+
+    if ((pong = create_pong_signal(dbus, dest, seq)) != NULL) {
+        if (mrp_dbus_send_msg(dbus, pong))
+            mrp_log_info("<- pong %s #%u", dest ? "signal" : "broadcast", seq);
+        else
+            mrp_log_error("Failed to send pong signal #%u.", seq);
+
+        mrp_dbus_msg_unref(pong);
+    }
+    else
+        mrp_log_error("Failed to create pong signal #%u.", seq);
+
+    return TRUE;
+}
+
+
+static int name_owner_changed(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg,
+                              void *user_data)
+{
+    context_t  *c = (context_t *)user_data;
+    const char *name, *prev, *next;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(dbus);
+
+    if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &name) &&
+        mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &prev) &&
+        mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &next))
+        mrp_log_info("Name %s was reassigned from %s to %s...", name,
+                     prev, next);
+    else
+        mrp_log_error("Failed to parse NameOwnerChanged signal.");
+
+    return TRUE;
+}
+
+
+static void server_setup(context_t *c)
+{
+    c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL);
+
+    if (c->dbus == NULL) {
+        mrp_log_error("Failed to create D-BUS connection to '%s' bus.",
+                      c->busaddr);
+        exit(1);
+    }
+
+    c->name = mrp_dbus_get_unique_name(c->dbus);
+    mrp_log_info("Our address is %s on the bus...",
+                 c->name ? c->name : "unknown");
+
+    if (c->srvname && *c->srvname) {
+        if (!mrp_dbus_acquire_name(c->dbus, c->srvname, NULL)) {
+            mrp_log_error("Failed to acquire D-BUS name '%s' on bus '%s'.",
+                          c->srvname, c->busaddr);
+            exit(1);
+        }
+    }
+
+    if (!mrp_dbus_export_method(c->dbus, SERVER_PATH, SERVER_INTERFACE,
+                                PING, ping_handler, c)) {
+        mrp_log_error("Failed to export D-BUS method '%s'.", PING);
+        exit(1);
+    }
+
+    if (!mrp_dbus_subscribe_signal(c->dbus, name_owner_changed, c,
+                                   "org.freedesktop.DBus",
+                                   "/org/freedesktop/DBus",
+                                   "org.freedesktop.DBus",
+                                   "NameOwnerChanged",
+                                   NULL)) {
+        mrp_log_error("Failed to subscribe to NameOwnerChanged signals.");
+        exit(1);
+    }
+}
+
+
+void server_cleanup(context_t *c)
+{
+    if (c->srvname && *c->srvname)
+        mrp_dbus_release_name(c->dbus, c->srvname, NULL);
+
+    mrp_dbus_remove_method(c->dbus, SERVER_PATH, SERVER_INTERFACE,
+                           PING, ping_handler, c);
+    mrp_dbus_unref(c->dbus);
+}
+
+
+static void ping_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    uint32_t   seq;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(user_data);
+
+    if (mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_ERROR) {
+        mrp_log_error("Received errorping reply.");
+
+        return;
+    }
+
+    if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq))
+        mrp_log_info("-> ping reply #%u", seq);
+    else
+        mrp_log_error("Received malformedping reply.");
+
+    c->cid = 0;
+}
+
+
+static void ping_request(context_t *c)
+{
+    uint32_t seq;
+
+    if (c->cid != 0) {
+        mrp_log_warning("Previous ping request still unanswered...");
+        return;
+    }
+
+    seq    = c->seqno++;
+    c->cid = mrp_dbus_call(c->dbus,
+                           c->srvname, SERVER_PATH, SERVER_INTERFACE,
+                           PING, 500, ping_reply, c,
+                           MRP_DBUS_TYPE_UINT32, &seq,
+                           MRP_DBUS_TYPE_INVALID);
+
+    if (c->cid > 0)
+        mrp_log_info("<- ping request #%u", seq);
+    else
+        mrp_log_warning("Failed to send ping request #%u.", seq);
+}
+
+
+static void send_cb(mrp_timer_t *t, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    ping_request(c);
+}
+
+
+static int pong_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    uint32_t   seq;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(dbus);
+
+    if ((seq = parse_pong_signal(msg)) != (uint32_t)-1)
+        mrp_log_info("-> pong signal #%u", seq);
+    else
+        mrp_log_error("-> malformed pong signal");
+
+    return TRUE;
+}
+
+
+static void server_status_cb(mrp_dbus_t *dbus, const char *name, int up,
+                             const char *owner, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(name);
+
+    if (up) {
+        mrp_log_info("%s came up (as %s)", name, owner);
+
+        if (c->timer == NULL) {
+            c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+            if (c->timer == NULL) {
+                mrp_log_error("Failed to create D-BUS sending timer.");
+                exit(1);
+            }
+        }
+    }
+    else {
+        mrp_log_info("%s went down", name);
+
+        if (c->timer != NULL) {
+            mrp_del_timer(c->timer);
+            c->timer = NULL;
+        }
+    }
+}
+
+
+static void client_setup(context_t *c)
+{
+    const char *dest;
+
+    c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL);
+
+    if (c->dbus == NULL) {
+        mrp_log_error("Failed to create D-BUS connection to '%s' bus.",
+                      c->busaddr);
+        exit(1);
+    }
+
+    c->name = mrp_dbus_get_unique_name(c->dbus);
+    mrp_log_info("Our address is %s on the bus...",
+                 c->name ? c->name : "unknown");
+
+    mrp_dbus_follow_name(c->dbus, c->srvname, server_status_cb, c);
+
+    if (c->all_pongs) {
+        mrp_log_info("Subscribing for all pong signals...");
+        dest = NULL;
+    }
+    else {
+        mrp_log_info("Subscribing only for pong signals to us...");
+        dest = c->name;
+    }
+
+    if (!mrp_dbus_subscribe_signal(c->dbus, pong_handler, c,
+                                   dest, SERVER_PATH, SERVER_INTERFACE,
+                                   PONG, NULL)) {
+        mrp_log_error("Failed to subscribe for signal '%s/%s.%s'.", SERVER_PATH,
+                      SERVER_INTERFACE, PONG);
+        exit(1);
+    }
+
+    c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+    if (c->timer == NULL) {
+        mrp_log_error("Failed to create D-BUS sending timer.");
+        exit(1);
+    }
+}
+
+
+static void client_cleanup(context_t *c)
+{
+    mrp_dbus_forget_name(c->dbus, c->srvname, server_status_cb, c);
+    mrp_del_timer(c->timer);
+    mrp_dbus_unsubscribe_signal(c->dbus, pong_handler, c,
+                                c->name, SERVER_PATH, SERVER_INTERFACE,
+                                PONG, NULL);
+    mrp_dbus_unref(c->dbus);
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -s, --server                   run as test server (default)\n"
+           "  -b, --bus                      connect the given D-BUS\n"
+           "      If omitted, defaults to the session bus.\n"
+           "  -a, --all-pongs                subscribe for all pong signals\n"
+           "      If omitted, only pong with the client address are handled.\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug site               enable debug message for <site>\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->busaddr    = "session";
+    ctx->srvname    = SERVER_NAME;
+    ctx->server     = FALSE;
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "sab:n:l:t:vd:h"
+    struct option options[] = {
+        { "server"    , no_argument      , NULL, 's' },
+        { "bus"       , required_argument, NULL, 'b' },
+        { "name"      , required_argument, NULL, 'n' },
+        { "all-pongs" , no_argument      , NULL, 'a' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int  opt;
+
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            ctx->server = TRUE;
+            break;
+
+        case 'b':
+            ctx->busaddr = optarg;
+            break;
+
+        case 'n':
+            ctx->srvname = optarg;
+            break;
+
+        case 'a':
+            ctx->all_pongs = TRUE;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h);
+    context_t      *c = (context_t *)user_data;
+
+    MRP_UNUSED(c);
+
+    switch (signum) {
+    case SIGINT:
+        mrp_log_info("Got SIGINT, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+
+    case SIGTERM:
+        mrp_log_info("Got SIGTERM, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t c;
+
+    mrp_clear(&c);
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.server)
+        mrp_log_info("Running as server, using D-BUS '%s'...", c.busaddr);
+    else
+        mrp_log_info("Running as client, using D-BUS '%s'...", c.busaddr);
+
+    c.ml = mrp_mainloop_create();
+
+    if (c.ml == NULL) {
+        mrp_log_error("Failed to create mainloop.");
+        exit(1);
+    }
+
+    mrp_add_sighandler(c.ml, SIGINT , signal_handler, &c);
+
+    if (c.server)
+        server_setup(&c);
+    else
+        client_setup(&c);
+
+    mrp_mainloop_run(c.ml);
+
+    if (c.server)
+        server_cleanup(&c);
+    else
+        client_cleanup(&c);
+
+    return 0;
+}
diff --git a/src/common/tests/dbus-test.c b/src/common/tests/dbus-test.c
new file mode 100644 (file)
index 0000000..e0ab062
--- /dev/null
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+#include <murphy/common/libdbus.h>
+
+#define SERVER_NAME      "org.test.murphy-server"
+#define SERVER_PATH      "/server"
+#define SERVER_INTERFACE "Murphy.Server"
+#define PING             "ping"
+#define CLIENT_NAME      "org.test.murphy-client"
+#define CLIENT_PATH      "/client"
+#define CLIENT_INTERFACE "Murphy.Client"
+#define PONG             "pong"
+
+
+typedef struct {
+    char            *busaddr;
+    char            *srvname;
+    int              server;
+    int              log_mask;
+    const char      *log_target;
+    mrp_mainloop_t  *ml;
+    mrp_timer_t     *timer;
+    uint32_t         seqno;
+    mrp_dbus_t      *dbus;
+    const char      *name;
+    int32_t          cid;
+    int              server_up;
+    int              all_pongs;
+} context_t;
+
+
+static int ping_handler(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data)
+{
+    context_t  *c = (context_t *)user_data;
+    uint32_t    seq;
+    const char *dest;
+
+    MRP_UNUSED(c);
+
+    if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL &&
+        dbus_message_get_args(msg, NULL,
+                              DBUS_TYPE_UINT32, &seq,
+                              DBUS_TYPE_INVALID))
+        mrp_log_info("-> ping request #%u", seq);
+    else
+        mrp_log_error("-> malformed ping request");
+
+    if (!mrp_dbus_reply(dbus, msg,
+                        DBUS_TYPE_UINT32, &seq,
+                        DBUS_TYPE_INVALID))
+        mrp_log_error("Failed to send ping reply #%u.", seq);
+    else
+        mrp_log_info("<- ping reply #%u", seq);
+
+    if (seq & 0x1)
+        dest = dbus_message_get_sender(msg);
+    else
+        dest = NULL;
+
+    if (!mrp_dbus_signal(dbus, dest, SERVER_PATH, SERVER_INTERFACE, PONG,
+                         DBUS_TYPE_UINT32, &seq,
+                         DBUS_TYPE_INVALID))
+        mrp_log_error("Failed to send pong signal #%u.", seq);
+    else
+        mrp_log_info("<- pong %s #%u", dest ? "signal" : "broadcast", seq);
+
+    return TRUE;
+}
+
+
+static void server_setup(context_t *c)
+{
+    c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL);
+
+    if (c->dbus == NULL) {
+        mrp_log_error("Failed to create D-BUS connection to '%s' bus.",
+                      c->busaddr);
+        exit(1);
+    }
+
+    c->name = mrp_dbus_get_unique_name(c->dbus);
+    mrp_log_info("Our address is %s on the bus...",
+                 c->name ? c->name : "unknown");
+
+    if (c->srvname && *c->srvname) {
+        if (!mrp_dbus_acquire_name(c->dbus, c->srvname, NULL)) {
+            mrp_log_error("Failed to acquire D-BUS name '%s' on bus '%s'.",
+                          c->srvname, c->busaddr);
+            exit(1);
+        }
+    }
+
+    if (!mrp_dbus_export_method(c->dbus, SERVER_PATH, SERVER_INTERFACE,
+                                PING, ping_handler, c)) {
+        mrp_log_error("Failed to export D-BUS method '%s'.", PING);
+        exit(1);
+    }
+}
+
+
+void server_cleanup(context_t *c)
+{
+    if (c->srvname && *c->srvname)
+        mrp_dbus_release_name(c->dbus, c->srvname, NULL);
+    mrp_dbus_remove_method(c->dbus, SERVER_PATH, SERVER_INTERFACE,
+                           PING, ping_handler, c);
+    mrp_dbus_unref(c->dbus);
+}
+
+
+static void ping_reply(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    uint32_t   seq;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(user_data);
+
+    if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_ERROR) {
+        const char *ename, *emsg;
+
+        if (!dbus_message_get_args(msg, NULL,
+                                   DBUS_TYPE_STRING, &ename,
+                                   DBUS_TYPE_STRING, &emsg,
+                                   DBUS_TYPE_INVALID)) {
+            ename = "<unknown>";
+            emsg  = "<unknown>";
+        }
+
+        mrp_log_error("Received error reply (%s, %s) to ping.", ename, emsg);
+
+        c->cid = 0;
+        return;
+    }
+
+    if (dbus_message_get_args(msg, NULL,
+                              DBUS_TYPE_UINT32, &seq,
+                              DBUS_TYPE_INVALID))
+        mrp_log_info("-> ping reply #%u", seq);
+    else
+        mrp_log_error("Received malformed ping reply.");
+
+    c->cid = 0;
+}
+
+
+static void ping_request(context_t *c)
+{
+    uint32_t seq;
+
+    if (c->cid != 0) {
+        mrp_log_warning("Previous ping request still unanswered...");
+        return;
+    }
+
+    seq    = c->seqno++;
+    c->cid = mrp_dbus_call(c->dbus,
+                           c->srvname, SERVER_PATH, SERVER_INTERFACE,
+                           PING, 500, ping_reply, c,
+                           DBUS_TYPE_UINT32, &seq,
+                           DBUS_TYPE_INVALID);
+
+    if (c->cid > 0)
+        mrp_log_info("<- ping request #%u", seq);
+    else
+        mrp_log_warning("Failed to send ping request #%u.", seq);
+}
+
+
+static void send_cb(mrp_timer_t *t, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    ping_request(c);
+}
+
+
+static int pong_handler(mrp_dbus_t *dbus, DBusMessage *msg, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    uint32_t   seq;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(dbus);
+
+    if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_SIGNAL &&
+        dbus_message_get_args(msg, NULL,
+                              DBUS_TYPE_UINT32, &seq,
+                              DBUS_TYPE_INVALID))
+        mrp_log_info("-> pong signal #%u", seq);
+    else
+        mrp_log_error("-> malformed pong signal");
+
+    return TRUE;
+}
+
+
+static void server_status_cb(mrp_dbus_t *dbus, const char *name, int up,
+                             const char *owner, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(name);
+
+    if (up) {
+        mrp_log_info("%s came up (as %s)", name, owner);
+
+        if (c->timer == NULL) {
+            c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+            if (c->timer == NULL) {
+                mrp_log_error("Failed to create D-BUS sending timer.");
+                exit(1);
+            }
+        }
+    }
+    else {
+        mrp_log_info("%s went down", name);
+
+        if (c->timer != NULL) {
+            mrp_del_timer(c->timer);
+            c->timer = NULL;
+        }
+    }
+}
+
+
+static void client_setup(context_t *c)
+{
+    const char *dest;
+
+    c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL);
+
+    if (c->dbus == NULL) {
+        mrp_log_error("Failed to create D-BUS connection to '%s' bus.",
+                      c->busaddr);
+        exit(1);
+    }
+
+    c->name = mrp_dbus_get_unique_name(c->dbus);
+    mrp_log_info("Our address is %s on the bus...",
+                 c->name ? c->name : "unknown");
+
+    mrp_dbus_follow_name(c->dbus, c->srvname, server_status_cb, c);
+
+    if (c->all_pongs) {
+        mrp_log_info("Subscribing for all pong signals...");
+        dest = NULL;
+    }
+    else {
+        mrp_log_info("Subscribing only for pong signals to us...");
+        dest = c->name;
+    }
+
+    if (!mrp_dbus_subscribe_signal(c->dbus, pong_handler, c,
+                                   dest, SERVER_PATH, SERVER_INTERFACE,
+                                   PONG, NULL)) {
+        mrp_log_error("Failed to subscribe for signal '%s/%s.%s'.", SERVER_PATH,
+                      SERVER_INTERFACE, PONG);
+        exit(1);
+    }
+
+    c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+    if (c->timer == NULL) {
+        mrp_log_error("Failed to create D-BUS sending timer.");
+        exit(1);
+    }
+}
+
+
+static void client_cleanup(context_t *c)
+{
+    mrp_dbus_follow_name(c->dbus, c->srvname, server_status_cb, c);
+    mrp_del_timer(c->timer);
+    mrp_dbus_subscribe_signal(c->dbus, pong_handler, c,
+                              c->name, SERVER_PATH, SERVER_INTERFACE,
+                              PONG, NULL);
+    mrp_dbus_unref(c->dbus);
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -s, --server                   run as test server (default)\n"
+           "  -b, --bus                      connect the given D-BUS\n"
+           "      If omitted, defaults to the session bus.\n"
+           "  -a, --all-pongs                subscribe for all pong signals\n"
+           "      If omitted, only pong with the client address are handled.\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->busaddr    = "session";
+    ctx->srvname    = SERVER_NAME;
+    ctx->server     = FALSE;
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "sab:n:l:t:vdh"
+    struct option options[] = {
+        { "server"    , no_argument      , NULL, 's' },
+        { "bus"       , required_argument, NULL, 'b' },
+        { "name"      , required_argument, NULL, 'n' },
+        { "all-pongs" , no_argument      , NULL, 'a' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , no_argument      , NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int  opt, debug;
+
+    debug = FALSE;
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            ctx->server = TRUE;
+            break;
+
+        case 'b':
+            ctx->busaddr = optarg;
+            break;
+
+        case 'n':
+            ctx->srvname = optarg;
+            break;
+
+        case 'a':
+            ctx->all_pongs = TRUE;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            debug = TRUE;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    if (debug)
+        ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+
+    return TRUE;
+}
+
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h);
+    context_t      *c = (context_t *)user_data;
+
+    MRP_UNUSED(c);
+
+    switch (signum) {
+    case SIGINT:
+        mrp_log_info("Got SIGINT, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+
+    case SIGTERM:
+        mrp_log_info("Got SIGTERM, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t c;
+
+    mrp_clear(&c);
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.server)
+        mrp_log_info("Running as server, using D-BUS '%s'...", c.busaddr);
+    else
+        mrp_log_info("Running as client, using D-BUS '%s'...", c.busaddr);
+
+    c.ml = mrp_mainloop_create();
+
+    if (c.ml == NULL) {
+        mrp_log_error("Failed to create mainloop.");
+        exit(1);
+    }
+
+    mrp_add_sighandler(c.ml, SIGINT , signal_handler, &c);
+
+    if (c.server)
+        server_setup(&c);
+    else
+        client_setup(&c);
+
+    mrp_mainloop_run(c.ml);
+
+    if (c.server)
+        server_cleanup(&c);
+    else
+        client_cleanup(&c);
+
+    return 0;
+}
diff --git a/src/common/tests/fragbuf-test.c b/src/common/tests/fragbuf-test.c
new file mode 100644 (file)
index 0000000..4da9a34
--- /dev/null
@@ -0,0 +1,384 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <getopt.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/fragbuf.h>
+
+
+#define fatal(fmt, args...) do {                \
+        mrp_log_error(fmt, ## args);            \
+        exit(1);                                \
+    } while (0)
+
+
+typedef struct {
+    int         log_mask;
+    const char *log_target;
+    int         framed;
+} context_t;
+
+context_t ctx;
+
+void check_message(void *data, size_t size, char **messages,
+                   int *chk, int *offs)
+{
+    char *p, *d;
+    int   l;
+
+    if (ctx.framed) {
+        if (!strncmp(messages[*chk], data, size) && !messages[*chk][size])
+            mrp_debug("message check: OK");
+        else
+            fatal("message check: failed");
+
+        *chk += 1;
+    }
+    else {
+        d = data;
+        while (size > 0) {
+            p = messages[*chk] + *offs;
+            l = strlen(p);
+
+            if (l > (int)size)
+                l = (int)size;
+
+            if (strncmp(p, d, l))
+                fatal("message check: failed");
+
+            *offs += l;
+            size  -= l;
+            d     += l;
+
+            if (messages[*chk][*offs] == '\0') {
+                *chk  += 1;
+                *offs  = 0;
+            }
+        }
+        mrp_debug("message check: OK");
+    }
+}
+
+
+void dump_buffer(mrp_fragbuf_t *buf, char **messages, int *chk, int *offs)
+{
+    void   *data;
+    size_t  size;
+    int     cnt;
+
+    data = NULL;
+    size = 0;
+    cnt  = 0;
+
+    while (mrp_fragbuf_pull(buf, &data, &size)) {
+        mrp_log_info("got message: (%zd bytes) [%*.*s]", size,
+                     (int)size, (int)size, (char *)data);
+
+        check_message(data, size, messages, chk, offs);
+
+        cnt++;
+    }
+
+    if (!cnt)
+        mrp_debug("no full messages in buffer");
+    else
+        mrp_debug("pulled %d messages from buffer...", cnt);
+}
+
+
+int test(mrp_fragbuf_t *buf, size_t *chunks, int dump_interval)
+{
+    char *messages[] = {
+        "Ticking away the moments",
+        "That make up a dull day",
+        "Fritter and waste the hours",
+        "In an off-hand way",
+        "Kicking around on a piece of ground",
+        "In your home town",
+        "Waiting for someone or something",
+        "To show you the way",
+        "Tired of lying in the sunshine",
+        "Staying home to watch the rain",
+        "You are young and life is long",
+        "And there is time to kill today",
+        "And then the one day you find",
+        "Ten years have got behind you",
+        "No one told you when to run",
+        "You missed the starting gun",
+        "And you run and you run",
+        "To catch up with the sun",
+        "But it's sinking",
+        "Racing around",
+        "To come up behind you again",
+        "The sun is the same",
+        "In a relative way",
+        "But you're older",
+        "Shorter of breath",
+        "And one day closer to death",
+        "Every year is getting shorter",
+        "Never seem to find the time",
+        "Plans that either come to naught",
+        "Or half a page of scribbled lines",
+        "Hanging on in quiet desperation",
+        "Is the English way",
+        "The time is gone",
+        "The song is over",
+        "Thought I'd something more to say",
+        "Home",
+        "Home again",
+        "I like to be here",
+        "When I can",
+        "When I come home",
+        "Cold and tired",
+        "It's good to warm my bones",
+        "Beside the fire",
+        "Far away",
+        "Across the field",
+        "Tolling on the iron bell",
+        "Calls the faithful to their knees",
+        "To hear the softly spoken magic spell...",
+        "test #1",
+        "test #2",
+        "this is a test #3",
+        "message #4",
+        "message #5",
+        "test message #6",
+        "a test #7",
+        "the quick brown (#8)",
+        "fox (#9)",
+        "jumps over the (#10)",
+        "lazy dog (#11)",
+        "this is another test message (#12)",
+        "and here is one more for you (#13)",
+        "foo (#14)",
+        "bar (#15)",
+        "foobar (#16)",
+        "barfoo (#17)",
+        "xyzzykukkuluuruu (#18)"
+    };
+
+    char          *msg, *p;
+    uint32_t       size, nbo_size;
+    size_t         n, total;
+    int            dump, chk, offs, i, j;
+
+    dump = chk = offs = 0;
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(messages); i++) {
+        msg   = messages[i];
+        size  = strlen(msg);
+
+        total = 0;
+        p     = msg;
+
+        if (ctx.framed) {
+            nbo_size = htobe32(size);
+            if (!mrp_fragbuf_push(buf, &nbo_size, sizeof(nbo_size)))
+                fatal("failed to push message size to buffer");
+        }
+
+        for (j = 0; *p != '\0'; j++) {
+            if (!chunks[j])
+                j = 0;
+            n = chunks[j];
+            if (n > strlen(p))
+                n = strlen(p);
+
+            mrp_debug("pushing %zd bytes (%*.*s)...", n, (int)n, (int)n, p);
+
+            if (!mrp_fragbuf_push(buf, p, n))
+                fatal("failed to push %*.*s to buffer", (int)n, (int)n, p);
+
+            p     += n;
+            total += n;
+
+            dump++;
+
+            if (!dump_interval ||
+                (dump_interval > 0 && !(dump % dump_interval)))
+                dump_buffer(buf, messages, &chk, &offs);
+        }
+
+        if (dump_interval < -1) {
+            if (i && !(i % -dump_interval))
+                dump_buffer(buf, messages, &chk, &offs);
+        }
+    }
+
+    dump_buffer(buf, messages, &chk, &offs);
+
+    return TRUE;
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        printf("\n");
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -n, --non-framed               set buffer to non-framed mode\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(void)
+{
+    mrp_clear(&ctx);
+    ctx.log_mask   = MRP_LOG_UPTO(MRP_LOG_INFO);
+    ctx.log_target = MRP_LOG_TO_STDOUT;
+    ctx.framed     = TRUE;
+}
+
+
+void parse_cmdline(int argc, char **argv)
+{
+#   define OPTIONS "l:t:vd:nh"
+    struct option options[] = {
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "non-framed", no_argument      , NULL, 'n' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    config_set_defaults();
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'v':
+            ctx.log_mask <<= 1;
+            ctx.log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx.log_mask = mrp_log_parse_levels(optarg);
+            if (ctx.log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx.log_target = mrp_log_parse_target(optarg);
+            if (!ctx.log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            ctx.log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case'n':
+            ctx.framed = FALSE;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        case '?':
+            if (opterr)
+                print_usage(argv[0], EINVAL, "");
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+}
+
+int main(int argc, char *argv[])
+{
+    mrp_fragbuf_t *buf;
+    size_t         chunkstbl[][8] = {
+        { 3, 1, 2, 3, 5, 0, 0, 0 },
+        { 1, 2, 3, 4, 3, 2, 1, 0 },
+        { 1, 5, 3, 4, 2, 1, 1, 0 },
+        { 4, 3, 2, 1, 2, 3, 4, 0 },
+    };
+    size_t *chunks;
+    size_t  single[]    = { 1, 0 };
+    int     intervals[] = { 1, 2, 3, 4, 5, 0, -1 };
+    int     i, j, interval;
+
+    parse_cmdline(argc, argv);
+
+    mrp_log_set_mask(ctx.log_mask);
+    mrp_log_set_target(ctx.log_target);
+
+    buf = mrp_fragbuf_create(ctx.framed, 0);
+
+    if (buf == NULL)
+        fatal("failed to create data collecting buffer");
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(intervals); i++) {
+        interval = intervals[i];
+        for (j = 0; j < (int)MRP_ARRAY_SIZE(chunkstbl); j++) {
+            chunks = &chunkstbl[j][0];
+            mrp_log_info("testing with interval %d, chunks #%d", interval, j);
+            test(buf, chunks, interval);
+            test(buf, single, interval);
+            mrp_log_info("testing with interval %d, chunks #%d", -i -2, j);
+            test(buf, chunks, -i - 2);
+            test(buf, single, -i - 2);
+        }
+    }
+
+    mrp_fragbuf_destroy(buf);
+
+    return 0;
+}
diff --git a/src/common/tests/glib-pump.c b/src/common/tests/glib-pump.c
new file mode 100644 (file)
index 0000000..ece07ff
--- /dev/null
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <glib.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+
+
+/*
+ * A simple glue layer to pump GMainLoop from mrp_mainloop_t. This
+ * will pretty much be turned into a murphy plugin as such...
+ */
+
+
+typedef struct {
+    GMainLoop     *ml;
+    GMainContext  *mc;
+    gint           maxprio;
+    mrp_subloop_t *sl;
+} glib_glue_t;
+
+static glib_glue_t *glib_glue;
+
+
+static int glib_prepare(void *user_data)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    return g_main_context_prepare(glue->mc, &glue->maxprio);
+}
+
+
+static int glib_query(void *user_data, struct pollfd *fds, int nfd,
+                      int *timeout)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    return g_main_context_query(glue->mc, glue->maxprio, timeout,
+                                (GPollFD *)fds, nfd);
+}
+
+
+static int glib_check(void *user_data, struct pollfd *fds, int nfd)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    return g_main_context_check(glue->mc, glue->maxprio, (GPollFD *)fds, nfd);
+
+}
+
+
+static void glib_dispatch(void *user_data)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    g_main_context_dispatch(glue->mc);
+
+}
+
+
+static int glib_pump_setup(mrp_mainloop_t *ml)
+{
+    static mrp_subloop_ops_t glib_ops = {
+        .prepare  = glib_prepare,
+        .query    = glib_query,
+        .check    = glib_check,
+        .dispatch = glib_dispatch
+    };
+
+    GMainContext *main_context;
+    GMainLoop    *main_loop;
+
+    if (sizeof(GPollFD) != sizeof(struct pollfd)) {
+        mrp_log_error("sizeof(GPollFD:%zd) != sizeof(struct pollfd:%zd)\n",
+                      sizeof(GPollFD), sizeof(struct pollfd));
+        return FALSE;
+    }
+
+    main_context = NULL;
+    main_loop    = NULL;
+    glib_glue    = NULL;
+
+    if ((main_context = g_main_context_default())             != NULL &&
+        (main_loop    = g_main_loop_new(main_context, FALSE)) != NULL &&
+        (glib_glue    = mrp_allocz(sizeof(*glib_glue)))       != NULL) {
+
+        glib_glue->mc = main_context;
+        glib_glue->ml = main_loop;
+        glib_glue->sl = mrp_add_subloop(ml, &glib_ops, glib_glue);
+
+        if (glib_glue->sl != NULL)
+            return TRUE;
+        else
+            mrp_log_error("glib-pump failed to register subloop.");
+    }
+
+    /* all of these handle a NULL argument gracefully... */
+    g_main_loop_unref(main_loop);
+    g_main_context_unref(main_context);
+
+    mrp_free(glib_glue);
+    glib_glue = NULL;
+
+    return FALSE;
+}
+
+
+static void glib_pump_cleanup(void)
+{
+    if (glib_glue != NULL) {
+        mrp_del_subloop(glib_glue->sl);
+
+        g_main_loop_unref(glib_glue->ml);
+        g_main_context_unref(glib_glue->mc);
+
+        mrp_free(glib_glue);
+        glib_glue = NULL;
+    }
+}
+
diff --git a/src/common/tests/hash-test.c b/src/common/tests/hash-test.c
new file mode 100644 (file)
index 0000000..6195775
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/macros.h>
+#include <murphy/common/hashtbl.h>
+
+#define MEMBER_OFFSET MRP_OFFSET
+#define ALLOC_ARR(type, n) mrp_allocz(sizeof(type) * (n))
+#define FREE               mrp_free
+#define STRDUP             mrp_strdup
+
+#define hash_tbl_t      mrp_htbl_t
+#define hash_tbl_cfg_t  mrp_htbl_config_t
+#define hash_tbl_create mrp_htbl_create
+#define hash_tbl_delete mrp_htbl_destroy
+#define hash_tbl_add    mrp_htbl_insert
+#define hash_tbl_del    mrp_htbl_remove
+#define hash_tbl_lookup mrp_htbl_lookup
+
+#define list_hook_t     mrp_list_hook_t
+#define list_init       mrp_list_init
+#define list_append     mrp_list_append
+#define list_delete     mrp_list_delete
+
+#define NKEY   4
+#define NPHASE 0xff
+
+#define INFO(fmt, args...)  do {                         \
+        printf("[%s] "fmt"\n" , __FUNCTION__ , ## args); \
+        fflush(stdout);                                  \
+    } while (0)
+
+#define ERROR(fmt, args...) do {                                \
+        printf("[%s] error: "fmt"\n" , __FUNCTION__, ## args);  \
+        fflush(stdout);                                         \
+    } while (0)
+
+#define FATAL(fmt, args...) do {                                        \
+        printf("[%s] fatal error: "fmt"\n" , __FUNCTION__, ## args);    \
+        fflush(stdout);                                                 \
+        exit(1);                                                        \
+    } while (0)
+
+#define MKSTR(fmt, args...) ({                                  \
+            char *_ptr, _buf[64] = "";                          \
+            snprintf(_buf, sizeof(_buf), fmt , ## args);        \
+            _ptr = STRDUP(_buf);                                \
+            _ptr; })
+
+#define ENTRY_KEY(entry, idx) ({                        \
+        char *_key;                                     \
+        switch ((idx)) {                                \
+        case 0:  _key = (entry)->str1; break;           \
+        case 1:  _key = (entry)->str2; break;           \
+        case 2:  _key = (entry)->str3; break;           \
+        case 3:  _key = (entry)->str4; break;           \
+        default: FATAL("invalid key idx %d", (idx));    \
+        }                                               \
+        _key; })
+
+#define PATTERN_BIT(pattern, idx)                       \
+    (pattern & (1 << ((idx) & ((sizeof(pattern) * 8) - 1))))
+
+typedef struct {
+    char        *str1;
+    int          int1;
+    char        *str2;
+    list_hook_t  hook;
+    char        *str3;
+    int          int2;
+    char        *str4;
+} entry_t;
+
+
+typedef struct {
+    hash_tbl_t *ht;
+    size_t      size;
+
+    entry_t    *entries;
+    int         nentry;
+
+    int         keyidx;
+    uint32_t    pattern;
+} test_t;
+
+
+test_t test;
+
+void
+populate(void)
+{
+    entry_t *entry;
+    char    *key;
+    int      i;
+
+    INFO("populating...");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        key = ENTRY_KEY(entry, test.keyidx);
+
+        if (hash_tbl_add(test.ht, key, entry))
+            INFO("hashed in entry '%s'", key);
+        else
+            FATAL("failed to hash in entry '%s'", key);
+    }
+
+    INFO("done.");
+}
+
+
+void
+evict(void)
+{
+    entry_t *entry, *found;
+    char    *key;
+    int      i;
+
+    INFO("evicting...");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        if (PATTERN_BIT(test.pattern, i)) {
+            key   = ENTRY_KEY(entry, test.keyidx);
+            found = hash_tbl_del(test.ht, key, FALSE);
+
+            if (found != entry)
+                FATAL("expected entry to delete '%s' not found (%p != %p)",
+                      key, found, entry);
+
+            INFO("removed entry '%s' (%p)", key, found);
+        }
+    }
+
+    INFO("done.");
+}
+
+
+void
+readd(void)
+{
+    entry_t *entry, *found;
+    char    *key;
+    int      i;
+
+    INFO("re-adding...");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        if (PATTERN_BIT(test.pattern, i)) {
+            key   = ENTRY_KEY(entry, test.keyidx);
+            found = hash_tbl_lookup(test.ht, key);
+
+            if (found != NULL)
+                FATAL("unexpected entry to re-add '%s' found (%p)", key, found);
+
+            if (!hash_tbl_add(test.ht, key, entry))
+                FATAL("failed to re-add entry '%s'", key);
+
+            INFO("re-added entry '%s'", key);
+        }
+    }
+
+    INFO("done.");
+}
+
+
+void
+check(void)
+{
+    entry_t *entry, *found;
+    char    *key;
+    int      i;
+
+    INFO("checking...");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        key   = ENTRY_KEY(entry, test.keyidx);
+        found = hash_tbl_lookup(test.ht, key);
+
+        if (!PATTERN_BIT(test.pattern, i)) {
+            if (found != entry)
+                FATAL("expected entry '%s' not found (%p != %p)",
+                      key, found, entry);
+        }
+        else {
+            if (found != NULL)
+                FATAL("unexpected entry '%s' found", key);
+        }
+    }
+
+    INFO("done.");
+}
+
+
+void
+empty_cb(char *key, entry_t *entry, void *data)
+{
+    (void)data;
+
+    FATAL("unexpected entry %p (%s) in hash table", entry, key);
+}
+
+
+void
+reset(void)
+{
+    entry_t *entry, *found;
+    char    *key;
+    int      i;
+
+    INFO("resetting...");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        key   = ENTRY_KEY(entry, test.keyidx);
+        found = hash_tbl_del(test.ht, key, FALSE);
+
+        if (found != entry)
+            FATAL("expected entry %s not found (%p != %p)",
+                  key, found, entry);
+
+        INFO("removed entry '%s' (%p)", key, found);
+    }
+
+    INFO("done.");
+}
+
+
+unsigned int hash_func(const void *key)
+{
+    unsigned int  h;
+    const char   *p;
+
+    for (h = 0, p = key; *p; p++) {
+        h <<= 1;
+        h  ^= *p;
+    }
+
+    return h;
+}
+
+
+int cmp_func(const void *key1, const void *key2)
+{
+    return strcmp(key1, key2);
+}
+
+
+void
+test_init(void)
+{
+    int      i;
+    entry_t *entry;
+
+    INFO("setting up tests...");
+
+    if ((test.entries = ALLOC_ARR(entry_t, test.nentry)) == NULL)
+        FATAL("failed to allocate test set");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        list_init(&entry->hook);
+
+        entry->str1 = MKSTR("entry-string-%d:1", i);
+        entry->int1 = i;
+        entry->str2 = MKSTR("entry-string-%d:2", i);
+        entry->str3 = MKSTR("entry-string-%d:3", i);
+        entry->int2 = i * 2;
+        entry->str4 = MKSTR("entry-string-%d:4", i);
+
+        if (!entry->str1 || !entry->str2 || !entry->str3 || !entry->str4)
+            FATAL("failed to initialize test set");
+    }
+
+    INFO("test setup done.");
+}
+
+
+void
+test_exit(void)
+{
+    entry_t *entry;
+    int      i;
+
+    INFO("cleaning up tests...");
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        FREE(entry->str1);
+        FREE(entry->str2);
+        FREE(entry->str3);
+        FREE(entry->str4);
+    }
+
+    FREE(test.entries);
+
+    test.entries = NULL;
+    test.nentry  = 0;
+
+    INFO("test cleanup done.");
+}
+
+
+void
+test_run(void)
+{
+    hash_tbl_cfg_t  cfg;
+    entry_t        *entry;
+    int             i, j;
+
+
+    /*
+     * Create a hash table, run a test loop consisting of
+     *
+     *   1) populate table
+     *   2) selectively remove entries
+     *   3) check the table
+     *   4) check the entries (for corruption)
+     *   5) reset the table
+     *
+     * then delete the hash table
+     */
+
+    cfg.nbucket = test.size / 4;
+    cfg.hash    = hash_func;
+    cfg.comp    = cmp_func;
+    cfg.free    = NULL;
+    test.ht     = hash_tbl_create(&cfg);
+
+    if (test.ht == NULL)
+        FATAL("failed to create hash table (#%d, size %zd)",
+              test.keyidx, test.size);
+
+    for (i = 0, entry = test.entries; i < test.nentry; i++, entry++) {
+        populate();
+
+        test.pattern = 0;
+        for (j = 0; j < NPHASE; j++) {
+            INFO("Running test phase #%d...", j);
+
+            evict();
+            check();
+            readd();
+
+            test.pattern++;
+
+            INFO("done.");
+        }
+
+        reset();
+    }
+
+    hash_tbl_delete(test.ht, FALSE);
+    test.ht = NULL;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+    int i;
+
+    memset(&test, 0, sizeof(test));
+
+    if (argc < 2 || (test.nentry = (int)strtoul(argv[1], NULL, 10)) <= 16)
+        test.nentry = 16;
+
+    test_init();
+
+    for (i = 0; i < NKEY; i++) {
+        test.keyidx = i;
+        test.size = test.nentry;     test_run();
+        test.size = test.nentry / 2; test_run();
+        test.size = test.nentry / 4; test_run();
+    }
+
+    test_exit();
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim:set expandtab shiftwidth=4:
+ */
+
diff --git a/src/common/tests/hash12-test.c b/src/common/tests/hash12-test.c
new file mode 100644 (file)
index 0000000..667c212
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdbool.h>
+#include <murphy/common.h>
+
+#define LE_STRING "/org/murphy/resource/0/%d"
+
+// Placeholder structure
+typedef struct {
+    void *pointer;
+} test_object;
+
+static void htbl_free_test_object(void *key, void *object) {
+    test_object *obj = object;
+
+    if (key)
+        mrp_free(key);
+
+    if (obj)
+        mrp_free(obj);
+}
+
+int main(int argc, char *argv[]) {
+    mrp_htbl_config_t cfg;
+
+    mrp_htbl_t  *table  = NULL;
+    test_object *object = NULL;
+
+    char  *string      = NULL;
+    size_t string_size = 0;
+    int written_count  = 0;
+
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    cfg.comp    = mrp_string_comp;
+    cfg.hash    = mrp_string_hash;
+    cfg.free    = htbl_free_test_object;
+    cfg.nbucket = 0; // nentry/4 -> smaller than min -> 8 by def
+    cfg.nentry  = 10;
+
+    table = mrp_htbl_create(&cfg);
+    if (!table) {
+        printf("blergh @ creating initial hash table\n");
+        return 1;
+    }
+
+    // broken range: 12 - 66
+    for (int i = 0; i < 12; i++) {
+        object = mrp_allocz(sizeof(test_object));
+        if (!object) {
+            printf("blergh @ allocating object %d\n", i);
+            return 1;
+        }
+        // allocz should handle this, but let's just have a test value written there
+        object->pointer = NULL;
+
+        string_size = snprintf(NULL, 0, LE_STRING, i);
+        if (!string_size) {
+            printf("blergh @ calculating string %d size\n", i);
+            return 1;
+        }
+        // we need the null character as well
+        string_size++;
+
+        string = mrp_allocz(string_size);
+        if (!string) {
+            printf("blergh @ allocating string %d\n", i);
+            return 1;
+        }
+
+        written_count = snprintf(string, string_size, LE_STRING, i);
+        if (written_count <= 0 || written_count + 1 < (int)string_size) {
+            printf("blergh @ writing string %d\n", i);
+            return 1;
+        }
+
+        mrp_htbl_insert(table, string, object);
+        mrp_htbl_remove(table, string, TRUE);
+    }
+
+    mrp_htbl_destroy(table, TRUE);
+    printf("Successfully finished the test\n");
+    return 0;
+}
diff --git a/src/common/tests/internal-transport-test.c b/src/common/tests/internal-transport-test.c
new file mode 100644 (file)
index 0000000..e4fa131
--- /dev/null
@@ -0,0 +1,784 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+
+
+/*
+ * tags for generic message fields
+ */
+
+#define TAG_SEQ   ((uint16_t)0x1)
+#define TAG_MSG   ((uint16_t)0x2)
+#define TAG_U8    ((uint16_t)0x3)
+#define TAG_S8    ((uint16_t)0x4)
+#define TAG_U16   ((uint16_t)0x5)
+#define TAG_S16   ((uint16_t)0x6)
+#define TAG_DBL   ((uint16_t)0x7)
+#define TAG_BLN   ((uint16_t)0x8)
+#define TAG_ASTR  ((uint16_t)0x9)
+#define TAG_AU32  ((uint16_t)0xa)
+#define TAG_RPL   ((uint16_t)0xb)
+#define TAG_END   MRP_MSG_FIELD_END
+
+#define U32_GUARD (uint32_t)-1
+
+/*
+ * our test custom data type
+ */
+
+#define TAG_CUSTOM 0x1
+
+typedef struct {
+    uint32_t   seq;
+    char      *msg;
+    uint8_t     u8;
+    int8_t      s8;
+    uint16_t   u16;
+    int16_t    s16;
+    double     dbl;
+    bool       bln;
+    char     **astr;
+    uint32_t   nstr;
+    uint32_t   fsck;
+    uint32_t  *au32;
+    char      *rpl;
+} custom_t;
+
+
+MRP_DATA_DESCRIPTOR(custom_descr, TAG_CUSTOM, custom_t,
+                    MRP_DATA_MEMBER(custom_t,  seq, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t,  msg, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t,   u8, MRP_MSG_FIELD_UINT8 ),
+                    MRP_DATA_MEMBER(custom_t,   s8, MRP_MSG_FIELD_SINT8 ),
+                    MRP_DATA_MEMBER(custom_t,  u16, MRP_MSG_FIELD_UINT16),
+                    MRP_DATA_MEMBER(custom_t,  s16, MRP_MSG_FIELD_SINT16),
+                    MRP_DATA_MEMBER(custom_t,  dbl, MRP_MSG_FIELD_DOUBLE),
+                    MRP_DATA_MEMBER(custom_t,  bln, MRP_MSG_FIELD_BOOL  ),
+                    MRP_DATA_MEMBER(custom_t,  rpl, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_ARRAY_COUNT(custom_t, astr, nstr,
+                                         MRP_MSG_FIELD_STRING),
+                    MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD,
+                                         MRP_MSG_FIELD_UINT32));
+
+MRP_DATA_DESCRIPTOR(buggy_descr, TAG_CUSTOM, custom_t,
+                    MRP_DATA_MEMBER(custom_t,  seq, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t,  msg, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t,   u8, MRP_MSG_FIELD_UINT8 ),
+                    MRP_DATA_MEMBER(custom_t,   s8, MRP_MSG_FIELD_SINT8 ),
+                    MRP_DATA_MEMBER(custom_t,  u16, MRP_MSG_FIELD_UINT16),
+                    MRP_DATA_MEMBER(custom_t,  s16, MRP_MSG_FIELD_SINT16),
+                    MRP_DATA_MEMBER(custom_t,  dbl, MRP_MSG_FIELD_DOUBLE),
+                    MRP_DATA_MEMBER(custom_t,  bln, MRP_MSG_FIELD_BOOL  ),
+                    MRP_DATA_MEMBER(custom_t,  rpl, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_ARRAY_COUNT(custom_t, astr, fsck,
+                                         MRP_MSG_FIELD_STRING),
+                    MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD,
+                                         MRP_MSG_FIELD_UINT32));
+
+mrp_data_descr_t *data_descr;
+
+typedef struct {
+    mrp_mainloop_t  *ml;
+    mrp_transport_t *lt, *st;
+    char            *addrstr;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *atype;
+    int              server;
+    int              sock;
+    mrp_io_watch_t  *iow;
+    mrp_timer_t     *timer;
+    int              custom;
+    int              buggy;
+    int              connect;
+    int              stream;
+    int              log_mask;
+    const char      *log_target;
+    uint32_t         seqno;
+    mrp_list_hook_t  clients;
+} context_t;
+
+typedef struct {
+    int              id;
+    mrp_transport_t *t;
+    context_t       *c;
+    mrp_list_hook_t  hook;
+} client_t;
+
+
+void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data);
+void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data);
+
+void recv_custom(mrp_transport_t *t, void *data, uint16_t tag, void *user_data);
+void recvfrom_custom(mrp_transport_t *t, void *data, uint16_t tag,
+                     mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data);
+
+
+
+void dump_msg(mrp_msg_t *msg, FILE *fp)
+{
+    mrp_msg_dump(msg, fp);
+}
+
+
+void srv_recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data)
+{
+    context_t       *c = (context_t *)user_data;
+    mrp_msg_field_t *f;
+    uint32_t         seq;
+    char             buf[256];
+    int              status;
+
+    mrp_log_info("received a message");
+    dump_msg(msg, stdout);
+
+    seq = 0;
+    if ((f = mrp_msg_find(msg, TAG_SEQ)) != NULL) {
+        if (f->type == MRP_MSG_FIELD_UINT32)
+            seq = f->u32;
+    }
+
+    snprintf(buf, sizeof(buf), "reply to message #%u", seq);
+
+    if (!mrp_msg_append(msg, TAG_RPL, MRP_MSG_FIELD_STRING, buf,
+                        TAG_END)) {
+        mrp_log_info("failed to append to received message");
+        exit(1);
+    }
+
+    if (c->connect)
+        status = mrp_transport_send(t, msg);
+    else
+        status = mrp_transport_sendto(t, msg, addr, addrlen);
+
+    if (status)
+        mrp_log_info("reply successfully sent");
+    else
+        mrp_log_error("failed to send reply");
+
+    /* message unreffed by transport layer */
+}
+
+
+void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data)
+{
+    MRP_UNUSED(t);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+    MRP_UNUSED(user_data);
+
+    mrp_log_info("client received a message");
+    dump_msg(msg, stdout);
+}
+
+
+void srv_recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    return srv_recvfrom_msg(t, msg, NULL, 0, user_data);
+}
+
+
+void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    return recvfrom_msg(t, msg, NULL, 0, user_data);
+}
+
+
+void dump_custom(custom_t *msg, FILE *fp)
+{
+    uint32_t i;
+
+    mrp_data_dump(msg, data_descr, fp);
+    fprintf(fp, "{\n");
+    fprintf(fp, "    seq = %u\n"  , msg->seq);
+    fprintf(fp, "    msg = '%s'\n", msg->msg);
+    fprintf(fp, "     u8 = %u\n"  , msg->u8);
+    fprintf(fp, "     s8 = %d\n"  , msg->s8);
+    fprintf(fp, "    u16 = %u\n"  , msg->u16);
+    fprintf(fp, "    s16 = %d\n"  , msg->s16);
+    fprintf(fp, "    dbl = %f\n"  , msg->dbl);
+    fprintf(fp, "    bln = %s\n"  , msg->bln ? "true" : "false");
+    fprintf(fp, "   astr = (%u)\n", msg->nstr);
+    for (i = 0; i < msg->nstr; i++)
+        fprintf(fp, "           %s\n", msg->astr[i]);
+    fprintf(fp, "   au32 =\n");
+    for (i = 0; msg->au32[i] != U32_GUARD; i++)
+        fprintf(fp, "           %u\n", msg->au32[i]);
+    fprintf(fp, "    rpl = '%s'\n", msg->rpl);
+    fprintf(fp, "}\n");
+}
+
+
+void free_custom(custom_t *msg)
+{
+    mrp_data_free(msg, data_descr->tag);
+}
+
+
+
+void srv_recvfrom_custom(mrp_transport_t *t, void *data, uint16_t tag,
+                     mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    context_t *c   = (context_t *)user_data;
+    custom_t  *msg = (custom_t *)data;
+    custom_t   rpl;
+    char       buf[256];
+    uint32_t   au32[] = { 9, 8, 7, 6, 5, -1 };
+    int        status;
+
+    mrp_log_info("server received custom message of type 0x%x", tag);
+    dump_custom(data, stdout);
+
+    if (tag != data_descr->tag) {
+        mrp_log_error("Tag 0x%x != our custom type (0x%x).",
+                      tag, data_descr->tag);
+        exit(1);
+    }
+
+    rpl = *msg;
+    snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq);
+    rpl.rpl  = buf;
+    rpl.au32 = au32;
+
+    if (c->connect)
+        status = mrp_transport_senddata(t, &rpl, data_descr->tag);
+    else
+        status = mrp_transport_senddatato(t, &rpl, data_descr->tag,
+                                          addr, addrlen);
+    if (status)
+        mrp_log_info("reply successfully sent");
+    else
+        mrp_log_error("failed to send reply");
+
+    free_custom(msg);
+}
+
+void recvfrom_custom(mrp_transport_t *t, void *data, uint16_t tag,
+                     mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    custom_t  *msg = (custom_t *)data;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(data);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+    MRP_UNUSED(user_data);
+
+    mrp_log_info("received custom message of type 0x%x", tag);
+    dump_custom(data, stdout);
+
+    if (tag != data_descr->tag) {
+        mrp_log_error("Tag 0x%x != our custom type (0x%x).",
+                      tag, data_descr->tag);
+        exit(1);
+    }
+
+    free_custom(msg);
+}
+
+
+void recv_custom(mrp_transport_t *t, void *data, uint16_t tag, void *user_data)
+{
+    recvfrom_custom(t, data, tag, NULL, 0, user_data);
+}
+
+void srv_recv_custom(mrp_transport_t *t, void *data, uint16_t tag, void *user_data)
+{
+    srv_recvfrom_custom(t, data, tag, NULL, 0, user_data);
+}
+
+void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(c);
+
+    if (error) {
+        mrp_log_error("Connection closed with error %d (%s).", error,
+                      strerror(error));
+        exit(1);
+    }
+    else {
+        mrp_log_info("Peer has closed the connection.");
+        exit(0);
+    }
+}
+
+
+void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    int        flags;
+
+    mrp_log_info("connection event!");
+
+    flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+    c->st = mrp_transport_accept(lt, c, flags);
+
+    if (c->st == NULL) {
+        mrp_log_error("Failed to accept new connection.");
+        exit(1);
+    }
+}
+
+
+void type_init(context_t *c)
+{
+    if (c->buggy && c->server) {
+        data_descr = &buggy_descr;
+        mrp_log_info("Deliberately using buggy data descriptor...");
+    }
+    else
+        data_descr = &custom_descr;
+
+    if (!mrp_msg_register_type(data_descr)) {
+        mrp_log_error("Failed to register custom data type.");
+        exit(1);
+    }
+}
+
+
+void server_init(context_t *c)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = NULL },
+        { .recvmsgfrom = NULL },
+        .closed        = closed_evt,
+        .connection    = connection_evt
+    };
+
+    int flags;
+
+    if (c->custom) {
+        evt.recvdata     = srv_recv_custom;
+        evt.recvdatafrom = srv_recvfrom_custom;
+    }
+    else {
+        evt.recvmsg     = srv_recv_msg;
+        evt.recvmsgfrom = srv_recvfrom_msg;
+    }
+
+
+    flags = MRP_TRANSPORT_REUSEADDR |
+        (c->custom ? MRP_TRANSPORT_MODE_DATA : 0);
+    c->lt = mrp_transport_create(c->ml, c->atype, &evt, c, flags);
+
+    if (c->lt == NULL) {
+        mrp_log_error("Failed to create listening server transport.");
+        exit(1);
+    }
+
+    if (!mrp_transport_bind(c->lt, &c->addr, c->alen)) {
+        mrp_log_error("Failed to bind transport to address %s.", c->addrstr);
+        exit(1);
+    }
+
+    if (c->stream) {
+        if (!mrp_transport_listen(c->lt, 0)) {
+            mrp_log_error("Failed to listen on server transport.");
+            exit(1);
+        }
+    }
+}
+
+
+void send_msg(client_t *client)
+{
+    mrp_msg_t *msg;
+    uint32_t   seq;
+    char       buf[256];
+    char      *astr[] = { "this", "is", "an", "array", "of", "strings" };
+    uint32_t   au32[] = { 1, 2, 3,
+                          1 << 16, 2 << 16, 3 << 16,
+                          1 << 24, 2 << 24, 3 << 24 };
+    uint32_t   nstr = MRP_ARRAY_SIZE(astr);
+    uint32_t   nu32 = MRP_ARRAY_SIZE(au32);
+    int        status;
+    context_t *c = client->c;
+
+    seq = c->seqno++;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+
+    msg = mrp_msg_create(TAG_SEQ , MRP_MSG_FIELD_UINT32, seq,
+                         TAG_MSG , MRP_MSG_FIELD_STRING, buf,
+                         TAG_U8  , MRP_MSG_FIELD_UINT8 ,   seq & 0xf,
+                         TAG_S8  , MRP_MSG_FIELD_SINT8 , -(seq & 0xf),
+                         TAG_U16 , MRP_MSG_FIELD_UINT16,   seq,
+                         TAG_S16 , MRP_MSG_FIELD_SINT16, - seq,
+                         TAG_DBL , MRP_MSG_FIELD_DOUBLE, seq / 3.0,
+                         TAG_BLN , MRP_MSG_FIELD_BOOL  , seq & 0x1,
+                         TAG_ASTR, MRP_MSG_FIELD_ARRAY_OF(STRING), nstr, astr,
+                         TAG_AU32, MRP_MSG_FIELD_ARRAY_OF(UINT32), nu32, au32,
+                         TAG_END);
+
+    if (msg == NULL) {
+        mrp_log_error("Failed to create new message.");
+        exit(1);
+    }
+
+    if (c->connect)
+        status = mrp_transport_send(client->t, msg);
+    else
+        status = mrp_transport_sendto(client->t, msg, &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", seq);
+
+    mrp_msg_unref(msg);
+}
+
+
+void send_custom(client_t *client)
+{
+    custom_t   msg;
+    char       buf[256];
+    char      *astr[] = { "this", "is", "a", "test", "string", "array" };
+    uint32_t   au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 };
+    int        status;
+    context_t *c = client->c;
+    uint32_t   seq = c->seqno++;
+
+    msg.seq = seq;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+    msg.msg  = buf;
+    msg.u8   =   seq & 0xf;
+    msg.s8   = -(seq & 0xf);
+    msg.u16  =   seq;
+    msg.s16  = - seq;
+    msg.dbl  =   seq / 3.0;
+    msg.bln  =   seq & 0x1;
+    msg.astr = astr;
+    msg.nstr = MRP_ARRAY_SIZE(astr);
+    msg.fsck = 1000;
+    msg.au32 = au32;
+    msg.rpl  = "";
+
+    if (c->connect)
+        status = mrp_transport_senddata(client->t, &msg, data_descr->tag);
+    else
+        status = mrp_transport_senddatato(client->t, &msg, data_descr->tag,
+                                          &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", msg.seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", msg.seq);
+}
+
+
+
+void send_cb(mrp_timer_t *t, void *user_data)
+{
+    client_t *client = (client_t *)user_data;
+    context_t *c = client->c;
+
+    MRP_UNUSED(t);
+
+    if (c->custom)
+        send_custom(client);
+    else
+        send_msg(client);
+}
+
+
+void client_init(context_t *c)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = NULL },
+        { .recvmsgfrom = NULL },
+        .closed        = closed_evt,
+        .connection    = NULL
+    };
+
+    int flags;
+    client_t *client;
+
+    if (c->custom) {
+        evt.recvdata     = recv_custom;
+        evt.recvdatafrom = recvfrom_custom;
+    }
+    else {
+        evt.recvmsg     = recv_msg;
+        evt.recvmsgfrom = recvfrom_msg;
+    }
+
+    client = mrp_allocz(sizeof(client_t));
+    mrp_list_init(&client->hook);
+    client->c = c;
+
+    mrp_list_append(&c->clients, &client->hook);
+
+    flags = c->custom ? MRP_TRANSPORT_MODE_DATA : 0;
+    client->t  = mrp_transport_create(c->ml, c->atype, &evt, client, flags);
+
+    if (client->t == NULL) {
+        mrp_log_error("Failed to create new transport.");
+        exit(1);
+    }
+
+    if (!strcmp(c->atype, "unxd")) {
+        char           addrstr[] = "unxd:@stream-test-client";
+        mrp_sockaddr_t addr;
+        socklen_t      alen;
+
+        alen = mrp_transport_resolve(NULL, addrstr, &addr, sizeof(addr), NULL);
+        if (alen <= 0) {
+            mrp_log_error("Failed to resolve transport address '%s'.", addrstr);
+            exit(1);
+        }
+
+        if (!mrp_transport_bind(client->t, &addr, alen)) {
+            mrp_log_error("Failed to bind to transport address '%s'.", addrstr);
+            exit(1);
+        }
+    }
+
+    if (c->connect) {
+        if (!mrp_transport_connect(client->t, &c->addr, c->alen)) {
+            mrp_log_error("Failed to connect to %s.", c->addrstr);
+            exit(1);
+        }
+    }
+
+
+    c->timer = mrp_add_timer(c->ml, 1000, send_cb, client);
+
+    if (c->timer == NULL) {
+        mrp_log_error("Failed to create send timer.");
+        exit(1);
+    }
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options] [transport-address]\n\n"
+           "The possible options are:\n"
+           "  -s, --server                   run as test server (default)\n"
+           "  -C, --connect                  connect transport\n"
+           "      For connection-oriented transports, this is automatic.\n"
+           "  -a, --address                  address to use\n"
+           "  -c, --custom                   use custom messages\n"
+           "  -m, --message                  use generic messages (default)\n"
+           "  -b, --buggy                    use buggy data descriptors\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->addrstr    = "tcp4:127.0.0.1:3000";
+    ctx->server     = FALSE;
+    ctx->custom     = FALSE;
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "scmbCa:l:t:vdh"
+    struct option options[] = {
+        { "server"    , no_argument      , NULL, 's' },
+        { "address"   , required_argument, NULL, 'a' },
+        { "custom"    , no_argument      , NULL, 'c' },
+        { "connect"   , no_argument      , NULL, 'C' },
+        { "message"   , no_argument      , NULL, 'm' },
+        { "buggy"     , no_argument      , NULL, 'b' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , no_argument      , NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int  opt, debug;
+
+    debug = FALSE;
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            ctx->server = TRUE;
+            break;
+
+        case 'c':
+            ctx->custom = TRUE;
+            break;
+
+        case 'm':
+            ctx->custom = FALSE;
+            break;
+
+        case 'b':
+            ctx->buggy = TRUE;
+            break;
+
+        case 'C':
+            ctx->connect = TRUE;
+            break;
+
+        case 'a':
+            ctx->addrstr = optarg;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            debug = TRUE;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    if (debug)
+        ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t c;
+
+    mrp_clear(&c);
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    mrp_log_info("Using address '%s'...", c.addrstr);
+
+    mrp_list_init(&c.clients);
+
+    if (c.custom)
+        mrp_log_info("Using custom messages...");
+    else
+        mrp_log_info("Using generic messages...");
+
+    if (!strncmp(c.addrstr, "tcp", 3) ||
+        !strncmp(c.addrstr, "unxs", 4)) {
+        c.stream  = TRUE;
+        c.connect = TRUE;
+    }
+
+    c.alen = mrp_transport_resolve(NULL, c.addrstr,
+                                   &c.addr, sizeof(c.addr), &c.atype);
+    if (c.alen <= 0) {
+        mrp_log_error("Failed to resolve transport address '%s'.", c.addrstr);
+        exit(1);
+    }
+
+    c.ml = mrp_mainloop_create();
+
+    type_init(&c);
+
+    server_init(&c);
+
+    client_init(&c);
+    client_init(&c);
+
+    mrp_mainloop_run(c.ml);
+
+    return 0;
+}
diff --git a/src/common/tests/libdbus-test.c b/src/common/tests/libdbus-test.c
new file mode 100644 (file)
index 0000000..1f68bc4
--- /dev/null
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+#include <murphy/common/dbus-libdbus.h>
+
+#define SERVER_NAME      "org.test.murphy-server"
+#define SERVER_PATH      "/server"
+#define SERVER_INTERFACE "Murphy.Server"
+#define PING             "ping"
+#define CLIENT_NAME      "org.test.murphy-client"
+#define CLIENT_PATH      "/client"
+#define CLIENT_INTERFACE "Murphy.Client"
+#define PONG             "pong"
+
+
+typedef struct {
+    char            *busaddr;
+    int              server;
+    int              log_mask;
+    const char      *log_target;
+    mrp_mainloop_t  *ml;
+    mrp_timer_t     *timer;
+    uint32_t         seqno;
+    mrp_dbus_t      *dbus;
+    const char      *name;
+    int32_t          cid;
+    int              server_up;
+    int              all_pongs;
+} context_t;
+
+
+static int ping_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data)
+{
+    context_t  *c = (context_t *)user_data;
+    uint32_t    seq;
+    const char *dest;
+
+    MRP_UNUSED(c);
+
+    if (mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_METHOD_CALL) {
+        if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq))
+            mrp_log_info("-> ping request #%u", seq);
+        else
+            mrp_log_error("-> malformed ping request");
+
+        if (!mrp_dbus_reply(dbus, msg,
+                            MRP_DBUS_TYPE_UINT32, &seq,
+                            MRP_DBUS_TYPE_INVALID))
+            mrp_log_error("Failed to send ping reply #%u.", seq);
+        else
+            mrp_log_info("<- ping reply #%u", seq);
+
+        if (seq & 0x1)
+            dest = mrp_dbus_msg_sender(msg);
+        else
+            dest = NULL;
+
+        if (!mrp_dbus_signal(dbus, dest, SERVER_PATH, SERVER_INTERFACE, PONG,
+                             MRP_DBUS_TYPE_UINT32, &seq,
+                             MRP_DBUS_TYPE_INVALID))
+            mrp_log_error("Failed to send pong signal #%u.", seq);
+        else
+            mrp_log_info("<- pong %s #%u", dest ? "signal" : "broadcast", seq);
+    }
+
+    return TRUE;
+}
+
+
+static void server_setup(context_t *c)
+{
+    c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL);
+
+    if (c->dbus == NULL) {
+        mrp_log_error("Failed to create D-BUS connection to '%s' bus.",
+                      c->busaddr);
+        exit(1);
+    }
+
+    c->name = mrp_dbus_get_unique_name(c->dbus);
+    mrp_log_info("Our address is %s on the bus...",
+                 c->name ? c->name : "unknown");
+
+    if (!mrp_dbus_acquire_name(c->dbus, SERVER_NAME, NULL)) {
+        mrp_log_error("Failed to acquire D-BUS name '%s' on bus '%s'.",
+                      SERVER_NAME, c->busaddr);
+        exit(1);
+    }
+
+    if (!mrp_dbus_export_method(c->dbus, SERVER_PATH, SERVER_INTERFACE,
+                                PING, ping_handler, c)) {
+        mrp_log_error("Failed to export D-BUS method '%s'.", PING);
+        exit(1);
+    }
+}
+
+
+void server_cleanup(context_t *c)
+{
+    mrp_dbus_release_name(c->dbus, SERVER_NAME, NULL);
+    mrp_dbus_remove_method(c->dbus, SERVER_PATH, SERVER_INTERFACE,
+                           PING, ping_handler, c);
+    mrp_dbus_unref(c->dbus);
+}
+
+
+static void ping_reply(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    uint32_t   seq;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(user_data);
+
+    if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq))
+        mrp_log_info("-> ping reply #%u", seq);
+    else
+        mrp_log_error("Received malformed ping reply.");
+
+    c->cid = 0;
+}
+
+
+static void ping_request(context_t *c)
+{
+    uint32_t seq;
+
+    if (c->cid != 0) {
+        mrp_log_warning("Previous ping request still unanswered...");
+        return;
+    }
+
+    seq    = c->seqno++;
+    c->cid = mrp_dbus_call(c->dbus,
+                           SERVER_NAME, SERVER_PATH, SERVER_INTERFACE,
+                           PING, 500, ping_reply, c,
+                           MRP_DBUS_TYPE_UINT32, &seq,
+                           MRP_DBUS_TYPE_INVALID);
+
+    if (c->cid > 0)
+        mrp_log_info("<- ping request #%u", seq);
+    else
+        mrp_log_warning("Failed to send ping request #%u.", seq);
+}
+
+
+static void send_cb(mrp_timer_t *t, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    ping_request(c);
+}
+
+
+static int pong_handler(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    uint32_t   seq;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(dbus);
+
+    if (mrp_dbus_msg_type(msg) == MRP_DBUS_MESSAGE_TYPE_SIGNAL) {
+        if (mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &seq))
+            mrp_log_info("-> pong signal #%u", seq);
+        else
+            mrp_log_error("-> malformed pong signal");
+    }
+
+    return TRUE;
+}
+
+
+static void server_status_cb(mrp_dbus_t *dbus, const char *name, int up,
+                             const char *owner, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(dbus);
+    MRP_UNUSED(name);
+
+    if (up) {
+        mrp_log_info("%s came up (as %s)", name, owner);
+
+        if (c->timer == NULL) {
+            c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+            if (c->timer == NULL) {
+                mrp_log_error("Failed to create D-BUS sending timer.");
+                exit(1);
+            }
+        }
+    }
+    else {
+        mrp_log_info("%s went down", name);
+
+        if (c->timer != NULL) {
+            mrp_del_timer(c->timer);
+            c->timer = NULL;
+        }
+    }
+}
+
+
+static void client_setup(context_t *c)
+{
+    const char *dest;
+
+    c->dbus = mrp_dbus_connect(c->ml, c->busaddr, NULL);
+
+    if (c->dbus == NULL) {
+        mrp_log_error("Failed to create D-BUS connection to '%s' bus.",
+                      c->busaddr);
+        exit(1);
+    }
+
+    c->name = mrp_dbus_get_unique_name(c->dbus);
+    mrp_log_info("Our address is %s on the bus...",
+                 c->name ? c->name : "unknown");
+
+    mrp_dbus_follow_name(c->dbus, SERVER_NAME, server_status_cb, c);
+
+    if (c->all_pongs) {
+        mrp_log_info("Subscribing for all pong signals...");
+        dest = NULL;
+    }
+    else {
+        mrp_log_info("Subscribing only for pong signals to us...");
+        dest = c->name;
+    }
+
+    if (!mrp_dbus_subscribe_signal(c->dbus, pong_handler, c,
+                                   dest, SERVER_PATH, SERVER_INTERFACE,
+                                   PONG, NULL)) {
+        mrp_log_error("Failed to subscribe for signal '%s/%s.%s'.", SERVER_PATH,
+                      SERVER_INTERFACE, PONG);
+        exit(1);
+    }
+
+    c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+    if (c->timer == NULL) {
+        mrp_log_error("Failed to create D-BUS sending timer.");
+        exit(1);
+    }
+}
+
+
+static void client_cleanup(context_t *c)
+{
+    mrp_dbus_follow_name(c->dbus, SERVER_NAME, server_status_cb, c);
+    mrp_del_timer(c->timer);
+    mrp_dbus_subscribe_signal(c->dbus, pong_handler, c,
+                              c->name, SERVER_PATH, SERVER_INTERFACE,
+                              PONG, NULL);
+    mrp_dbus_unref(c->dbus);
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -s, --server                   run as test server (default)\n"
+           "  -b, --bus                      connect the given D-BUS\n"
+           "      If omitted, defaults to the session bus.\n"
+           "  -a, --all-pongs                subscribe for all pong signals\n"
+           "      If omitted, only pong with the client address are handled.\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->busaddr    = "session";
+    ctx->server     = FALSE;
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "sab:l:t:vdh"
+    struct option options[] = {
+        { "server"    , no_argument      , NULL, 's' },
+        { "bus"       , required_argument, NULL, 'b' },
+        { "all-pongs" , no_argument      , NULL, 'a' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , no_argument      , NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int  opt, debug;
+
+    debug = FALSE;
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            ctx->server = TRUE;
+            break;
+
+        case 'b':
+            ctx->busaddr = optarg;
+            break;
+
+        case 'a':
+            ctx->all_pongs = TRUE;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            debug = TRUE;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    if (debug)
+        ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+
+    return TRUE;
+}
+
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h);
+    context_t      *c = (context_t *)user_data;
+
+    MRP_UNUSED(c);
+
+    switch (signum) {
+    case SIGINT:
+        mrp_log_info("Got SIGINT, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+
+    case SIGTERM:
+        mrp_log_info("Got SIGTERM, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t c;
+
+    mrp_clear(&c);
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.server)
+        mrp_log_info("Running as server, using D-BUS '%s'...", c.busaddr);
+    else
+        mrp_log_info("Running as client, using D-BUS '%s'...", c.busaddr);
+
+    c.ml = mrp_mainloop_create();
+
+    if (c.ml == NULL) {
+        mrp_log_error("Failed to create mainloop.");
+        exit(1);
+    }
+
+    mrp_add_sighandler(c.ml, SIGINT , signal_handler, &c);
+
+    if (c.server)
+        server_setup(&c);
+    else
+        client_setup(&c);
+
+    mrp_mainloop_run(c.ml);
+
+    if (c.server)
+        server_cleanup(&c);
+    else
+        client_cleanup(&c);
+
+    return 0;
+}
diff --git a/src/common/tests/libdbus-transport-test.c b/src/common/tests/libdbus-transport-test.c
new file mode 100644 (file)
index 0000000..06c2320
--- /dev/null
@@ -0,0 +1,847 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+
+/*
+ * tags for generic message fields
+ */
+
+#define TAG_SEQ   ((uint16_t)0x1)
+#define TAG_MSG   ((uint16_t)0x2)
+#define TAG_U8    ((uint16_t)0x3)
+#define TAG_S8    ((uint16_t)0x4)
+#define TAG_U16   ((uint16_t)0x5)
+#define TAG_S16   ((uint16_t)0x6)
+#define TAG_DBL   ((uint16_t)0x7)
+#define TAG_BLN   ((uint16_t)0x8)
+#define TAG_ASTR  ((uint16_t)0x9)
+#define TAG_AU32  ((uint16_t)0xa)
+#define TAG_RPL   ((uint16_t)0xb)
+#define TAG_END   MRP_MSG_FIELD_END
+
+#define U32_GUARD (uint32_t)-1
+
+/*
+ * our test custom data type
+ */
+
+#define TAG_CUSTOM 0x1
+
+typedef struct {
+    uint32_t   seq;
+    char      *msg;
+    uint8_t     u8;
+    int8_t      s8;
+    uint16_t   u16;
+    int16_t    s16;
+    double     dbl;
+    bool       bln;
+    char     **astr;
+    uint32_t   nstr;
+    uint32_t   fsck;
+    uint32_t  *au32;
+    char      *rpl;
+} custom_t;
+
+
+MRP_DATA_DESCRIPTOR(custom_descr, TAG_CUSTOM, custom_t,
+                    MRP_DATA_MEMBER(custom_t,  seq, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t,  msg, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t,   u8, MRP_MSG_FIELD_UINT8 ),
+                    MRP_DATA_MEMBER(custom_t,   s8, MRP_MSG_FIELD_SINT8 ),
+                    MRP_DATA_MEMBER(custom_t,  u16, MRP_MSG_FIELD_UINT16),
+                    MRP_DATA_MEMBER(custom_t,  s16, MRP_MSG_FIELD_SINT16),
+                    MRP_DATA_MEMBER(custom_t,  dbl, MRP_MSG_FIELD_DOUBLE),
+                    MRP_DATA_MEMBER(custom_t,  bln, MRP_MSG_FIELD_BOOL  ),
+                    MRP_DATA_MEMBER(custom_t,  rpl, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_ARRAY_COUNT(custom_t, astr, nstr,
+                                         MRP_MSG_FIELD_STRING),
+                    MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD,
+                                         MRP_MSG_FIELD_UINT32));
+
+MRP_DATA_DESCRIPTOR(buggy_descr, TAG_CUSTOM, custom_t,
+                    MRP_DATA_MEMBER(custom_t,  seq, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t,  msg, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t,   u8, MRP_MSG_FIELD_UINT8 ),
+                    MRP_DATA_MEMBER(custom_t,   s8, MRP_MSG_FIELD_SINT8 ),
+                    MRP_DATA_MEMBER(custom_t,  u16, MRP_MSG_FIELD_UINT16),
+                    MRP_DATA_MEMBER(custom_t,  s16, MRP_MSG_FIELD_SINT16),
+                    MRP_DATA_MEMBER(custom_t,  dbl, MRP_MSG_FIELD_DOUBLE),
+                    MRP_DATA_MEMBER(custom_t,  bln, MRP_MSG_FIELD_BOOL  ),
+                    MRP_DATA_MEMBER(custom_t,  rpl, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_ARRAY_COUNT(custom_t, astr, fsck,
+                                         MRP_MSG_FIELD_STRING),
+                    MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD,
+                                         MRP_MSG_FIELD_UINT32));
+
+mrp_data_descr_t *data_descr;
+
+
+typedef enum {
+    MODE_DEFAULT = 0,
+    MODE_MESSAGE = 1,
+    MODE_DATA    = 2,
+    MODE_RAW     = 3,
+} msg_mode_t;
+
+
+typedef struct {
+    mrp_mainloop_t  *ml;
+    mrp_transport_t *lt, *t;
+    char            *addrstr;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *atype;
+    int              server;
+    int              sock;
+    mrp_io_watch_t  *iow;
+    mrp_timer_t     *timer;
+    int              mode;
+    int              buggy;
+    int              connect;
+    int              stream;
+    int              log_mask;
+    const char      *log_target;
+    uint32_t         seqno;
+} context_t;
+
+
+void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data);
+void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data);
+
+void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data);
+void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag,
+                   mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data);
+
+void recvraw(mrp_transport_t *t, void *data, size_t size, void *user_data);
+void recvrawfrom(mrp_transport_t *t, void *data, size_t size,
+                 mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data);
+
+
+void dump_msg(mrp_msg_t *msg, FILE *fp)
+{
+    mrp_msg_dump(msg, fp);
+}
+
+
+void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data)
+{
+    context_t       *c = (context_t *)user_data;
+    mrp_msg_field_t *f;
+    uint32_t         seq;
+    char             buf[256];
+    int              status;
+
+    mrp_log_info("received a message");
+    dump_msg(msg, stdout);
+
+    if (c->server) {
+        seq = 0;
+        if ((f = mrp_msg_find(msg, TAG_SEQ)) != NULL) {
+            if (f->type == MRP_MSG_FIELD_UINT32)
+                seq = f->u32;
+        }
+
+        snprintf(buf, sizeof(buf), "reply to message #%u", seq);
+
+        if (!mrp_msg_append(msg, TAG_RPL, MRP_MSG_FIELD_STRING, buf,
+                            TAG_END)) {
+            mrp_log_info("failed to append to received message");
+            exit(1);
+        }
+
+        if (c->connect)
+            status = mrp_transport_send(t, msg);
+        else
+            status = mrp_transport_sendto(t, msg, addr, addrlen);
+
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+
+        /* message unreffed by transport layer */
+    }
+}
+
+
+void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    return recvfrom_msg(t, msg, NULL, 0, user_data);
+}
+
+
+void dump_custom(custom_t *msg, FILE *fp)
+{
+    uint32_t i;
+
+    mrp_data_dump(msg, data_descr, fp);
+    fprintf(fp, "{\n");
+    fprintf(fp, "    seq = %u\n"  , msg->seq);
+    fprintf(fp, "    msg = '%s'\n", msg->msg);
+    fprintf(fp, "     u8 = %u\n"  , msg->u8);
+    fprintf(fp, "     s8 = %d\n"  , msg->s8);
+    fprintf(fp, "    u16 = %u\n"  , msg->u16);
+    fprintf(fp, "    s16 = %d\n"  , msg->s16);
+    fprintf(fp, "    dbl = %f\n"  , msg->dbl);
+    fprintf(fp, "    bln = %s\n"  , msg->bln ? "true" : "false");
+    fprintf(fp, "   astr = (%u)\n", msg->nstr);
+    for (i = 0; i < msg->nstr; i++)
+        fprintf(fp, "           %s\n", msg->astr[i]);
+    fprintf(fp, "   au32 =\n");
+    for (i = 0; msg->au32[i] != U32_GUARD; i++)
+        fprintf(fp, "           %u\n", msg->au32[i]);
+    fprintf(fp, "    rpl = '%s'\n", msg->rpl);
+    fprintf(fp, "}\n");
+}
+
+
+void free_custom(custom_t *msg)
+{
+    mrp_data_free(msg, data_descr->tag);
+}
+
+
+void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag,
+                   mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    context_t *c   = (context_t *)user_data;
+    custom_t  *msg = (custom_t *)data;
+    custom_t   rpl;
+    char       buf[256];
+    uint32_t   au32[] = { 9, 8, 7, 6, 5, -1 };
+    int        status;
+
+    mrp_log_info("received custom message of type 0x%x", tag);
+    dump_custom(data, stdout);
+
+    if (tag != data_descr->tag) {
+        mrp_log_error("Tag 0x%x != our custom type (0x%x).",
+                      tag, data_descr->tag);
+        exit(1);
+    }
+
+    if (c->server) {
+        rpl = *msg;
+        snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq);
+        rpl.rpl  = buf;
+        rpl.au32 = au32;
+
+        if (c->connect)
+            status = mrp_transport_senddata(t, &rpl, data_descr->tag);
+        else
+            status = mrp_transport_senddatato(t, &rpl, data_descr->tag,
+                                              addr, addrlen);
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+    }
+
+    free_custom(msg);
+}
+
+
+void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data)
+{
+    recvfrom_data(t, data, tag, NULL, 0, user_data);
+}
+
+
+void dump_raw(void *data, size_t size, FILE *fp)
+{
+    int len = (int)size;
+
+    fprintf(fp, "[%*.*s]\n", len, len, (char *)data);
+}
+
+
+void recvfrom_raw(mrp_transport_t *t, void *data, size_t size,
+                  mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    context_t *c   = (context_t *)user_data;
+    char       rpl[256];
+    size_t     rpl_size;
+    int        status;
+
+    rpl_size = snprintf(rpl, sizeof(rpl), "reply to message [%*.*s]",
+                        (int)size, (int)size, (char *)data);
+
+    mrp_log_info("received raw message");
+    dump_raw(data, size, stdout);
+
+    if (strncmp((char *)data, "reply to ", 9) != 0) {
+        if (c->connect)
+            status = mrp_transport_sendraw(t, rpl, rpl_size);
+        else
+            status = mrp_transport_sendrawto(t, rpl, rpl_size, addr, addrlen);
+
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+    }
+}
+
+
+void recv_raw(mrp_transport_t *t, void *data, size_t size, void *user_data)
+{
+    recvfrom_raw(t, data, size, NULL, 0, user_data);
+}
+
+
+
+void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(c);
+
+    if (error) {
+        mrp_log_error("Connection closed with error %d (%s).", error,
+                      strerror(error));
+        exit(1);
+    }
+    else {
+        mrp_log_info("Peer has closed the connection.");
+        exit(0);
+    }
+}
+
+
+void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    int        flags;
+
+    flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+    c->t = mrp_transport_accept(lt, c, flags);
+
+    if (c->t == NULL) {
+        mrp_log_error("Failed to accept new connection.");
+        exit(1);
+    }
+}
+
+
+void type_init(context_t *c)
+{
+    if (c->buggy && c->server) {
+        data_descr = &buggy_descr;
+        mrp_log_info("Deliberately using buggy data descriptor...");
+    }
+    else
+        data_descr = &custom_descr;
+
+    if (!mrp_msg_register_type(data_descr)) {
+        mrp_log_error("Failed to register custom data type.");
+        exit(1);
+    }
+}
+
+
+void server_init(context_t *c)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = NULL },
+        { .recvmsgfrom = NULL },
+        .closed        = NULL,
+        .connection    = NULL,
+    };
+
+    int flags;
+
+    type_init(c);
+
+    switch (c->mode) {
+    case MODE_DATA:
+        evt.recvdata     = recv_data;
+        evt.recvdatafrom = recvfrom_data;
+        break;
+    case MODE_RAW:
+        evt.recvraw      = recv_raw;
+        evt.recvrawfrom  = recvfrom_raw;
+        break;
+    case MODE_MESSAGE:
+    default:
+        evt.recvmsg      = recv_msg;
+        evt.recvmsgfrom  = recvfrom_msg;
+    }
+
+    if (c->stream) {
+        evt.connection = connection_evt;
+        evt.closed     = closed_evt;
+    }
+
+    flags = MRP_TRANSPORT_REUSEADDR;
+
+    switch (c->mode) {
+    case MODE_DATA:    flags |= MRP_TRANSPORT_MODE_DATA; break;
+    case MODE_RAW:     flags |= MRP_TRANSPORT_MODE_RAW;  break;
+    default:
+    case MODE_MESSAGE: flags |= MRP_TRANSPORT_MODE_MSG;
+    }
+
+    c->lt = mrp_transport_create(c->ml, c->atype, &evt, c, flags);
+
+    if (c->lt == NULL) {
+        mrp_log_error("Failed to create listening server transport.");
+        exit(1);
+    }
+
+    if (!mrp_transport_bind(c->lt, &c->addr, c->alen)) {
+       mrp_log_error("Failed to bind transport to address %s.", c->addrstr);
+        exit(1);
+    }
+
+    if (c->stream) {
+        if (!mrp_transport_listen(c->lt, 0)) {
+            mrp_log_error("Failed to listen on server transport.");
+            exit(1);
+        }
+    }
+}
+
+
+void send_msg(context_t *c)
+{
+    mrp_msg_t *msg;
+    uint32_t   seq;
+    char       buf[256];
+    char      *astr[] = { "this", "is", "an", "array", "of", "strings" };
+    uint32_t   au32[] = { 1, 2, 3,
+                          1 << 16, 2 << 16, 3 << 16,
+                          1 << 24, 2 << 24, 3 << 24 };
+    uint32_t   nstr = MRP_ARRAY_SIZE(astr);
+    uint32_t   nu32 = MRP_ARRAY_SIZE(au32);
+    int        status;
+
+    seq = c->seqno++;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+
+    msg = mrp_msg_create(TAG_SEQ , MRP_MSG_FIELD_UINT32, seq,
+                         TAG_MSG , MRP_MSG_FIELD_STRING, buf,
+                         TAG_U8  , MRP_MSG_FIELD_UINT8 ,   seq & 0xf,
+                         TAG_S8  , MRP_MSG_FIELD_SINT8 , -(seq & 0xf),
+                         TAG_U16 , MRP_MSG_FIELD_UINT16,   seq,
+                         TAG_S16 , MRP_MSG_FIELD_SINT16, - seq,
+                         TAG_DBL , MRP_MSG_FIELD_DOUBLE, seq / 3.0,
+                         TAG_BLN , MRP_MSG_FIELD_BOOL  , seq & 0x1,
+                         TAG_ASTR, MRP_MSG_FIELD_ARRAY_OF(STRING), nstr, astr,
+                         TAG_AU32, MRP_MSG_FIELD_ARRAY_OF(UINT32), nu32, au32,
+                         TAG_END);
+
+    if (msg == NULL) {
+        mrp_log_error("Failed to create new message.");
+        exit(1);
+    }
+
+    if (c->connect)
+        status = mrp_transport_send(c->t, msg);
+    else
+        status = mrp_transport_sendto(c->t, msg, &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", seq);
+
+    mrp_msg_unref(msg);
+}
+
+
+void send_data(context_t *c)
+{
+    uint32_t  seq = c->seqno++;
+    custom_t  msg;
+    char      buf[256];
+    char     *astr[] = { "this", "is", "a", "test", "string", "array" };
+    uint32_t  au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 };
+    int       status;
+
+    msg.seq = seq;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+    msg.msg  = buf;
+    msg.u8   =   seq & 0xf;
+    msg.s8   = -(seq & 0xf);
+    msg.u16  =   seq;
+    msg.s16  = - seq;
+    msg.dbl  =   seq / 3.0;
+    msg.bln  =   seq & 0x1;
+    msg.astr = astr;
+    msg.nstr = MRP_ARRAY_SIZE(astr);
+    msg.fsck = 1000;
+    msg.au32 = au32;
+    msg.rpl  = "";
+
+    if (c->connect)
+        status = mrp_transport_senddata(c->t, &msg, data_descr->tag);
+    else
+        status = mrp_transport_senddatato(c->t, &msg, data_descr->tag,
+                                          &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", msg.seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", msg.seq);
+}
+
+
+void send_raw(context_t *c)
+{
+    uint32_t  seq = c->seqno++;
+    char      msg[256];
+    size_t    size;
+    int       status;
+
+    size = snprintf(msg, sizeof(msg), "this is message #%u", seq);
+
+    if (c->connect)
+        status = mrp_transport_sendraw(c->t, msg, size);
+    else
+        status = mrp_transport_sendrawto(c->t, msg, size, &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send raw message #%d.", seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%u succesfully sent.", seq);
+}
+
+
+
+void send_cb(mrp_timer_t *t, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    switch (c->mode) {
+    case MODE_DATA:    send_data(c); break;
+    case MODE_RAW:     send_raw(c);  break;
+    default:
+    case MODE_MESSAGE: send_msg(c);
+    }
+}
+
+
+void client_init(context_t *c)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = NULL },
+        { .recvmsgfrom = NULL },
+        .closed        = closed_evt,
+        .connection    = NULL
+    };
+
+    int flags;
+
+    type_init(c);
+
+    switch (c->mode) {
+    case MODE_DATA:
+        evt.recvdata     = recv_data;
+        evt.recvdatafrom = recvfrom_data;
+        flags            = MRP_TRANSPORT_MODE_DATA;
+        break;
+    case MODE_RAW:
+        evt.recvraw      = recv_raw;
+        evt.recvrawfrom  = recvfrom_raw;
+        flags            = MRP_TRANSPORT_MODE_RAW;
+        break;
+    default:
+    case MODE_MESSAGE:
+        evt.recvmsg      = recv_msg;
+        evt.recvmsgfrom  = recvfrom_msg;
+        flags            = MRP_TRANSPORT_MODE_MSG;
+    }
+
+    c->t = mrp_transport_create(c->ml, c->atype, &evt, c, flags);
+
+    if (c->t == NULL) {
+        mrp_log_error("Failed to create new transport.");
+        exit(1);
+    }
+
+    if (!strcmp(c->atype, "unxd")) {
+        char           addrstr[] = "unxd:@stream-test-client";
+        mrp_sockaddr_t addr;
+        socklen_t      alen;
+
+        alen = mrp_transport_resolve(NULL, addrstr, &addr, sizeof(addr), NULL);
+        if (alen <= 0) {
+            mrp_log_error("Failed to resolve transport address '%s'.", addrstr);
+            exit(1);
+        }
+
+        if (!mrp_transport_bind(c->t, &addr, alen)) {
+            mrp_log_error("Failed to bind to transport address '%s'.", addrstr);
+            exit(1);
+        }
+    }
+
+    if (c->connect) {
+        if (!mrp_transport_connect(c->t, &c->addr, c->alen)) {
+            mrp_log_error("Failed to connect to %s.", c->addrstr);
+            exit(1);
+        }
+    }
+
+
+    c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+    if (c->timer == NULL) {
+        mrp_log_error("Failed to create send timer.");
+        exit(1);
+    }
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options] [transport-address]\n\n"
+           "The possible options are:\n"
+           "  -s, --server                   run as test server (default)\n"
+           "  -C, --connect                  connect transport\n"
+           "      For connection-oriented transports, this is automatic.\n"
+           "  -a, --address                  address to use\n"
+           "  -c, --custom                   use custom messages\n"
+           "  -m, --message                  use generic messages (default)\n"
+           "  -r, --raw                      use raw messages\n"
+           "  -b, --buggy                    use buggy data descriptors\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->addrstr    = "tcp4:127.0.0.1:3000";
+    ctx->server     = FALSE;
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "scmrbCa:l:t:v:d:h"
+    struct option options[] = {
+        { "server"    , no_argument      , NULL, 's' },
+        { "address"   , required_argument, NULL, 'a' },
+        { "custom"    , no_argument      , NULL, 'c' },
+        { "message"   , no_argument      , NULL, 'm' },
+        { "raw"       , no_argument      , NULL, 'r' },
+        { "connect"   , no_argument      , NULL, 'C' },
+
+        { "buggy"     , no_argument      , NULL, 'b' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            ctx->server = TRUE;
+            break;
+
+        case 'c':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_DATA;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'm':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_MESSAGE;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'r':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_RAW;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'b':
+            ctx->buggy = TRUE;
+            break;
+
+        case 'C':
+            ctx->connect = TRUE;
+            break;
+
+        case 'a':
+            ctx->addrstr = optarg;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t c;
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.server)
+        mrp_log_info("Running as server, using address '%s'...", c.addrstr);
+    else
+        mrp_log_info("Running as client, using address '%s'...", c.addrstr);
+
+    switch (c.mode) {
+    case MODE_DATA:    mrp_log_info("Using custom data messages..."); break;
+    case MODE_RAW:     mrp_log_info("Using raw messages...");         break;
+    default:
+    case MODE_MESSAGE: mrp_log_info("Using generic messages...");
+    }
+
+    if (!strncmp(c.addrstr, "tcp", 3) || !strncmp(c.addrstr, "unxs", 4) ||
+        !strncmp(c.addrstr, "wsck", 4)) {
+        c.stream  = TRUE;
+        c.connect = TRUE;
+    }
+
+    c.alen = mrp_transport_resolve(NULL, c.addrstr,
+                                   &c.addr, sizeof(c.addr), &c.atype);
+    if (c.alen <= 0) {
+        mrp_log_error("Failed to resolve transport address '%s'.", c.addrstr);
+        exit(1);
+    }
+
+    c.ml = mrp_mainloop_create();
+
+    if (c.server)
+        server_init(&c);
+    else
+        client_init(&c);
+
+    mrp_mainloop_run(c.ml);
+
+    return 0;
+}
diff --git a/src/common/tests/mainloop-ecore-test.c b/src/common/tests/mainloop-ecore-test.c
new file mode 100644 (file)
index 0000000..d094fac
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef ECORE_ENABLED
+
+struct ecore_config_s {
+    int dummy;
+};
+
+
+mrp_mainloop_t *ecore_mainloop_create(test_config_t *cfg)
+{
+    cfg->ml = mrp_mainloop_ecore_get();
+
+    return cfg->ml;
+}
+
+
+int ecore_mainloop_run(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    ecore_main_loop_begin();
+
+    return TRUE;
+}
+
+
+int ecore_mainloop_quit(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    ecore_main_loop_quit();
+
+    return TRUE;
+}
+
+
+int ecore_mainloop_cleanup(test_config_t *cfg)
+{
+    mrp_mainloop_unregister(cfg->ml);
+    mrp_mainloop_destroy(cfg->ml);
+
+    cfg->ml = NULL;
+
+    return TRUE;
+}
+
+
+#else
+
+
+mrp_mainloop_t *ecore_mainloop_create(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("EFL/ecore mainloop support is not available.");
+    exit(1);
+}
+
+
+int ecore_mainloop_run(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("EFL/ecore mainloop support is not available.");
+    exit(1);
+}
+
+
+int ecore_mainloop_quit(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("EFL/ecore mainloop support is not available.");
+    exit(1);
+}
+
+
+int ecore_mainloop_cleanup(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("EFL/ecore mainloop support is not available.");
+    exit(1);
+}
+
+
+#endif
diff --git a/src/common/tests/mainloop-glib-test.c b/src/common/tests/mainloop-glib-test.c
new file mode 100644 (file)
index 0000000..a13741e
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef GLIB_ENABLED
+
+#include <murphy/common/glib-glue.h>
+
+struct glib_config_s {
+    GMainLoop *gml;
+};
+
+
+mrp_mainloop_t *glib_mainloop_create(test_config_t *cfg)
+{
+    glib_config_t  *glib;
+    mrp_mainloop_t *ml;
+
+    glib = mrp_allocz(sizeof(*glib));
+
+    if (glib != NULL) {
+        glib->gml = g_main_loop_new(NULL, FALSE);
+        ml        = mrp_mainloop_glib_get(glib->gml);
+
+        if (ml != NULL) {
+            cfg->glib = glib;
+            cfg->ml   = ml;
+
+            return ml;
+        }
+        else {
+            g_main_loop_unref(glib->gml);
+            mrp_free(glib);
+        }
+    }
+
+    return NULL;
+}
+
+
+int glib_mainloop_run(test_config_t *cfg)
+{
+    if (cfg->glib != NULL) {
+        g_main_loop_run(cfg->glib->gml);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int glib_mainloop_quit(test_config_t *cfg)
+{
+    if (cfg->glib != NULL) {
+        g_main_loop_quit(cfg->glib->gml);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int glib_mainloop_cleanup(test_config_t *cfg)
+{
+    if (cfg->glib != NULL) {
+        mrp_mainloop_unregister(cfg->ml);
+        mrp_mainloop_destroy(cfg->ml);
+        cfg->ml = NULL;
+
+        g_main_loop_unref(cfg->glib->gml);
+        mrp_free(cfg->glib);
+        cfg->glib = NULL;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+#else
+
+
+mrp_mainloop_t *glib_mainloop_create(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("glib mainloop support is not available.");
+    exit(1);
+}
+
+
+int glib_mainloop_run(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("glib mainloop support is not available.");
+    exit(1);
+}
+
+
+int glib_mainloop_quit(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("glib mainloop support is not available.");
+    exit(1);
+}
+
+
+int glib_mainloop_cleanup(test_config_t *cfg)
+{
+    MRP_UNUSED(cfg);
+
+    mrp_log_error("glib mainloop support is not available.");
+    exit(1);
+}
+
+
+#endif
diff --git a/src/common/tests/mainloop-pulse-test.c b/src/common/tests/mainloop-pulse-test.c
new file mode 100644 (file)
index 0000000..58945dc
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifdef PULSE_ENABLED
+
+#include <murphy/common/pulse-glue.h>
+
+struct pulse_config_s {
+    pa_mainloop     *pa_main;
+    pa_mainloop_api *pa;
+};
+
+
+mrp_mainloop_t *pulse_mainloop_create(test_config_t *cfg)
+{
+    pulse_config_t *pulse;
+    mrp_mainloop_t *ml;
+
+    pulse = mrp_allocz(sizeof(*pulse));
+
+    if (pulse != NULL) {
+        pulse->pa_main = pa_mainloop_new();
+        pulse->pa      = pa_mainloop_get_api(pulse->pa_main);
+        ml             = mrp_mainloop_pulse_get(pulse->pa);
+
+        if (ml != NULL) {
+            cfg->pulse = pulse;
+            cfg->ml    = ml;
+
+            return ml;
+        }
+        else {
+            pa_mainloop_free(pulse->pa_main);
+            mrp_free(pulse);
+        }
+    }
+
+    return NULL;
+}
+
+
+int pulse_mainloop_run(test_config_t *cfg)
+{
+    int retval;
+
+    if (cfg->pulse && cfg->pulse->pa != NULL) {
+        pa_mainloop_run(cfg->pulse->pa_main, &retval);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int pulse_mainloop_quit(test_config_t *cfg)
+{
+    if (cfg->pulse && cfg->pulse->pa) {
+        pa_mainloop_quit(cfg->pulse->pa_main, 0);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int pulse_mainloop_cleanup(test_config_t *cfg)
+{
+    if (cfg->pulse != NULL) {
+        mrp_mainloop_unregister(cfg->ml);
+        mrp_mainloop_destroy(cfg->ml);
+        cfg->ml = NULL;
+
+        pa_mainloop_free(cfg->pulse->pa_main);
+        mrp_free(cfg->pulse);
+        cfg->pulse = NULL;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+#else
+
+
+mrp_mainloop_t *pulse_mainloop_create(test_config_t *cfg)
+{
+    mrp_log_error("PulseAudio mainloop support is not available.");
+    exit(1);
+}
+
+
+int pulse_mainloop_run(test_config_t *cfg)
+{
+    mrp_log_error("PulseAudio mainloop support is not available.");
+    exit(1);
+}
+
+
+int pulse_mainloop_quit(test_config_t *cfg)
+{
+    mrp_log_error("PulseAudio mainloop support is not available.");
+    exit(1);
+}
+
+
+int pulse_mainloop_cleanup(test_config_t *cfg)
+{
+    mrp_log_error("PulseAudio mainloop support is not available.");
+    exit(1);
+}
+
+
+#endif
diff --git a/src/common/tests/mainloop-qt-test.cpp b/src/common/tests/mainloop-qt-test.cpp
new file mode 100644 (file)
index 0000000..4c047a6
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/config.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+
+#include "mainloop-qt-test.h"
+
+#include <QCoreApplication>
+#include <murphy/common/qt-glue.h>
+
+
+typedef struct qt_config_s {
+    QCoreApplication *app;
+    mrp_mainloop_t   *ml;
+} qt_config_t ;
+
+static qt_config_t *qt;
+
+mrp_mainloop_t *qt_mainloop_create()
+{
+    mrp_mainloop_t *ml = NULL;
+
+    if (qt == NULL) {
+        int argc = 0;
+        char **argv = 0;
+
+        qt = (qt_config_t *)mrp_allocz(sizeof(*qt));
+        if (!qt) return NULL;
+
+        qt->app = new QCoreApplication(argc, argv);
+        ml      = mrp_mainloop_qt_get();
+
+        if (ml == NULL) {
+            delete qt->app;
+            mrp_free(qt);
+            qt = NULL;
+        }
+    }
+    else {
+        return qt->ml;
+    }
+
+    return ml;
+}
+
+int qt_mainloop_run()
+{
+    if (qt != NULL) {
+        QCoreApplication::exec();
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+int qt_mainloop_quit()
+{
+    if (qt != NULL) {
+        QCoreApplication::quit();
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+int qt_mainloop_cleanup(mrp_mainloop_t *ml)
+{
+    if (qt != NULL) {
+        mrp_mainloop_unregister(ml);
+        mrp_mainloop_destroy(ml);
+
+        delete qt->app;
+        mrp_free(qt);
+        qt = NULL;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
diff --git a/src/common/tests/mainloop-qt-test.h b/src/common/tests/mainloop-qt-test.h
new file mode 100644 (file)
index 0000000..cfa3f3e
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_QT_TEST_H_
+#define __MURPHY_QT_TEST_H_
+
+#include <murphy/config.h>
+#include <murphy/common/macros.h>
+#include <murphy/common/mainloop.h>
+
+MRP_CDECL_BEGIN
+
+mrp_mainloop_t *qt_mainloop_create(void);
+int qt_mainloop_run(void);
+int qt_mainloop_quit(void);
+int qt_mainloop_cleanup(mrp_mainloop_t *ml);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_QT_TEST_H_ */
diff --git a/src/common/tests/mainloop-test.c b/src/common/tests/mainloop-test.c
new file mode 100644 (file)
index 0000000..77dea36
--- /dev/null
@@ -0,0 +1,1795 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <dbus/dbus.h>
+
+#include <murphy/config.h>
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+
+#ifdef PULSE_ENABLED
+#  include <pulse/mainloop.h>
+#  include <murphy/common/pulse-glue.h>
+#endif
+
+#ifdef ECORE_ENABLED
+#  include <Ecore.h>
+#  include <murphy/common/ecore-glue.h>
+#endif
+
+#ifdef GLIB_ENABLED
+#  include <glib.h>
+#  include "glib-pump.c"
+#  include <murphy/common/glib-glue.h>
+#endif
+
+#ifdef QT_ENABLED
+#include <murphy/common/qt-glue.h>
+#endif
+
+#define info(fmt, args...) do {                                           \
+        fprintf(stdout, "I: "fmt"\n" ,  ## args);                         \
+        fflush(stdout);                                                   \
+    } while (0)
+
+#define warning(fmt, args...) do {                                        \
+        fprintf(stderr, "W: "fmt"\n" ,  ## args);                         \
+        fflush(stderr);                                                   \
+    } while (0)
+
+#define error(fmt, args...) do {                                          \
+        fprintf(stderr, "E: "fmt"\n" ,  ## args);                         \
+        fflush(stderr);                                                   \
+    } while (0)
+
+#define fatal(fmt, args...) do {                                          \
+        fprintf(stderr, "C: "fmt"\n" ,  ## args);                         \
+        fflush(stderr);                                                   \
+        exit(1);                                                          \
+    } while (0)
+
+#define USECS_PER_SEC (1000 * 1000)
+
+#define DEFAULT_RUNTIME 30                            /* run for 30 seconds */
+
+
+enum {
+    MAINLOOP_NATIVE,
+    MAINLOOP_PULSE,
+    MAINLOOP_ECORE,
+    MAINLOOP_GLIB,
+    MAINLOOP_QT
+};
+
+
+struct pulse_config_s;
+typedef struct pulse_config_s pulse_config_t;
+
+struct ecore_config_s;
+typedef struct ecore_config_s ecore_config_t;
+
+struct glib_config_s;
+typedef struct glib_config_s glib_config_t;
+
+
+
+typedef struct {
+    int nio;
+    int ntimer;
+    int deferred;
+    int nsignal;
+
+    int ngio;
+    int ngtimer;
+
+    int ndbus_method;
+    int ndbus_signal;
+
+    int         log_mask;
+    const char *log_target;
+
+    int         mainloop_type;
+
+    mrp_mainloop_t *ml;
+    pulse_config_t *pulse;
+    ecore_config_t *ecore;
+    glib_config_t  *glib;
+
+    int nrunning;
+    int runtime;
+
+    pid_t        child;
+    unsigned int wlpf;
+    unsigned int wfrc;
+} test_config_t;
+
+
+#include "mainloop-pulse-test.c"
+#include "mainloop-ecore-test.c"
+#include "mainloop-glib-test.c"
+#include "mainloop-qt-test.h"
+
+static test_config_t cfg;
+
+
+static mrp_mainloop_t *mainloop_create(test_config_t *cfg);
+static void mainloop_run(test_config_t *cfg);
+static void mainloop_quit(test_config_t *cfg);
+static void mainloop_cleanup(test_config_t *cfg);
+
+
+/*
+ * native timers
+ */
+
+#define TIMER_INTERVALS 1, 2, 3, 4, 6, 8, 1, 3, 12, 15, 18, 21, 24
+
+
+typedef struct {
+    int             id;
+    mrp_timer_t    *timer;
+    int             interval;
+    int             count;
+    int             target;
+    struct timeval  prev;
+} test_timer_t;
+
+
+static test_timer_t *timers;
+
+static mrp_wakeup_t *wakeup;
+static mrp_wakeup_t *wuplim;
+
+
+
+static int timeval_diff(struct timeval *tv1, struct timeval *tv2)
+{
+    int64_t u1, u2;
+
+    u1 = tv1->tv_sec * USECS_PER_SEC + tv1->tv_usec;
+    u2 = tv2->tv_sec * USECS_PER_SEC + tv2->tv_usec;
+
+    return (int)(u1 - u2);
+}
+
+
+static void timeval_now(struct timeval *tv)
+{
+    gettimeofday(tv, NULL);
+}
+
+
+void timer_cb(mrp_timer_t *timer, void *user_data)
+{
+    test_timer_t   *t = (test_timer_t *)user_data;
+    struct timeval  now;
+    double          diff, error;
+
+    MRP_UNUSED(timer);
+
+    timeval_now(&now);
+    diff  = timeval_diff(&now, &t->prev) / 1000.0;
+    error = diff - t->interval;
+    if (error < 0.0)
+        error = -error;
+
+    info("MRPH timer #%d: %d/%d, diff %.2f (lag %.2f, %.3f %%)",
+         t->id, t->count, t->target, diff, error, 100 * error / diff);
+
+    t->count++;
+    t->prev = now;
+
+    if (t->count >= t->target) {
+        info("MRPH timer #%d has finished.", t->id);
+
+        mrp_del_timer(t->timer);
+        t->timer = NULL;
+        cfg.nrunning--;
+    }
+}
+
+
+static void setup_timers(mrp_mainloop_t *ml)
+{
+    test_timer_t *t;
+    int           intervals[] = { TIMER_INTERVALS, 0 }, *iv = intervals;
+    int           msecs, i;
+
+    if ((timers = mrp_allocz_array(test_timer_t, cfg.ntimer)) != NULL) {
+        for (i = 0, t = timers; i < cfg.ntimer; i++, t++) {
+            t->id = i;
+
+            msecs = *iv;
+            while (cfg.runtime / msecs < 1 && msecs > 0)
+                msecs /= 2;
+            msecs *= 1000;
+            if (!msecs)
+                msecs = 500;
+
+            t->interval = msecs;
+            t->target   = 1000 * cfg.runtime / msecs;
+            if (!t->target)
+                continue;
+
+            timeval_now(&t->prev);
+            t->timer = mrp_add_timer(ml, t->interval, timer_cb, t);
+
+            if (t->timer != NULL)
+                info("MRPH timer #%d: interval=%d, target=%d", t->id, *iv,
+                     t->target);
+            else
+                fatal("MRPH timer #%d: failed to create", t->id);
+
+            cfg.nrunning++;
+            iv++;
+            if (!*iv)
+                iv = intervals;
+        }
+    }
+    else
+        if (cfg.ntimer > 0)
+            fatal("could not allocate %d timers", cfg.ntimer);
+}
+
+
+static void check_timers(void)
+{
+    test_timer_t *t;
+    int           i;
+
+    for (i = 0, t = timers; i < cfg.ntimer; i++, t++) {
+        if (t->target != 0 && t->count != t->target)
+            warning("MRPH timer #%d: FAIL (only %d/%d)", t->id, t->count,
+                    t->target);
+        else
+            info("MRPH timer #%d: OK (%d/%d)", t->id, t->count, t->target);
+    }
+}
+
+
+/*
+ * native I/O
+ */
+
+#define IO_INTERVALS    1, 3, 5, 9, 12, 15, 18, 21
+
+typedef struct {
+    int             id;
+    int             pipe[2];
+    mrp_io_watch_t *watch;
+    mrp_timer_t    *timer;
+    int             target;
+    int             sent;
+    int             received;
+} test_io_t;
+
+
+static test_io_t *ios;
+
+
+static void send_io(mrp_timer_t *timer, void *user_data)
+{
+    test_io_t *w = (test_io_t *)user_data;
+    char       buf[1024];
+    int        plural, size;
+
+    MRP_UNUSED(timer);
+
+    plural = (w->target - w->sent) != 1;
+    size   = snprintf(buf, sizeof(buf),
+                      "I/O #%d: %d message%s remain%s.", w->id,
+                      w->target - w->sent,
+                      plural ? "s" : "", plural ? "" : "s");
+
+    if (write(w->pipe[1], buf, size) < 0) {
+        /* just ignore it... */
+    }
+    w->sent++;
+
+    info("MRPH I/O #%d: sent message %d/%d.", w->id, w->sent, w->target);
+
+    if (w->sent >= w->target) {
+        info("MRPH I/O #%d: sending done.", w->id);
+
+        close(w->pipe[1]);
+        mrp_del_timer(timer);
+        w->timer = NULL;
+
+        cfg.nrunning--;
+    }
+}
+
+
+static void recv_io(mrp_io_watch_t *watch, int fd, mrp_io_event_t events,
+                    void *user_data)
+{
+    test_io_t *w = (test_io_t *)user_data;
+    char       buf[1024];
+    int        size;
+
+    MRP_UNUSED(watch);
+
+    if (watch != w->watch)
+        fatal("MRPH I/O #%d called with incorrect data.", w->id);
+
+    if (events & MRP_IO_EVENT_IN) {
+        size = read(fd, buf, sizeof(buf) - 1);
+
+        if (size > 0) {
+            w->received++;
+            buf[size] = '\0';
+            info("MRPH I/O #%d: received message [%s]", w->id, buf);
+        }
+        else
+            warning("MRPH I/O #%d: got empty message", w->id);
+    }
+
+    if (events & MRP_IO_EVENT_HUP) {
+        info("MRPH I/O #%d: receiver done (got %d/%d)", w->id, w->received,
+             w->sent);
+        close(w->pipe[0]);
+        mrp_del_io_watch(watch);
+    }
+}
+
+
+void setup_io(mrp_mainloop_t *ml)
+{
+    test_io_t      *w;
+    mrp_io_event_t  mask;
+    int             intervals[] = { IO_INTERVALS, 0 }, *iv = intervals;
+    int             msecs, i;
+
+
+    if ((ios = mrp_allocz_array(test_io_t, cfg.nio)) != NULL) {
+        mask = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP;
+
+        for (i = 0, w = ios; i < cfg.nio; i++, w++) {
+            w->id = i;
+
+            msecs = *iv;
+            while (cfg.runtime / msecs < 1 && msecs > 0)
+                msecs /= 2;
+            msecs *= 1000;
+            if (!msecs)
+                msecs = 500;
+
+            w->target = 1000 * cfg.runtime / msecs;
+            if (!w->target)
+                continue;
+
+            if (pipe(w->pipe) != 0)
+                fatal("MRPH I/O #%d: could not create pipe", w->id);
+
+            w->watch = mrp_add_io_watch(ml, w->pipe[0], mask, recv_io, w);
+            w->timer = mrp_add_timer(ml, msecs, send_io, w);
+
+            if (w->timer == NULL)
+                fatal("MRPH I/O #%d: could not create I/O timer", w->id);
+
+            if (w->watch == NULL)
+                fatal("MRPH I/O #%d: could not create I/O watch", w->id);
+            else
+                info("MRPH I/O #%d: interval=%d, target=%d", w->id, *iv,
+                     w->target);
+
+            cfg.nrunning++;
+            iv++;
+            if (!*iv)
+                iv = intervals;
+        }
+    }
+    else
+        if (cfg.nio > 0)
+        fatal("could not allocate %d I/O watches", cfg.nio);
+}
+
+
+static void check_io(void)
+{
+    test_io_t *w;
+    int        i;
+
+    for (i = 0, w = ios; i < cfg.nio; i++, w++) {
+        if (w->target != 0 && w->sent != w->received)
+            warning("MRPH I/O #%d: FAIL (only %d/%d)", w->id, w->received,
+                    w->sent);
+        else
+            info("MRPH I/O #%d: OK (%d/%d)", w->id, w->received, w->sent);
+    }
+}
+
+
+/*
+ * native deferred/idle callbacks
+ */
+
+
+static void setup_deferred(void)
+{
+    return;
+}
+
+
+
+/*
+ * native signals
+ */
+
+#define SIG_INTERVALS   1, 5, 9, 3, 6, 12
+#define SIGNUMS         { SIGUSR1, SIGUSR2, SIGTERM, SIGCONT, SIGQUIT, 0 }
+
+static const char *signames[] = {
+    [SIGINT]  = "SIGINT",  [SIGTERM] = "SIGTERM", [SIGQUIT] = "SIGQUIT",
+    [SIGCONT] = "SIGCONT", [SIGUSR1] = "SIGUSR1", [SIGUSR2] = "SIGUSR2",
+    [SIGCHLD] = "SIGCHLD"
+};
+
+
+typedef struct {
+    int               id;
+    int               signum;
+    mrp_sighandler_t *watch;
+    mrp_timer_t      *timer;
+    int               target;
+    int               sent;
+    int               received;
+} test_signal_t;
+
+test_signal_t *signals;
+
+
+static void send_signal(mrp_timer_t *timer, void *user_data)
+{
+    test_signal_t *t = (test_signal_t *)user_data;
+
+    MRP_UNUSED(timer);
+
+    if (t->sent >= t->target)
+        return;
+
+    kill(getpid(), t->signum);
+    t->sent++;
+    info("MRPH signal #%d: sent signal %d/%d of %s", t->id,
+         t->sent, t->target, strsignal(t->signum));
+
+    if (t->sent >= t->target) {
+        info("MRPH signal #%d: sending done", t->id);
+        mrp_del_timer(t->timer);
+        t->timer = NULL;
+    }
+}
+
+
+static void recv_signal(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    test_signal_t *t = (test_signal_t *)user_data;
+
+    MRP_UNUSED(h);
+
+    if (h != t->watch)
+        fatal("MRPH signal #%d called with incorrect data", t->id);
+
+    t->received++;
+    info("MRPH signal #%d: received signal %d/%d of %s", t->id,
+         t->received, t->target, signames[signum]);
+
+    if (t->sent >= t->target) {
+        info("MRPH signal #%d: receiving done", t->id);
+        cfg.nrunning--;
+    }
+}
+
+
+static void setup_signals(mrp_mainloop_t *ml)
+{
+    test_signal_t *t;
+    int            intervals[] = { SIG_INTERVALS, 0 }, *iv = intervals;
+    int            signums[] = SIGNUMS, *s = signums;
+    int            msecs, i;
+
+    if ((signals = mrp_allocz_array(test_signal_t, cfg.nsignal)) != NULL) {
+        for (i = 0, t = signals; i < cfg.nsignal; i++, t++) {
+            t->id = i;
+            t->signum = *s;
+
+            msecs = *iv;
+            while (cfg.runtime / msecs < 1 && msecs > 0)
+                msecs /= 2;
+            msecs *= 1000;
+            if (!msecs)
+                msecs = 500;
+
+            t->target = 1000 * cfg.runtime / msecs;
+            if (!t->target)
+                continue;
+
+            t->watch = mrp_add_sighandler(ml, *s, recv_signal, t);
+            t->timer = mrp_add_timer(ml, msecs, send_signal, t);
+
+            if (t->timer == NULL)
+                fatal("MRPH signal #%d: could not create timer", t->id);
+
+            if (t->watch == NULL)
+                fatal("MRPH signal #%d: could not create watch", t->id);
+            else
+                info("MRPH signal #%d: interval=%d, target=%d", t->id, *iv,
+                     t->target);
+
+            cfg.nrunning++;
+            iv++;
+            if (!*iv)
+                iv = intervals;
+
+            s++;
+            if (!*s)
+                s = signums;
+        }
+    }
+    else
+        if (cfg.nsignal > 0)
+            fatal("could not allocate %d signal watches", cfg.nsignal);
+}
+
+
+static void check_signals(void)
+{
+    test_signal_t *t;
+    int            i;
+
+    for (i = 0, t = signals; i < cfg.nsignal; i++, t++) {
+        if (t->sent < t->received)
+            warning("MRPH signal #%d: FAIL (only %d/%d", t->id,
+                    t->received, t->sent);
+        else
+            info("MRPH signal #%d: OK (%d/%d)", t->id, t->received, t->sent);
+    }
+}
+
+
+static void wakeup_cb(mrp_wakeup_t *w, mrp_wakeup_event_t event,
+                      void *user_data)
+{
+    static struct timeval  prev[2] = { {0, 0}, {0, 0} };
+    const char            *evt;
+    struct timeval         now;
+    double                 diff;
+    int                    id;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(user_data);
+
+    timeval_now(&now);
+
+    switch (event) {
+    case MRP_WAKEUP_EVENT_TIMER: evt = "timer";           break;
+    case MRP_WAKEUP_EVENT_IO:    evt = "I/O (or signal)"; break;
+    case MRP_WAKEUP_EVENT_LIMIT: evt = "limit";           break;
+    default:                     evt = "???";
+    }
+
+    id = user_data ? 1 : 0;
+
+    if (MRP_LIKELY(prev[id].tv_usec != 0)) {
+        diff = timeval_diff(&now, &prev[id]) / 1000.0;
+        info("woken up #%d by %s, %.2f msecs since previous", id, evt, diff);
+    }
+
+    prev[id] = now;
+}
+
+
+static void setup_wakeup(mrp_mainloop_t *ml)
+{
+    unsigned int nolim = MRP_WAKEUP_NOLIMIT;
+
+    if (cfg.child == 0)
+        return;
+
+    wakeup = mrp_add_wakeup(ml, MRP_WAKEUP_EVENT_ANY, nolim, nolim,
+                            wakeup_cb, (void *)0);
+    wuplim = mrp_add_wakeup(ml, MRP_WAKEUP_EVENT_ANY, cfg.wlpf, cfg.wfrc,
+                            wakeup_cb, (void *)1);
+}
+
+
+static void cleanup_wakeup(void)
+{
+    mrp_del_wakeup(wakeup);
+    wakeup = NULL;
+    mrp_del_wakeup(wuplim);
+    wuplim = NULL;
+}
+
+
+static void check_quit(mrp_timer_t *timer, void *user_data)
+{
+    MRP_UNUSED(user_data);
+
+    if (cfg.nrunning <= 0) {
+        mrp_del_timer(timer);
+        mainloop_quit(&cfg);
+    }
+}
+
+
+
+#ifdef GLIB_ENABLED
+/*
+ * glib timers
+ */
+
+#define GTIMER_INTERVALS 1, 2, 3, 4, 6, 8, 1, 3, 12, 15, 18, 21, 24
+
+typedef struct {
+    int            id;
+    guint          gsrc;
+    int            interval;
+    int            count;
+    int            target;
+    struct timeval prev;
+} glib_timer_t;
+
+
+static glib_timer_t *gtimers;
+
+
+static gboolean glib_timer_cb(gpointer user_data)
+{
+    glib_timer_t   *t = (glib_timer_t *)user_data;
+    struct timeval  now;
+    double          diff, error;
+
+    timeval_now(&now);
+    diff  = timeval_diff(&now, &t->prev) / 1000.0;
+    error = diff - t->interval;
+    if (error < 0.0)
+        error = -error;
+
+    info("GLIB timer #%d: %d/%d, diff %.2f (lag %.2f, %.3f %%)",
+         t->id, t->count, t->target, diff, error, 100 * error / diff);
+
+    t->count++;
+    t->prev = now;
+
+    if (t->count >= t->target) {
+        info("GLIB timer #%d has finished.", t->id);
+
+        t->gsrc = 0;
+        cfg.nrunning--;
+        return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+static void setup_glib_timers(void)
+{
+    glib_timer_t *t;
+    int           intervals[] = { GTIMER_INTERVALS, 0 }, *iv = intervals;
+    int           msecs, i;
+
+    if ((gtimers = mrp_allocz_array(glib_timer_t, cfg.ngtimer)) != NULL) {
+        for (i = 0, t = gtimers; i < cfg.ngtimer; i++, t++) {
+            t->id = i;
+
+            msecs = *iv;
+            while (cfg.runtime / msecs < 1 && msecs > 0)
+                msecs /= 2;
+            msecs *= 1000;
+            if (!msecs)
+                msecs = 500;
+
+            t->interval = msecs;
+            t->target   = 1000 * cfg.runtime / msecs;
+            if (!t->target)
+                continue;
+
+            timeval_now(&t->prev);
+            t->gsrc = g_timeout_add(msecs, glib_timer_cb, t);
+
+            if (t->gsrc != 0)
+                info("GLIB timer #%d: interval=%d, target=%d", t->id, *iv,
+                     t->target);
+            else
+                fatal("GLIB timer #%d: failed to create", t->id);
+
+            cfg.nrunning++;
+            iv++;
+            if (!*iv)
+                iv = intervals;
+        }
+    }
+    else
+        if (cfg.ntimer > 0)
+            fatal("could not allocate %d GLIB timers", cfg.ngtimer);
+}
+
+
+static void check_glib_timers(void)
+{
+    glib_timer_t *t;
+    int           i;
+
+    for (i = 0, t = gtimers; i < cfg.ngtimer; i++, t++) {
+        if (t->target != 0 && t->count != t->target)
+            warning("GLIB timer #%d: FAIL (only %d/%d)", t->id, t->count,
+                    t->target);
+        else
+            info("GLIB timer #%d: OK (%d/%d)", t->id, t->count, t->target);
+    }
+}
+
+
+/*
+ * glib I/O
+ */
+
+#define GIO_INTERVALS    1, 3, 4, 5, 6, 7, 9, 12, 15, 18, 21
+
+typedef struct {
+    int             id;
+    int             pipe[2];
+    GIOChannel     *gioc;
+    guint           gsrc;
+    guint           timer;
+    int             target;
+    int             sent;
+    int             received;
+} glib_io_t;
+
+
+static glib_io_t *gios;
+
+
+static gboolean glib_send_io(gpointer user_data)
+{
+    glib_io_t *t = (glib_io_t *)user_data;
+    char       buf[1024];
+    int        plural, size;
+
+    plural = (t->target - t->sent) != 1;
+    size   = snprintf(buf, sizeof(buf),
+                      "I/O #%d: %d message%s remain%s.", t->id,
+                      t->target - t->sent,
+                      plural ? "s" : "", plural ? "" : "s");
+
+    if (write(t->pipe[1], buf, size) < 0) {
+        /* just ignore it... */
+    }
+    t->sent++;
+
+    info("GLIB I/O #%d: sent message %d/%d.", t->id, t->sent, t->target);
+
+    if (t->sent >= t->target) {
+        info("GLIB I/O #%d: sending done.", t->id);
+
+        close(t->pipe[1]);
+        t->timer = 0;
+
+        cfg.nrunning--;
+        return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+static gboolean glib_recv_io(GIOChannel *ioc, GIOCondition cond,
+                             gpointer user_data)
+{
+    glib_io_t *t  = (glib_io_t *)user_data;
+    int        fd = g_io_channel_unix_get_fd(ioc);
+    char       buf[1024];
+    int        size;
+
+    if (cond & G_IO_IN) {
+        size = read(fd, buf, sizeof(buf) - 1);
+
+        if (size > 0) {
+            t->received++;
+            buf[size] = '\0';
+            info("GLIB I/O #%d: received message [%s]", t->id, buf);
+        }
+        else
+            warning("GLIB I/O #%d: got empty message", t->id);
+    }
+
+    if (cond & G_IO_HUP) {
+        info("GLIB I/O #%d: receiver done (got %d/%d)", t->id, t->received,
+             t->sent);
+        close(fd);
+        return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+void setup_glib_io(void)
+{
+    glib_io_t    *t;
+    GIOCondition  cond;
+    int           intervals[] = { GIO_INTERVALS, 0 }, *iv = intervals;
+    int           msecs, i;
+
+    if ((gios = mrp_allocz_array(glib_io_t, cfg.ngio)) != NULL) {
+        cond = G_IO_IN | G_IO_HUP;
+
+        for (i = 0, t = gios; i < cfg.ngio; i++, t++) {
+            t->id = i;
+
+            msecs = *iv;
+            while (cfg.runtime / msecs < 1 && msecs > 0)
+                msecs /= 2;
+            msecs *= 1000;
+            if (!msecs)
+                msecs = 500;
+
+            t->target = 1000 * cfg.runtime / msecs;
+            if (!t->target)
+                continue;
+
+            if (pipe(t->pipe) != 0)
+                fatal("GLIB I/O #%d: could not create pipe", t->id);
+
+            t->gioc = g_io_channel_unix_new(t->pipe[0]);
+            if (t->gioc == NULL)
+                fatal("GLIB I/O #%d: failed to create I/O channel", t->id);
+
+            t->gsrc = g_io_add_watch(t->gioc, cond, glib_recv_io, t);
+            if (t->gsrc == 0)
+                fatal("GLIB I/O #%d: failed to add I/O watch", t->id);
+
+            t->timer = g_timeout_add(msecs, glib_send_io, t);
+            if (t->timer == 0)
+                fatal("GLIB I/O #%d: could not create I/O timer", t->id);
+
+            info("GLIB I/O #%d: interval=%d, target=%d", t->id, *iv,
+                 t->target);
+
+            cfg.nrunning++;
+            iv++;
+            if (!*iv)
+                iv = intervals;
+        }
+    }
+    else
+        if (cfg.ngio > 0)
+            fatal("could not allocate %d glib I/O watches", cfg.ngio);
+}
+
+
+static void check_glib_io(void)
+{
+    glib_io_t *t;
+    int        i;
+
+    for (i = 0, t = gios; i < cfg.ngio; i++, t++) {
+        if (t->target != 0 && t->sent != t->received) {
+            warning("GLIB I/O #%d (fd %d): FAIL (only %d/%d)",
+                    t->id, t->pipe[0], t->received, t->sent);
+        }
+
+        else
+            info("GLIB I/O #%d (fd %d): OK (%d/%d)", t->id, t->pipe[0],
+                 t->received, t->sent);
+    }
+}
+
+static void glib_pump_cleanup(void);
+#endif
+
+
+/*
+ * DBUS tests (quite a mess the whole shebang...)
+ */
+
+#define DBUS_PATH   "/"
+#define DBUS_IFACE  "org.murphy.test"
+#define DBUS_METHOD "message"
+#define DBUS_SIGNAL "signal"
+
+typedef struct {
+    int             pipe[2];
+    pid_t           client;
+    char            address[256];
+
+    mrp_mainloop_t *ml;
+    DBusConnection *conn;
+    mrp_timer_t    *sigtimer;
+
+    int             nmethod;
+    int             nack;
+    int             nsignal;
+} dbus_test_t;
+
+
+static dbus_test_t dbus_test = { pipe: { -1, -1 } };
+
+
+
+
+int mrp_setup_dbus_connection(mrp_mainloop_t *ml, DBusConnection *conn);
+
+
+static void open_dbus_pipe(void)
+{
+    if (pipe(dbus_test.pipe) < 0)
+        fatal("failed to opend pipe for DBUS tests");
+}
+
+
+static void close_dbus_pipe(char *dir)
+{
+    while (*dir) {
+        switch (*dir++) {
+        case 'r':
+            if (dbus_test.pipe[0] != -1) {
+                close(dbus_test.pipe[0]);
+                dbus_test.pipe[0] = -1;
+            }
+            break;
+
+        case 'w':
+            if (dbus_test.pipe[1] != -1) {
+                close(dbus_test.pipe[1]);
+                dbus_test.pipe[1] = -1;
+            }
+            break;
+        }
+    }
+}
+
+
+static void recv_dbus_reply(DBusPendingCall *pending, void *user_data)
+{
+    DBusMessage *msg;
+    char        *reply;
+
+    MRP_UNUSED(user_data);
+
+    if ((msg = dbus_pending_call_steal_reply(pending)) != NULL) {
+        if (dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING,
+                                  &reply, DBUS_TYPE_INVALID)) {
+            info("DBUS test: got reply #%d '%s'", dbus_test.nack, reply);
+            dbus_test.nack++;
+        }
+
+        dbus_message_unref(msg);
+    }
+
+    dbus_pending_call_unref(pending);
+
+    if (dbus_test.nack >= cfg.ndbus_method) {
+        char dummy[256];
+
+        cfg.nrunning--;
+
+        /* block until the client is done */
+        if (read(dbus_test.pipe[0], dummy, sizeof(dummy)) < 0) {
+            /* just ignore it... */
+        }
+    }
+}
+
+
+static int send_dbus_message(DBusConnection *conn, char *addr, char *buf)
+{
+    DBusMessage     *msg;
+    DBusPendingCall *pending;
+
+    msg = dbus_message_new_method_call(addr, DBUS_PATH,
+                                       DBUS_IFACE, DBUS_METHOD);
+
+    if (msg == NULL)
+        fatal("failed to create DBUS message");
+
+    if (!dbus_message_append_args(msg,
+                                  DBUS_TYPE_STRING, &buf, DBUS_TYPE_INVALID))
+        fatal("failed to add arguments to DBUS method call");
+
+    if (!dbus_connection_send_with_reply(conn, msg, &pending, 5000))
+        fatal("failed to send DBUS message");
+
+    if (!dbus_pending_call_set_notify(pending, recv_dbus_reply, NULL, NULL))
+        fatal("failed to set pending call notification callback");
+
+    dbus_message_unref(msg);
+
+    return TRUE;
+}
+
+
+static int send_dbus_reply(DBusConnection *conn, DBusMessage *msg, char *buf)
+{
+    DBusMessage *reply;
+
+    if ((reply = dbus_message_new_method_return(msg)) != NULL) {
+        if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &buf,
+                                      DBUS_TYPE_INVALID))
+            fatal("failed to add arguments to DBUS method reply");
+
+        if (!dbus_connection_send(conn, reply, NULL))
+            fatal("failed to send DBUS reply");
+
+        dbus_message_unref(reply);
+    }
+
+    dbus_test.nmethod++;
+    if (dbus_test.nmethod >= cfg.ndbus_method)
+        cfg.nrunning--;
+
+    return TRUE;
+}
+
+
+static DBusConnection *connect_to_dbus(char *name)
+{
+    DBusConnection *conn;
+    DBusError       error;
+    unsigned int    flags;
+    int             status;
+
+    dbus_error_init(&error);
+
+    if ((conn = dbus_bus_get(DBUS_BUS_SESSION, NULL)) != NULL) {
+        if (!name || !*name)
+            return conn;
+
+        flags  = DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_DO_NOT_QUEUE;
+        status = dbus_bus_request_name(conn, name, flags, &error);
+
+        if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
+            return conn;
+        else
+            error("failed to get name '%s' on DBUS (error: %s)", name,
+                  error.message ? error.message : "unknown");
+    }
+
+    return NULL;
+}
+
+
+static void client_send_msg(mrp_timer_t *t, void *user_data)
+{
+    char buf[1024];
+
+    MRP_UNUSED(user_data);
+
+    if (dbus_test.nmethod < cfg.ndbus_method) {
+        snprintf(buf, sizeof(buf), "DBUS message #%d", dbus_test.nmethod);
+        send_dbus_message(dbus_test.conn, dbus_test.address, buf);
+
+        info("DBUS client: sent #%d message", dbus_test.nmethod);
+
+        dbus_test.nmethod++;
+    }
+
+    if (dbus_test.nmethod >= cfg.ndbus_method) {
+        mrp_del_timer(t);
+        if (cfg.ndbus_method == 0)
+            cfg.nrunning--;
+        else {
+            /* cfg.nrunning updated only once we've received the last reply */
+        }
+    }
+}
+
+
+static void setup_dbus_client(mrp_mainloop_t *ml)
+{
+    DBusConnection *conn;
+    int             i, nmethod, nsignal;
+    size_t          size;
+    ssize_t         amount_read;
+
+    nmethod = cfg.ndbus_method;
+    nsignal = cfg.ndbus_signal;
+    mrp_clear(&cfg);
+    cfg.ndbus_method = nmethod;
+    cfg.ndbus_signal = nsignal;
+
+    mrp_mainloop_quit(ml, 0);
+#ifdef GLIB_ENABLED
+    glib_pump_cleanup();
+#endif
+    mrp_mainloop_destroy(ml);
+
+    for (i = 3; i < 1024; i++)
+        if (i != dbus_test.pipe[0])
+            close(i);
+
+    size = sizeof(dbus_test.address) - 1;
+    amount_read = read(dbus_test.pipe[0], dbus_test.address, size);
+    if (amount_read > 0) {
+        dbus_test.address[amount_read] = '\0';
+        info("DBUS test: got address '%s'", dbus_test.address);
+    }
+
+    /*sleep(5);*/
+
+    if ((ml = dbus_test.ml = mrp_mainloop_create()) == NULL)
+        fatal("failed to create mainloop");
+
+    cfg.ml = ml;
+
+    if ((conn = dbus_test.conn = connect_to_dbus(NULL)) == NULL)
+        fatal("failed to connect to DBUS");
+
+    if (!mrp_setup_dbus_connection(ml, conn))
+        fatal("failed to setup DBUS connection with mainloop");
+
+    if (mrp_add_timer(ml, 1000, client_send_msg, NULL) == NULL)
+        fatal("failed to create DBUS message sending timer");
+
+    if (mrp_add_timer(ml, 1000, check_quit, NULL) == NULL)
+        fatal("failed to create quit-check timer");
+
+    cfg.nrunning++;
+}
+
+
+static DBusHandlerResult dispatch_method(DBusConnection *c,
+                                         DBusMessage *msg, void *data)
+{
+#define SAFESTR(str) (str ? str : "<none>")
+    const char *path      = dbus_message_get_path(msg);
+    const char *interface = dbus_message_get_interface(msg);
+    const char *member    = dbus_message_get_member(msg);
+    const char *message;
+    char        reply[1024];
+
+    MRP_UNUSED(data);
+
+    if (dbus_message_get_type(msg) != DBUS_MESSAGE_TYPE_METHOD_CALL || !member)
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    if (strcmp(path, DBUS_PATH) ||
+        strcmp(interface, DBUS_IFACE) ||
+        strcmp(member, DBUS_METHOD))
+        return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+    /*info("DBUS server: got call: path='%s', interface='%s', member='%s')...",
+      SAFESTR(path), SAFESTR(interface), SAFESTR(member));*/
+
+    if (dbus_message_get_args(msg, NULL,
+                              DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID)) {
+        snprintf(reply, sizeof(reply), "ACK: got '%s'", message);
+        if (!send_dbus_reply(c, msg, reply))
+            fatal("failed to sent reply to DBUS message");
+    }
+
+    return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+
+
+static void setup_dbus_server(mrp_mainloop_t *ml)
+{
+    static struct DBusObjectPathVTable vtable = {
+        .message_function = dispatch_method
+    };
+
+    char *addr = "org.murphy.test";
+
+    MRP_UNUSED(ml);
+
+    if ((dbus_test.conn = connect_to_dbus(addr)) == NULL)
+        fatal("failed to connect to DBUS");
+
+    if (!mrp_setup_dbus_connection(ml, dbus_test.conn))
+        fatal("failed to setup DBUS connection with mainloop");
+
+    if (!dbus_connection_register_fallback(dbus_test.conn, "/", &vtable, NULL))
+        fatal("failed to set up method dispatching");
+
+    if (write(dbus_test.pipe[1], addr, strlen(addr) + 1) < 0) {
+        /* just ignore it... */
+    }
+
+    cfg.nrunning++;
+}
+
+
+
+static void fork_dbus_client(mrp_mainloop_t *ml)
+{
+    dbus_test.client = cfg.child = fork();
+
+    switch (dbus_test.client) {
+    case -1:
+        fatal("failed to fork DBUS test client");
+        break;
+
+    case 0:
+        setup_dbus_client(ml);
+        break;
+
+    default:
+        info("DBUS test: child pid %u", dbus_test.client);
+        close(0);
+        /*sleep(10);*/
+        setup_dbus_server(ml);
+    }
+}
+
+
+static void sigchild_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    int status;
+
+    MRP_UNUSED(user_data);
+
+    info("DBUS test: received signal %d (%s)", signum, signames[signum]);
+
+    if (dbus_test.client != 0) {
+        if (waitpid(dbus_test.client, &status, WNOHANG) == dbus_test.client) {
+            info("DBUS test: client exited with status %d.", status);
+            dbus_test.client = 0;
+            close_dbus_pipe("w");
+            mrp_del_sighandler(h);
+            cfg.nrunning--;
+        }
+        else
+            error("waitpid failed for pid %u", dbus_test.client);
+    }
+}
+
+
+static void setup_dbus_tests(mrp_mainloop_t *ml)
+{
+    mrp_sighandler_t *h;
+
+    if (cfg.ndbus_method == 0 && cfg.ndbus_signal == 0)
+        return;
+
+    if ((h = mrp_add_sighandler(ml, SIGCHLD, sigchild_handler, NULL)) != NULL) {
+        open_dbus_pipe();
+        fork_dbus_client(ml);
+    }
+    else
+        fatal("failed create SIGCHLD handler");
+}
+
+
+static void check_dbus(void)
+{
+    if (cfg.ndbus_method == 0 && cfg.ndbus_signal == 0)
+        return;
+
+    if (dbus_test.client != 0) {
+        if (dbus_test.nmethod == cfg.ndbus_method)
+            info("DBUS test: method calls: OK (%d/%d)",
+                 dbus_test.nmethod, cfg.ndbus_method);
+        else
+            error("DBUS test: method calls: FAILED (%d/%d)",
+                  dbus_test.nmethod, cfg.ndbus_method);
+    }
+    else {
+        if (dbus_test.nack == cfg.ndbus_method)
+            info("DBUS test: method replies: OK (%d/%d)",
+                 dbus_test.nack, cfg.ndbus_method);
+        else
+            error("DBUS test: method replies: FAILED (%d/%d)",
+                  dbus_test.nack, cfg.ndbus_method);
+    }
+}
+
+
+
+#include "dbus-pump.c"
+
+
+
+static void config_set_defaults(test_config_t *cfg)
+{
+    mrp_clear(cfg);
+
+    cfg->nio     = 5;
+    cfg->ntimer  = 10;
+    cfg->nsignal = 5;
+    cfg->ngio    = 5;
+    cfg->ngtimer = 10;
+
+    cfg->ndbus_method = 10;
+    cfg->ndbus_signal = 10;
+
+    cfg->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    cfg->log_target = MRP_LOG_TO_STDERR;
+
+    cfg->wlpf = 1750;
+    cfg->wfrc = 5000;
+
+    cfg->runtime = DEFAULT_RUNTIME;
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -r, --runtime                  how many seconds to run tests\n"
+           "  -i, --ios                      number of I/O watches\n"
+           "  -t, --timers                   number of timers\n"
+           "  -s, --signals                  number of POSIX signals\n"
+           "  -I, --glib-ios                 number of glib I/O watches\n"
+           "  -T, --glib-timers              number of glib timers\n"
+           "  -S, --dbus-signals             number of D-Bus signals\n"
+           "  -M, --dbus-methods             number of D-Bus methods\n"
+           "  -o, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug site               enable debug messages for <site>\n"
+#ifdef PULSE_ENABLED
+           "  -p, --pulse                    use pulse mainloop\n"
+#endif
+#ifdef ECORE_ENABLED
+           "  -e, --ecore                    use ecore mainloop\n"
+#endif
+#ifdef GLIB_ENABLED
+           "  -g, --glib                     use glib mainloop\n"
+#endif
+#ifdef QT_ENABLED
+           "  -q, --qt                       use qt mainloop\n"
+#endif
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+int parse_cmdline(test_config_t *cfg, int argc, char **argv)
+{
+#ifdef PULSE_ENABLED
+#   define PULSE_OPTION "p"
+#else
+#   define PULSE_OPTION ""
+#endif
+#ifdef ECORE_ENABLED
+#   define ECORE_OPTION "e"
+#else
+#   define ECORE_OPTION ""
+#endif
+#ifdef GLIB_ENABLED
+#    define GLIB_OPTION "g"
+#else
+#    define GLIB_OPTION ""
+#endif
+#ifdef QT_ENABLED
+#    define QT_OPTION "q"
+#else
+#    define QT_OPTION ""
+#endif
+
+
+#   define OPTIONS "r:i:t:s:I:T:S:M:l:w:W:o:vd:h" \
+        PULSE_OPTION""ECORE_OPTION""GLIB_OPTION""QT_OPTION
+    struct option options[] = {
+        { "runtime"     , required_argument, NULL, 'r' },
+        { "ios"         , required_argument, NULL, 'i' },
+        { "timers"      , required_argument, NULL, 't' },
+        { "signals"     , required_argument, NULL, 's' },
+        { "glib-ios"    , required_argument, NULL, 'I' },
+        { "glib-timers" , required_argument, NULL, 'T' },
+        { "dbus-signals", required_argument, NULL, 'S' },
+        { "dbus-methods", required_argument, NULL, 'M' },
+#ifdef PULSE_ENABLED
+        { "pulse"       , no_argument      , NULL, 'p' },
+#endif
+#ifdef ECORE_ENABLED
+        { "ecore"       , no_argument      , NULL, 'e' },
+#endif
+#ifdef GLIB_ENABLED
+        { "glib"        , no_argument      , NULL, 'g' },
+#endif
+#ifdef QT_ENABLED
+        { "qt"          , no_argument      , NULL, 'q' },
+#endif
+        { "wakeup-lpf"  , required_argument, NULL, 'w' },
+        { "wakeup-force", required_argument, NULL, 'W' },
+        { "log-level"   , required_argument, NULL, 'l' },
+        { "log-target"  , required_argument, NULL, 'o' },
+        { "verbose"     , optional_argument, NULL, 'v' },
+        { "debug"       , required_argument, NULL, 'd' },
+        { "help"        , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+    char *end;
+    int   opt;
+
+    config_set_defaults(cfg);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'r':
+            cfg->runtime = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid runtime length '%s'.", optarg);
+            break;
+
+        case 'i':
+            cfg->nio = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of I/O watches '%s'.", optarg);
+            break;
+
+        case 't':
+            cfg->ntimer = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of timers '%s'.", optarg);
+            break;
+
+        case 's':
+            cfg->nsignal = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of signals '%s'.", optarg);
+            break;
+
+        case 'I':
+            cfg->ngio = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of glib I/O watches '%s'.", optarg);
+            break;
+
+        case 'T':
+            cfg->ngtimer = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of glib timers '%s'.", optarg);
+            break;
+
+        case 'S':
+            cfg->ndbus_signal = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of DBUS signals '%s'.", optarg);
+            break;
+
+        case 'M':
+            cfg->ndbus_method = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid number of DBUS methods '%s'.", optarg);
+            break;
+
+#ifdef PULSE_ENABLED
+        case 'p':
+            cfg->mainloop_type = MAINLOOP_PULSE;
+            break;
+#endif
+
+#ifdef ECORE_ENABLED
+        case 'e':
+            cfg->mainloop_type = MAINLOOP_ECORE;
+            break;
+#endif
+
+#ifdef GLIB_ENABLED
+        case 'g':
+            cfg->mainloop_type = MAINLOOP_GLIB;
+            break;
+#endif
+
+#ifdef QT_ENABLED
+        case 'q':
+            cfg->mainloop_type = MAINLOOP_QT;
+            break;
+#endif
+
+        case 'w':
+            cfg->wlpf = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid wakeup low-pass filter limit '%s'.",
+                            optarg);
+            break;
+
+        case 'W':
+            cfg->wfrc = (int)strtoul(optarg, &end, 10);
+            if (end && *end)
+                print_usage(argv[0], EINVAL,
+                            "invalid wakeup force trigger limit '%s'.",
+                            optarg);
+            break;
+
+        case 'v':
+            cfg->log_mask <<= 1;
+            cfg->log_mask  |= 1;
+            break;
+
+        case 'l':
+            cfg->log_mask = mrp_log_parse_levels(optarg);
+            if (cfg->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 'o':
+            cfg->log_target = mrp_log_parse_target(optarg);
+            if (!cfg->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            cfg->log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+static mrp_mainloop_t *mainloop_create(test_config_t *cfg)
+{
+    switch (cfg->mainloop_type) {
+    case MAINLOOP_NATIVE:
+        cfg->ml = mrp_mainloop_create();
+        break;
+
+    case MAINLOOP_PULSE:
+        pulse_mainloop_create(cfg);
+        break;
+
+    case MAINLOOP_ECORE:
+        ecore_mainloop_create(cfg);
+        break;
+
+    case MAINLOOP_GLIB:
+        glib_mainloop_create(cfg);
+        break;
+
+#ifdef QT_ENABLED
+    case MAINLOOP_QT:
+        cfg->ml = qt_mainloop_create();
+        break;
+#endif
+
+    default:
+        mrp_log_error("Invalid mainloop type 0x%x.", cfg->mainloop_type);
+        exit(1);
+    }
+
+    if (cfg->ml == NULL) {
+        mrp_log_error("Failed to create mainloop.");
+        exit(1);
+    }
+
+    return cfg->ml;
+}
+
+
+static void mainloop_run(test_config_t *cfg)
+{
+    switch (cfg->mainloop_type) {
+    case MAINLOOP_NATIVE:
+        mrp_mainloop_run(cfg->ml);
+        break;
+
+    case MAINLOOP_PULSE:
+        pulse_mainloop_run(cfg);
+        break;
+
+    case MAINLOOP_ECORE:
+        ecore_mainloop_run(cfg);
+        break;
+
+    case MAINLOOP_GLIB:
+        glib_mainloop_run(cfg);
+        break;
+
+#ifdef QT_ENABLED
+    case MAINLOOP_QT:
+        qt_mainloop_run();
+        break;
+#endif
+
+    default:
+        mrp_log_error("Invalid mainloop type 0x%x.", cfg->mainloop_type);
+        exit(1);
+    }
+}
+
+
+static void mainloop_quit(test_config_t *cfg)
+{
+    switch (cfg->mainloop_type) {
+    case MAINLOOP_NATIVE:
+        mrp_mainloop_quit(cfg->ml, 0);
+        break;
+
+    case MAINLOOP_PULSE:
+        pulse_mainloop_quit(cfg);
+        break;
+
+    case MAINLOOP_ECORE:
+        ecore_mainloop_quit(cfg);
+        break;
+
+    case MAINLOOP_GLIB:
+        glib_mainloop_quit(cfg);
+        break;
+
+#ifdef QT_ENABLED
+    case MAINLOOP_QT:
+        qt_mainloop_quit();
+        break;
+#endif
+
+    default:
+        mrp_log_error("Invalid mainloop type 0x%x.", cfg->mainloop_type);
+        exit(1);
+    }
+}
+
+
+void mainloop_cleanup(test_config_t *cfg)
+{
+    switch (cfg->mainloop_type) {
+    case MAINLOOP_NATIVE:
+        break;
+
+    case MAINLOOP_PULSE:
+        pulse_mainloop_cleanup(cfg);
+        break;
+
+    case MAINLOOP_ECORE:
+        ecore_mainloop_cleanup(cfg);
+        break;
+
+    case MAINLOOP_GLIB:
+        glib_mainloop_cleanup(cfg);
+        break;
+
+#ifdef QT_ENABLED
+    case MAINLOOP_QT:
+        qt_mainloop_cleanup(cfg->ml);
+        cfg->ml = NULL;
+        break;
+#endif
+
+    default:
+        mrp_log_error("Unknown mainloop type (0x%x).", cfg->mainloop_type);
+        exit(1);
+    }
+}
+
+
+int main(int argc, char *argv[])
+{
+    mrp_mainloop_t *ml;
+
+    mrp_clear(&cfg);
+    parse_cmdline(&cfg, argc, argv);
+
+    mrp_log_set_mask(cfg.log_mask);
+    mrp_log_set_target(cfg.log_target);
+
+    ml = mainloop_create(&cfg);
+
+    if (ml == NULL)
+        fatal("failed to create main loop.");
+
+    dbus_test.ml = ml;
+    setup_dbus_tests(ml);
+    ml = dbus_test.ml;
+
+    setup_timers(ml);
+    setup_io(ml);
+    setup_signals(ml);
+    MRP_UNUSED(setup_deferred);   /* XXX TODO: add deferred tests... */
+
+#ifdef GLIB_ENABLED
+    if (cfg.mainloop_type != MAINLOOP_GLIB && cfg.mainloop_type != MAINLOOP_QT) {
+        if (cfg.ngio > 0 || cfg.ngtimer > 0)
+            glib_pump_setup(ml);
+    }
+
+    setup_glib_io();
+    setup_glib_timers();
+#endif
+
+    if (mrp_add_timer(ml, 1000, check_quit, NULL) == NULL)
+        fatal("failed to create quit-check timer");
+
+    setup_wakeup(ml);
+
+    mainloop_run(&cfg);
+
+    check_io();
+    check_timers();
+    check_signals();
+
+#ifdef GLIB_ENABLED
+    check_glib_io();
+    check_glib_timers();
+#endif
+
+    if (dbus_test.client != 0)
+        close(dbus_test.pipe[1]);   /* let the client continue */
+
+    check_dbus();
+
+#ifdef GLIB_ENABLED
+    if (cfg.mainloop_type != MAINLOOP_GLIB) {
+        if (cfg.ngio > 0 || cfg.ngtimer > 0)
+            glib_pump_cleanup();
+    }
+#endif
+
+    cleanup_wakeup();
+
+    mainloop_cleanup(&cfg);
+}
diff --git a/src/common/tests/mask-test.c b/src/common/tests/mask-test.c
new file mode 100644 (file)
index 0000000..e4db6d0
--- /dev/null
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/types.h>
+
+#include <murphy/common/mask.h>
+
+int main(int argc, char *argv[])
+{
+    uint64_t   bits;
+    int        i, j, prev, set, n, cnt, clr, bit;
+    mrp_mask_t m = MRP_MASK_EMPTY, m1;
+    int        b[] = { 0, 1, 5, 16, 32, 48, 97, 112, 113, 114, 295, 313, -1 };
+
+    cnt = argc > 1 ? strtoul(argv[1], NULL, 10) : 100;
+
+    srand((unsigned int)time(NULL) ^ (unsigned int)getpid());
+
+    bits = 0x17;
+    bits <<= 35;
+    n = mrp_ffsll(bits);
+    printf("ffsl(0x%lx) = %d\n", bits, n);
+
+    for (i = 0; i < cnt; i++) {
+        bits = (unsigned long)rand();
+        n    = mrp_ffsll(bits);
+        clr  = ~((((unsigned long)-1) >> (n - 1)) << (n - 1));
+
+        if (n > 1) {
+            if ((bits & clr) != 0) {
+            fail:
+                printf("ffs(0x%lx) = %d: FAIL\n", bits, n);
+                exit(1);
+            }
+            else
+                printf("ffs(0x%lx) = %d: OK\n", bits, n);
+        }
+
+        if (n != __builtin_ffsl(bits))
+            goto fail;
+
+    }
+
+    for (i = 0; b[i] != -1; i++) {
+        printf("setting bit %d...\n", b[i]);
+        mrp_mask_set(&m, b[i]);
+        if (!mrp_mask_test(&m, b[i])) {
+            printf("testing bit %d: FAILED\n", b[i]);
+            exit(1);
+        }
+    }
+
+
+    prev = 0;
+    for (i = 0; b[i] != -1; i++) {
+        for (j = prev + 1; j < b[i]; j++) {
+            set = mrp_mask_test(&m, j);
+            if (set) {
+                printf("negative mask_test(%d): FAILED\n", j);
+                exit(1);
+            }
+        }
+
+        set = mrp_mask_test(&m, b[i]);
+
+        if (!set) {
+            printf("mask_test(%d): FAILED\n", b[i]);
+            exit(1);
+        }
+
+        prev = b[i];
+    }
+
+    printf("mask tests: OK\n");
+
+    MRP_MASK_FOREACH_SET(&m, bit, 0) {
+        printf("next bit set: %d\n", bit);
+    }
+
+    MRP_MASK_FOREACH_CLEAR(&m, bit, 150) {
+        printf("next bit clear: %d\n", bit);
+    }
+
+    mrp_mask_neg(&m);
+    MRP_MASK_FOREACH_CLEAR(&m, bit, 150) {
+        printf("next bit clear: %d\n", bit);
+    }
+
+    MRP_MASK_FOREACH_CLEAR(&m, bit, 0) {
+        printf("next bit clear (negated): %d\n", bit);
+    }
+
+    mrp_mask_neg(&m);
+
+    mrp_mask_copy(&m1, &m);
+    mrp_mask_neg(&m1);
+    mrp_mask_or(&m1, &m);
+
+    MRP_MASK_FOREACH_SET(&m1, bit, 0) {
+        printf("next bit set (or'd): %d\n", bit);
+    }
+
+    mrp_mask_copy(&m1, &m);
+    mrp_mask_neg(&m1);
+    mrp_mask_xor(&m1, &m);
+
+    MRP_MASK_FOREACH_SET(&m1, bit, 0) {
+        printf("next bit set (neg'd+xor'd): %d\n", bit);
+    }
+
+    mrp_mask_copy(&m1, &m);
+    mrp_mask_neg(&m1);
+    mrp_mask_and(&m1, &m);
+
+    MRP_MASK_FOREACH_SET(&m1, bit, 0) {
+        printf("next bit set (neg'd+and'd): %d\n", bit);
+    }
+
+    mrp_mask_copy(&m1, &m);
+    mrp_mask_and(&m1, &m);
+
+    MRP_MASK_FOREACH_SET(&m1, bit, 0) {
+        printf("next bit set (and'd): %d\n", bit);
+    }
+
+    mrp_mask_reset(&m);
+
+    return 0;
+}
diff --git a/src/common/tests/mkdir-test.c b/src/common/tests/mkdir-test.c
new file mode 100644 (file)
index 0000000..611b82a
--- /dev/null
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+
+#include <murphy/common/file-utils.h>
+
+int main(int argc, char *argv[])
+{
+    int i;
+
+    for (i = 1; i < argc; i++) {
+        printf("Trying to create directory '%s'..\n", argv[i]);
+        if (mrp_mkdir(argv[i], 0755) < 0)
+            printf("failed (%d: %s)\n", errno, strerror(errno));
+        else
+            printf("ok\n");
+    }
+
+    return 0;
+}
diff --git a/src/common/tests/mm-test.c b/src/common/tests/mm-test.c
new file mode 100644 (file)
index 0000000..c22b866
--- /dev/null
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <murphy/common/mm.h>
+
+#define fatal(fmt, args...) do {                                          \
+        fprintf(stderr, "fatal error: "fmt"\n" , ## args);                \
+        exit(1);                                                          \
+    } while (0)
+
+#define error(fmt, args...) do {                                          \
+        fprintf(stdout, "error: "fmt"\n" , ## args);                      \
+    } while (0)
+
+#define info(fmt, args...) do {                                           \
+        fprintf(stdout, fmt"\n" , ## args);                               \
+    } while (0)
+
+
+
+static int basic_tests(int n)
+{
+    void **ptrs;
+    char   buf[1024], *p;
+    int    i;
+
+    mrp_mm_config(MRP_MM_DEBUG);
+
+    ptrs = mrp_allocz(n * sizeof(*ptrs));
+
+    if (ptrs == NULL)
+        fatal("Failed to allocate pointer table.");
+
+    for (i = 0; i < n; i++) {
+        snprintf(buf, sizeof(buf), "#%d: message number %d (0x%x)", i, i, i);
+
+        p = ptrs[i] = mrp_strdup(buf);
+
+        if (p != NULL) {
+            if (!strcmp(buf, p)) {
+                printf("'%s' was duplicated as '%s'\n", buf, p);
+            }
+            else {
+                printf("'%s' was incorrectly duplicated as '%s'\n", buf, p);
+                return FALSE;
+            }
+        }
+        else {
+            printf("failed to duplicate '%s'\n", buf);
+            return FALSE;
+        }
+    }
+
+    mrp_mm_check(stdout);
+
+    for (i = 0; i < n; i += 2) {
+        mrp_free(ptrs[i]);
+        ptrs[i] = NULL;
+    }
+
+    mrp_mm_check(stdout);
+
+    for (i = 0; i < n; i++) {
+        mrp_free(ptrs[i]);
+        ptrs[i] = NULL;
+    }
+
+    mrp_mm_check(stdout);
+
+    mrp_free(ptrs);
+
+    mrp_mm_check(stdout);
+
+    return TRUE;
+}
+
+
+typedef struct {
+    char    name[32];
+    int     i;
+    double  d;
+    char   *s;
+    void   *p;
+} obj_t;
+
+
+#define NAME_FORMAT "#%d test object"
+#define POISON      0xf3
+
+static int obj_setup(void *ptr)
+{
+    static int  idx = 0;
+    obj_t      *obj = ptr;
+
+    snprintf(obj->name, sizeof(obj->name), NAME_FORMAT, idx);
+    obj->i = idx;
+    obj->d = 2.0 * idx;
+    obj->s = mrp_strdup(obj->name);
+    obj->p = ptr;
+
+    return TRUE;
+}
+
+
+static void obj_cleanup(void *ptr)
+{
+    obj_t *obj = ptr;
+
+    mrp_free(obj->s);
+}
+
+
+static int obj_check(obj_t *obj, int alloced)
+{
+    char name[32];
+
+    if (alloced) {
+        snprintf(name, sizeof(name), NAME_FORMAT, obj->i);
+
+        return (!strcmp(name, obj->name) && !strcmp(name, obj->s) &&
+                obj->d == 2 * obj->i && obj->p == obj);
+    }
+    else {
+        char check[sizeof(obj_t)];
+
+        memset(check, POISON, sizeof(check));
+        if (memcmp(check, obj, sizeof(*obj)))
+            error("Object %p not properly poisoned.", obj);
+    }
+
+        return TRUE;
+}
+
+
+static int pool_tests(void)
+{
+    mrp_objpool_config_t  cfg;
+    mrp_objpool_t        *pool;
+    obj_t               **ptrs;
+    int                   limit, prealloc, i, max;
+    int                   success;
+
+    limit    = 0;
+    prealloc = 512;
+    max      = 8382;
+    ptrs     = mrp_allocz(max * sizeof(obj_t));
+
+    if (ptrs == NULL) {
+        error("Failed to allocate check pointer table.");
+        return FALSE;
+    }
+
+    cfg.name     = "test pool";
+    cfg.limit    = limit;
+    cfg.objsize  = sizeof(obj_t);
+    cfg.prealloc = prealloc;
+    cfg.setup    = obj_setup;
+    cfg.cleanup  = obj_cleanup;
+    cfg.poison   = POISON;
+    cfg.flags    = MRP_OBJPOOL_FLAG_POISON;
+
+    info("Creating object pool...");
+    pool = mrp_objpool_create(&cfg);
+
+    if (pool == NULL) {
+        error("Failed to create test object pool.");
+        return FALSE;
+    }
+
+    info("Allocating objects...");
+    for (i = 0; i < max; i++) {
+        ptrs[i] = mrp_objpool_alloc(pool);
+
+        if (ptrs[i] == NULL) {
+            error("Failed to allocate test object #%d.", i);
+            success = FALSE;
+            goto out;
+        }
+
+        if (!obj_check(ptrs[i], TRUE)) {
+            error("Object check failed for %p.", ptrs[i]);
+            success = FALSE;
+        }
+    }
+
+    info("Freeing objects...");
+    for (i = 0; i < max; i += 2) {
+        mrp_objpool_free(ptrs[i]);
+        obj_check(ptrs[i], FALSE);
+        ptrs[i] = NULL;
+    }
+
+    info("Reallocating objects...");
+    for (i = 0; i < max; i += 2) {
+        ptrs[i] = mrp_objpool_alloc(pool);
+
+        if (ptrs[i] == NULL) {
+            error("Failed to re-allocate test object #%d.", i);
+            success = FALSE;
+            goto out;
+        }
+
+        if (!obj_check(ptrs[i], TRUE)) {
+            error("Object check failed for %p.", ptrs[i]);
+            success = FALSE;
+        }
+
+    }
+
+    info("Freeing objects...");
+    for (i = 0; i < max; i++) {
+        mrp_objpool_free(ptrs[i]);
+        ptrs[i] = NULL;
+    }
+
+    info("Reallocating again objects...");
+    for (i = 0; i < max; i++) {
+        ptrs[i] = mrp_objpool_alloc(pool);
+
+        if (ptrs[i] == NULL) {
+            error("Failed to re-allocate test object #%d.", i);
+            success = FALSE;
+            goto out;
+        }
+
+        if (!obj_check(ptrs[i], TRUE)) {
+            error("Object check failed for %p.", ptrs[i]);
+            success = FALSE;
+        }
+    }
+
+ out:
+    mrp_free(ptrs);
+    info("Destroying object pool...");
+    mrp_objpool_destroy(pool);
+
+    return success;
+}
+
+
+int main(int argc, char *argv[])
+{
+    int max;
+
+    if (argc > 1)
+        max = (int)strtol(argv[1], NULL, 10);
+    else
+        max = 256;
+
+    info("Running basic tests...");
+    basic_tests(max);
+
+    info("Running object pool tests...");
+    pool_tests();
+
+    return 0;
+}
diff --git a/src/common/tests/msg-test.c b/src/common/tests/msg-test.c
new file mode 100644 (file)
index 0000000..44c5cd2
--- /dev/null
@@ -0,0 +1,823 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common.h>
+
+#include <murphy/common/msg.h>
+#include <murphy/common/msg.c>
+
+#define TYPE(type, name) [MRP_MSG_FIELD_##type] = name
+const char *types[] = {
+    TYPE(INVALID, "invalid"),
+    TYPE(STRING , "string" ),
+    TYPE(BOOL   , "bool"   ),
+    TYPE(SINT8  , "sint8"  ),
+    TYPE(UINT8  , "uint8"  ),
+    TYPE(SINT16 , "sint16" ),
+    TYPE(UINT16 , "uint16" ),
+    TYPE(SINT32 , "sint32" ),
+    TYPE(UINT32 , "uint32" ),
+    TYPE(SINT64 , "sint64" ),
+    TYPE(UINT64 , "uint64" ),
+    TYPE(DOUBLE , "double" ),
+    TYPE(BLOB   , "blob"   ),
+    NULL,
+};
+#undef TYPE
+
+
+uint16_t get_type(const char **types, const char *name)
+{
+    const char **t;
+
+    for (t = types; *t != NULL; t++) {
+        if (!strcmp(*t, name))
+            return (uint16_t)(t - types);
+    }
+
+    return MRP_MSG_FIELD_INVALID;
+}
+
+
+void test_default_encode_decode(int argc, char **argv)
+{
+    mrp_msg_t *msg, *decoded;
+    void      *encoded;
+    ssize_t    size;
+    uint16_t   tag, type, prev_tag;
+    uint8_t    u8;
+    int8_t     s8;
+    uint16_t   u16;
+    int16_t    s16;
+    uint32_t   u32;
+    int32_t    s32;
+    uint64_t   u64;
+    int64_t    s64;
+    double     dbl;
+    bool       bln;
+    char      *val, *end;
+    int        i, ok;
+
+    if ((msg = mrp_msg_create_empty()) == NULL) {
+        mrp_log_error("Failed to create new message.");
+        exit(1);
+    }
+
+    prev_tag = 0;
+    i        = 1;
+    while (i < argc) {
+
+        if ('0' <= *argv[i] && *argv[i] <= '9') {
+            if (argc <= i + 2) {
+                mrp_log_error("Missing field type or value.");
+                exit(1);
+            }
+
+            tag = prev_tag = (uint16_t)strtoul(argv[i++], &end, 0);
+            if (end && *end) {
+                mrp_log_error("Invalid field tag '%s'.", argv[i]);
+                exit(1);
+            }
+        }
+        else {
+            if (argc <= i + 1) {
+                mrp_log_error("Missing field type or value.");
+                exit(1);
+            }
+
+            tag = ++prev_tag;
+        }
+
+        type = get_type(types, argv[i++]);
+        val  = argv[i++];
+
+        if (type == MRP_MSG_FIELD_INVALID) {
+            mrp_log_error("Invalid field type '%s'.", argv[i + 1]);
+            exit(1);
+        }
+
+        switch (type) {
+        case MRP_MSG_FIELD_STRING:
+            ok = mrp_msg_append(msg, tag, type, val);
+            break;
+
+        case MRP_MSG_FIELD_BOOL:
+            if (!strcasecmp(val, "true"))
+                bln = TRUE;
+            else if (!strcasecmp(val, "false"))
+                bln = FALSE;
+            else {
+                mrp_log_error("Invalid boolean value '%s'.", val);
+                exit(1);
+            }
+            ok = mrp_msg_append(msg, tag, type, bln);
+            break;
+
+#define HANDLE_INT(_bits, _uget, _sget)                                   \
+        case MRP_MSG_FIELD_UINT##_bits:                                   \
+            u##_bits = (uint##_bits##_t)strtoul(val, &end, 0);            \
+            if (end && *end) {                                            \
+                mrp_log_error("Invalid uint%d value '%s'.", _bits, val);  \
+                exit(1);                                                  \
+            }                                                             \
+            ok = mrp_msg_append(msg, tag, type, u##_bits);                \
+            break;                                                        \
+        case MRP_MSG_FIELD_SINT##_bits:                                   \
+            s##_bits = (int##_bits##_t)strtol(val, &end, 0);              \
+            if (end && *end) {                                            \
+                mrp_log_error("Invalid sint%d value '%s'.", _bits, val);  \
+                exit(1);                                                  \
+            }                                                             \
+            ok = mrp_msg_append(msg, tag, type, s##_bits);                \
+            break
+
+            HANDLE_INT(8 , strtol , strtoul);
+            HANDLE_INT(16, strtol , strtoul);
+            HANDLE_INT(32, strtol , strtoul);
+            HANDLE_INT(64, strtoll, strtoull);
+
+        case MRP_MSG_FIELD_DOUBLE:
+            dbl = strtod(val, &end);
+            if (end && *end) {
+                mrp_log_error("Invalid double value '%s'.", val);
+                exit(1);
+            }
+            ok = mrp_msg_append(msg, tag, type, dbl);
+            break;
+
+        default:
+            mrp_log_error("Invalid (or unimplemented) type 0x%x (%s).",
+                          type, argv[i + 1]);
+            ok = FALSE;
+        }
+
+        if (!ok) {
+            mrp_log_error("Failed to add field to message.");
+            exit(1);
+        }
+    }
+
+    mrp_msg_dump(msg, stdout);
+
+    size = mrp_msg_default_encode(msg, &encoded);
+    if (size <= 0) {
+        mrp_log_error("Failed to encode message with default encoder.");
+        exit(1);
+    }
+
+    mrp_log_info("encoded message size: %d", (int)size);
+
+    decoded = mrp_msg_default_decode(encoded, size);
+    if (decoded == NULL) {
+        mrp_log_error("Failed to decode message with default decoder.");
+        exit(1);
+    }
+
+    mrp_msg_dump(decoded, stdout);
+
+    mrp_msg_unref(msg);
+    mrp_msg_unref(decoded);
+}
+
+
+typedef struct {
+    char     *str1;
+    uint16_t  u16;
+    int32_t   s32;
+    char     *str2;
+    double    dbl1;
+    bool      bln1;
+    double    dbl2;
+    char     *str3;
+    bool      bln2;
+} data1_t;
+
+typedef struct {
+    char     *str;
+    uint8_t   u8;
+    bool      bln;
+} data2_t;
+
+typedef struct {
+    char     *str;
+    uint16_t  u16;
+    int32_t   s32;
+    double    dbl;
+} data3_t;
+
+#if 0
+typedef struct {
+    uint16_t offs;                       /* member offset within structure */
+    uint16_t tag;                        /* tag for member */
+    uint16_t type;                       /* type of this member */
+} mrp_msg_member_t;
+
+typedef struct {
+    uint16_t          tag;               /* structure tag */
+    size_t            size;              /* structure size */
+    int               nfield;            /* number of members */
+    mrp_msg_member_t *fields;            /* member descriptor */
+} mrp_msg_descr_t;
+#endif
+
+#define DUMP_FIELD(memb, fmt) printf("    %s: "fmt"\n", #memb, d->memb)
+
+int cmp_data1(data1_t *d1, data1_t *d2)
+{
+    return
+        !strcmp(d1->str1, d2->str1) &&
+        !strcmp(d1->str2, d2->str2) &&
+        !strcmp(d1->str3, d2->str3) &&
+        d1->u16  == d2->u16  &&
+        d1->s32  == d2->s32  &&
+        d1->dbl1 == d2->dbl1 &&
+        d1->bln1 == d2->bln1 &&
+        d1->dbl2 == d2->dbl2 &&
+        d1->bln2 == d2->bln2;
+}
+
+int cmp_data2(data2_t *d1, data2_t *d2)
+{
+    return
+        !strcmp(d1->str, d2->str) &&
+        d1->u8  == d2->u8  &&
+        d1->bln == d2->bln;
+}
+
+int cmp_data3(data3_t *d1, data3_t *d2)
+{
+    return
+        !strcmp(d1->str, d2->str) &&
+        d1->u16 == d2->u16 &&
+        d1->s32 == d2->s32 &&
+        d1->dbl == d2->dbl;
+}
+
+void dump_data1(char *prefix, data1_t *d)
+{
+    printf("%s{\n", prefix);
+    DUMP_FIELD(str1, "%s");
+    DUMP_FIELD(u16 , "%u");
+    DUMP_FIELD(s32 , "%d");
+    DUMP_FIELD(str2, "%s");
+    DUMP_FIELD(dbl1, "%f");
+    DUMP_FIELD(bln1, "%d");
+    DUMP_FIELD(dbl2, "%f");
+    DUMP_FIELD(str2, "%s");
+    DUMP_FIELD(bln2, "%d");
+    printf("}\n");
+
+}
+
+void dump_data2(char *prefix, data2_t *d)
+{
+    printf("%s{\n", prefix);
+    DUMP_FIELD(str, "%s");
+    DUMP_FIELD(u8 , "%u");
+    DUMP_FIELD(bln, "%d");
+    printf("}\n");
+}
+
+void dump_data3(char *prefix, data3_t *d)
+{
+    printf("%s{\n", prefix);
+    DUMP_FIELD(str, "%s");
+    DUMP_FIELD(u16, "%u");
+    DUMP_FIELD(s32, "%d");
+    DUMP_FIELD(dbl, "%f");
+    printf("}\n");
+}
+
+#undef DUMP_FIELD
+
+static size_t mrp_msg_encode(void **bufp, void *data,
+                             mrp_data_member_t *fields, int nfield);
+
+static void *mrp_msg_decode(void **bufp, size_t *sizep, size_t data_size,
+                            mrp_data_member_t *fields, int nfield);
+
+void test_custom_encode_decode(void)
+{
+#define DESCRIBE(_type, _memb, _tag, _ftype) {                            \
+        .offs  = MRP_OFFSET(_type, _memb),                                \
+        .tag   = _tag,                                                    \
+        .type  = MRP_MSG_FIELD_##_ftype,                                  \
+        .guard = FALSE,                                                   \
+        { NULL },                                                         \
+        .hook  = { NULL, NULL }                                           \
+ }
+
+    mrp_data_member_t data1_descr[] = {
+        DESCRIBE(data1_t, str1, 0x1, STRING),
+        DESCRIBE(data1_t,  u16, 0x2, UINT16),
+        DESCRIBE(data1_t, str1, 0x1, STRING),
+        DESCRIBE(data1_t, u16 , 0x2, UINT16),
+        DESCRIBE(data1_t, s32 , 0x3, SINT32),
+        DESCRIBE(data1_t, str2, 0x4, STRING),
+        DESCRIBE(data1_t, dbl1, 0x5, DOUBLE),
+        DESCRIBE(data1_t, bln1, 0x6, BOOL  ),
+        DESCRIBE(data1_t, dbl2, 0x7, DOUBLE),
+        DESCRIBE(data1_t, str3, 0x8, STRING),
+        DESCRIBE(data1_t, bln2, 0x9, BOOL  ),
+    };
+    int data1_nfield = MRP_ARRAY_SIZE(data1_descr);
+
+    mrp_data_member_t data2_descr[] = {
+        DESCRIBE(data2_t, str, 0x1, STRING),
+        DESCRIBE(data2_t, u8 , 0x2, UINT8 ),
+        DESCRIBE(data2_t, bln, 0x3, BOOL  ),
+    };
+    int data2_nfield = MRP_ARRAY_SIZE(data2_descr);
+
+    mrp_data_member_t data3_descr[] = {
+        DESCRIBE(data3_t, str, 0x1, STRING),
+        DESCRIBE(data3_t, u16, 0x2, UINT16),
+        DESCRIBE(data3_t, s32, 0x3, SINT32),
+        DESCRIBE(data3_t, dbl, 0x4, DOUBLE),
+    };
+    int data3_nfield = MRP_ARRAY_SIZE(data3_descr);
+
+#define TAG_DATA1 0x1
+#define TAG_DATA2 0x2
+#define TAG_DATA3 0x3
+
+
+    data1_t data1 = {
+        .str1 = "data1, str1",
+        .u16  = 32768U,
+        .s32  = -12345678,
+        .str2 = "data1, str2",
+        .dbl1 = 9.81,
+        .bln1 = TRUE,
+        .dbl2 = -3.141,
+        .str3 = "data1, str3",
+        .bln2 = FALSE
+    };
+    data2_t data2 = {
+        .str = "data2, str",
+        .u8  = 128,
+        .bln = TRUE
+    };
+    data3_t data3 = {
+        .str = "data3, str",
+        .u16 = 32768U,
+        .s32 = -12345678,
+        .dbl = 1.2345
+    };
+
+    data1_t *d1;
+    data2_t *d2;
+    data3_t *d3;
+    void    *buf;
+    size_t  size;
+
+    size = mrp_msg_encode(&buf, &data1, data1_descr, data1_nfield);
+
+    if (size <= 0) {
+        mrp_log_error("failed to encode data1_t");
+        exit(1);
+    }
+
+    d1 = mrp_msg_decode(&buf, &size, sizeof(data1_t), data1_descr,data1_nfield);
+
+    if (d1 == NULL) {
+        mrp_log_error("failed to decode encoded data1_t");
+        exit(1);
+    }
+
+    dump_data1("original data1: ", &data1);
+    dump_data1("decoded  data1: ", d1);
+    if (!cmp_data1(&data1, d1)) {
+        mrp_log_error("Original and decoded data1_t do not match!");
+        exit(1);
+    }
+    else
+        mrp_log_info("ok, original and decoded match...");
+
+
+    size = mrp_msg_encode(&buf, &data2, data2_descr, data2_nfield);
+
+    if (size <= 0) {
+        mrp_log_error("failed to encode data2_t");
+        exit(1);
+    }
+
+    d2 = mrp_msg_decode(&buf, &size, sizeof(data2_t), data2_descr,data2_nfield);
+
+    if (d2 == NULL) {
+        mrp_log_error("failed to decode encoded data2_t");
+        exit(1);
+    }
+
+    dump_data2("original data2: ", &data2);
+    dump_data2("decoded  data2: ", d2);
+    if (!cmp_data2(&data2, d2)) {
+        mrp_log_error("Original and decoded data2_t do not match!");
+        exit(1);
+    }
+    else
+        mrp_log_info("ok, original and decoded match...");
+
+
+    size = mrp_msg_encode(&buf, &data3, data3_descr, data3_nfield);
+
+    if (size <= 0) {
+        mrp_log_error("failed to encode data3_t");
+        exit(1);
+    }
+
+    d3 = mrp_msg_decode(&buf, &size, sizeof(data3_t), data3_descr,data3_nfield);
+
+    if (d3 == NULL) {
+        mrp_log_error("failed to decode encoded data3_t");
+        exit(1);
+    }
+
+    dump_data3("original data3: ", &data3);
+    dump_data3("decoded  data3: ", d3);
+    if (!cmp_data3(&data3, d3)) {
+        mrp_log_error("Original and decoded data3_t do not match!");
+        exit(1);
+    }
+    else
+        mrp_log_info("ok, original and decoded match...");
+}
+
+
+static void test_basic(void)
+{
+    mrp_msg_t *msg;
+    char      *str1, *str2;
+    uint16_t   u16;
+    int16_t    s16;
+    uint32_t   u32;
+    int32_t    s32;
+    double     dbl1, dbl2;
+    int        i;
+
+    struct field_t {
+        uint16_t  tag;
+        uint16_t  type;
+        void     *ptr;
+    } f[] = {
+        { 0x1, MRP_MSG_FIELD_STRING, &str1 },
+        { 0x2, MRP_MSG_FIELD_STRING, &str2 },
+        { 0x3, MRP_MSG_FIELD_UINT16, &u16  },
+        { 0x4, MRP_MSG_FIELD_SINT16, &s16  },
+        { 0x5, MRP_MSG_FIELD_UINT32, &u32  },
+        { 0x6, MRP_MSG_FIELD_SINT32, &s32  },
+        { 0x7, MRP_MSG_FIELD_DOUBLE, &dbl1 },
+        { 0x8, MRP_MSG_FIELD_DOUBLE, &dbl2 }
+    };
+
+    msg = mrp_msg_create(MRP_MSG_TAG_STRING(0x1, "string 0x1"),
+                         MRP_MSG_TAG_STRING(0x2, "string 0x2"),
+                         MRP_MSG_TAG_UINT16(0x3,  3),
+                         MRP_MSG_TAG_SINT16(0x4, -4),
+                         MRP_MSG_TAG_UINT32(0x5,  5),
+                         MRP_MSG_TAG_SINT32(0x6, -6),
+                         MRP_MSG_TAG_DOUBLE(0x7,  3.14),
+                         MRP_MSG_TAG_DOUBLE(0x8, -9.81),
+                         MRP_MSG_END);
+
+    if (msg == NULL) {
+        mrp_log_error("Failed to create message.");
+        exit(1);
+    }
+    else
+        mrp_log_info("Message created OK.");
+
+
+    if (!mrp_msg_get(msg,
+                     0x1, MRP_MSG_FIELD_STRING, &str1,
+                     0x2, MRP_MSG_FIELD_STRING, &str2,
+                     0x3, MRP_MSG_FIELD_UINT16, &u16,
+                     0x4, MRP_MSG_FIELD_SINT16, &s16,
+                     0x5, MRP_MSG_FIELD_UINT32, &u32,
+                     0x6, MRP_MSG_FIELD_SINT32, &s32,
+                     0x7, MRP_MSG_FIELD_DOUBLE, &dbl1,
+                     0x8, MRP_MSG_FIELD_DOUBLE, &dbl2,
+                     MRP_MSG_END)) {
+        mrp_log_error("Failed to get message fields.");
+        exit(1);
+    }
+    else {
+        mrp_log_info("Got message fields:");
+        mrp_log_info("  str1='%s', str2='%s'", str1, str2);
+        mrp_log_info("  u16=%u, s16=%d", u16, s16);
+        mrp_log_info("  u32=%u, s32=%d", u32, s32);
+        mrp_log_info("  dbl1=%f, dbl2=%f", dbl1, dbl2);
+    }
+
+    if (!mrp_msg_get(msg,
+                     0x8, MRP_MSG_FIELD_DOUBLE, &dbl2,
+                     0x7, MRP_MSG_FIELD_DOUBLE, &dbl1,
+                     0x6, MRP_MSG_FIELD_SINT32, &s32,
+                     0x5, MRP_MSG_FIELD_UINT32, &u32,
+                     0x4, MRP_MSG_FIELD_SINT16, &s16,
+                     0x3, MRP_MSG_FIELD_UINT16, &u16,
+                     0x2, MRP_MSG_FIELD_STRING, &str2,
+                     0x1, MRP_MSG_FIELD_STRING, &str1,
+                     MRP_MSG_END)) {
+        mrp_log_error("Failed to get message fields.");
+        exit(1);
+    }
+    else {
+        mrp_log_info("Got message fields:");
+        mrp_log_info("  str1='%s', str2='%s'", str1, str2);
+        mrp_log_info("  u16=%u, s16=%d", u16, s16);
+        mrp_log_info("  u32=%u, s32=%d", u32, s32);
+        mrp_log_info("  dbl1=%f, dbl2=%f", dbl1, dbl2);
+    }
+
+
+#define TAG(idx) f[(idx) & 0x7].tag
+#define TYPE(idx) f[(idx) & 0x7].type
+#define PTR(idx) f[(idx) & 0x7].ptr
+#define FIELD(idx) TAG((idx)), TYPE((idx)), PTR((idx))
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(f); i++) {
+        if (!mrp_msg_get(msg,
+                         FIELD(i+0), FIELD(i+1), FIELD(i+2), FIELD(i+3),
+                         FIELD(i+4), FIELD(i+5), FIELD(i+6), FIELD(i+7),
+                         MRP_MSG_END)) {
+            mrp_log_error("Failed to get message fields for offset %d.", i);
+            exit(1);
+        }
+        else {
+            mrp_log_info("Got message fields for offset %d:", i);
+            mrp_log_info("  str1='%s', str2='%s'", str1, str2);
+            mrp_log_info("  u16=%u, s16=%d", u16, s16);
+            mrp_log_info("  u32=%u, s32=%d", u32, s32);
+            mrp_log_info("  dbl1=%f, dbl2=%f", dbl1, dbl2);
+        }
+    }
+
+    if (mrp_msg_get(msg,
+                    0x9, MRP_MSG_FIELD_STRING, &str1, MRP_MSG_END)) {
+        mrp_log_error("Hmm... non-existent field found.");
+        exit(1);
+    }
+    else
+        mrp_log_info("Ok, non-existent field not found...");
+}
+
+
+int main(int argc, char *argv[])
+{
+    mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_DEBUG));
+    mrp_log_set_target(MRP_LOG_TO_STDOUT);
+
+    test_basic();
+
+    test_default_encode_decode(argc, argv);
+    test_custom_encode_decode();
+
+    return 0;
+}
+
+
+static size_t mrp_msg_encode(void **bufp, void *data,
+                             mrp_data_member_t *fields, int nfield)
+{
+    mrp_data_member_t *f;
+    mrp_msgbuf_t       mb;
+    mrp_msg_value_t   *v;
+    uint32_t           len;
+    int                i;
+    size_t             size;
+
+    size = nfield * (2 * sizeof(uint16_t) + sizeof(uint64_t));
+
+    if (mrp_msgbuf_write(&mb, size)) {
+        for (i = 0, f = fields; i < nfield; i++, f++) {
+            MRP_MSGBUF_PUSH(&mb, htobe16(f->tag) , 1, nomem);
+
+            v = (mrp_msg_value_t *)(data + f->offs);
+
+            switch (f->type) {
+            case MRP_MSG_FIELD_STRING:
+                len = strlen(v->str) + 1;
+                MRP_MSGBUF_PUSH(&mb, htobe32(len), 1, nomem);
+                MRP_MSGBUF_PUSH_DATA(&mb, v->str, len, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_BOOL:
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->bln ? TRUE : FALSE), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT8:
+                MRP_MSGBUF_PUSH(&mb, v->u8, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT8:
+                MRP_MSGBUF_PUSH(&mb, v->s8, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT16:
+                MRP_MSGBUF_PUSH(&mb, htobe16(v->u16), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT16:
+                MRP_MSGBUF_PUSH(&mb, htobe16(v->s16), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT32:
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->u32), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT32:
+                MRP_MSGBUF_PUSH(&mb, htobe32(v->s32), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_UINT64:
+                MRP_MSGBUF_PUSH(&mb, htobe64(v->u64), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_SINT64:
+                MRP_MSGBUF_PUSH(&mb, htobe64(v->s64), 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_DOUBLE:
+                MRP_MSGBUF_PUSH(&mb, v->dbl, 1, nomem);
+                break;
+
+            case MRP_MSG_FIELD_BLOB:
+                errno = EOPNOTSUPP;
+                /* intentional fall through */
+
+            default:
+                if (f->type & MRP_MSG_FIELD_ARRAY) {
+                    errno = EOPNOTSUPP;
+                    mrp_log_error("XXX TODO: MRP_MSG_FIELD_ARRAY "
+                                  "not implemented");
+                }
+                else
+                    errno = EINVAL;
+
+                mrp_msgbuf_cancel(&mb);
+            nomem:
+                *bufp = NULL;
+                return 0;
+            }
+        }
+    }
+
+    *bufp = mb.buf;
+    return (size_t)(mb.p - mb.buf);
+}
+
+
+#if 0
+static mrp_data_member_t *member_type(mrp_data_member_t *fields, int nfield,
+                                      uint16_t tag)
+{
+    mrp_data_member_t *f;
+    int                i;
+
+    for (i = 0, f = fields; i < nfield; i++, f++)
+        if (f->tag == tag)
+            return f;
+
+    return NULL;
+}
+#endif
+
+static void *mrp_msg_decode(void **bufp, size_t *sizep, size_t data_size,
+                            mrp_data_member_t *fields, int nfield)
+{
+    void              *data;
+    mrp_data_member_t *f;
+    mrp_msgbuf_t       mb;
+    uint16_t           tag;
+    mrp_msg_value_t   *v;
+    void              *value;
+    uint32_t           len;
+    int                i;
+
+    if (MRP_UNLIKELY((data = mrp_allocz(data_size)) == NULL))
+        return NULL;
+
+    mrp_msgbuf_read(&mb, *bufp, *sizep);
+
+    for (i = 0; i < nfield; i++) {
+        tag = be16toh(MRP_MSGBUF_PULL(&mb, typeof(tag) , 1, nodata));
+        f   = member_type(fields, nfield, tag);
+
+        if (MRP_UNLIKELY(f == NULL))
+            goto unknown_field;
+
+        v = (mrp_msg_value_t *)(data + f->offs);
+
+        switch (f->type) {
+        case MRP_MSG_FIELD_STRING:
+            len    = be32toh(MRP_MSGBUF_PULL(&mb, typeof(len), 1, nodata));
+            value  = MRP_MSGBUF_PULL_DATA(&mb, len, 1, nodata);
+            v->str = mrp_strdup((char *)value);
+            if (v->str == NULL)
+                goto nomem;
+            break;
+
+        case MRP_MSG_FIELD_BOOL:
+            v->bln = be32toh(MRP_MSGBUF_PULL(&mb, uint32_t, 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_UINT8:
+            v->u8 = MRP_MSGBUF_PULL(&mb, typeof(v->u8), 1, nodata);
+            break;
+
+        case MRP_MSG_FIELD_SINT8:
+            v->s8 = MRP_MSGBUF_PULL(&mb, typeof(v->s8), 1, nodata);
+            break;
+
+        case MRP_MSG_FIELD_UINT16:
+            v->u16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->u16), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_SINT16:
+            v->s16 = be16toh(MRP_MSGBUF_PULL(&mb, typeof(v->s16), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_UINT32:
+            v->u32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->u32), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_SINT32:
+            v->s32 = be32toh(MRP_MSGBUF_PULL(&mb, typeof(v->s32), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_UINT64:
+            v->u64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->u64), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_SINT64:
+            v->s64 = be64toh(MRP_MSGBUF_PULL(&mb, typeof(v->s64), 1, nodata));
+            break;
+
+        case MRP_MSG_FIELD_DOUBLE:
+            v->dbl = MRP_MSGBUF_PULL(&mb, typeof(v->dbl), 1, nodata);
+            break;
+
+        case MRP_MSG_FIELD_BLOB:
+            errno = EOPNOTSUPP;
+        default:
+            if (f->type & MRP_MSG_FIELD_ARRAY) {
+                errno = EOPNOTSUPP;
+                mrp_log_error("XXX TODO: MRP_MSG_FIELD_ARRAY "
+                              "not implemented");
+            }
+            else {
+            unknown_field:
+                errno = EINVAL;
+            }
+            goto fail;
+        }
+    }
+
+    *bufp   = mb.buf;
+    *sizep -= mb.p - mb.buf;
+    return data;
+
+ nodata:
+ nomem:
+ fail:
+    if (data != NULL) {
+        for (i = 0, f = fields; i < nfield; i++, f++) {
+            switch (f->type) {
+            case MRP_MSG_FIELD_STRING:
+            case MRP_MSG_FIELD_BLOB:
+                mrp_free(data + f->offs);
+            }
+        }
+
+        mrp_free(data);
+    }
+
+    return NULL;
+}
diff --git a/src/common/tests/native-test.c b/src/common/tests/native-test.c
new file mode 100644 (file)
index 0000000..171f5a4
--- /dev/null
@@ -0,0 +1,307 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/native-types.h>
+
+
+typedef enum {
+    MUSIC,
+    MOVIE,
+    BOOK,
+    PAINTING,
+} art_type_t;
+
+
+typedef struct {
+    art_type_t  type;
+    char       *artist;
+    char       *title;
+    uint16_t    year;
+    char       *location;
+    double      price;
+} art_t;
+
+
+typedef enum {
+    LEFT = 0,
+    RIGHT,
+    BOTH
+} hand_t;
+
+
+typedef enum {
+    MALE = 0,
+    FEMALE = 1,
+} gender_t;
+
+
+typedef struct {
+    char          *name;
+    gender_t       gender;
+    int            age;
+    char         **languages;
+    unsigned int   height;
+    float          weight;
+    char           nationality[32];
+    hand_t         hand;
+    bool           glasses;
+    art_t         *favourites;
+    size_t         nfavourite;
+} person_t;
+
+
+typedef struct {
+    person_t *father;
+    person_t *mother;
+    person_t *children;
+} family_t;
+
+
+art_t paps_favourites[] = {
+    {
+        BOOK ,
+        "Douglas Adams", "Dirk Gently's Holistic Detective Agency",
+        1987, "bookshelf", 9.5
+    },
+    {
+        MUSIC,
+        "Megadeth", "Sweating Bullets",
+        1992, "pocket", 12.5
+    },
+    {
+        MUSIC,
+        "Sentenced", "Noose",
+        1996, "phone", 12
+    },
+    {
+        MOVIE,
+        "Bananas", "Woody Allen",
+        1971, "PVR", 20.5
+    }
+};
+
+
+char *paps_languages[] = {
+    "english", "swedish", "finnish", NULL
+};
+
+person_t pap = {
+    .name        = "Pap",
+    .gender      = MALE,
+    .age         = 30,
+    .languages   = paps_languages,
+    .height      = 180,
+    .weight      = 84.5,
+    .nationality = "martian",
+    .hand        = RIGHT,
+    .glasses     = false,
+    .favourites  = paps_favourites,
+    .nfavourite  = MRP_ARRAY_SIZE(paps_favourites),
+};
+
+
+art_t moms_favourites[] = {
+    {
+        BOOK ,
+        "Douglas Adams", "THHGTTG",
+        1982, "bookshelf", 11.8
+    },
+    {
+        MUSIC,
+        "Megadeth", "Sweating Bullets",
+        1992, "pocket", 12.5
+    },
+    {
+        MOVIE,
+        "Hottie Chick", "GGW-II",
+        1996, "PVR", 0.5
+    },
+    {
+        BOOK ,
+        "Douglas Adams", "The Long Dark Tea-Time of the Soul",
+        1988, "Kindle Touch", 8.50
+    }
+};
+
+
+char *moms_languages[] = {
+    "finnish", "english", "swedish", "french", NULL
+};
+
+person_t mom = {
+    .name        = "Mom",
+    .gender      = FEMALE,
+    .age         = 28,
+    .languages   = moms_languages,
+    .height      = 165,
+    .weight      = 57.8,
+    .nationality = "venusian",
+    .hand        = LEFT,
+    .glasses     = true,
+    .favourites  = moms_favourites,
+    .nfavourite  = MRP_ARRAY_SIZE(moms_favourites),
+};
+
+
+char *kids_languages[] = {
+    "english", "finnish", "swedish", NULL
+};
+
+person_t tom_dick_and_harry[] = {
+    {
+        .name        = "Tom",
+        .gender      = MALE,
+        .age         = 10,
+        .languages   = kids_languages + 1,
+        .height      = 135,
+        .weight      = 40.5,
+        .nationality = "UFO",
+        .hand        = BOTH,
+        .glasses     = false,
+        .favourites  = NULL,
+        .nfavourite  = 0,
+    },
+    {
+        .name        = "Dick",
+        .gender      = MALE,
+        .age         = 12,
+        .languages   = kids_languages,
+        .height      = 145,
+        .weight      = 45.5,
+        .nationality = "UFO",
+        .hand        = RIGHT,
+        .glasses     = true,
+        .favourites  = paps_favourites + 1,
+        .nfavourite  = MRP_ARRAY_SIZE(paps_favourites) - 2,
+    },
+    {
+        .name        = "Harry",
+        .gender      = MALE,
+        .age         = 14,
+        .languages   = kids_languages + 2,
+        .height      = 165,
+        .weight      = 60.5,
+        .nationality = "UFO",
+        .hand        = LEFT,
+        .glasses     = false,
+        .favourites  = moms_favourites + 1,
+        .nfavourite  = MRP_ARRAY_SIZE(moms_favourites) - 2,
+    },
+    {
+        .name        = NULL,
+    },
+};
+
+
+family_t family = { &pap, &mom, &tom_dick_and_harry[0] };
+
+
+int main(int argc, char *argv[])
+{
+    MRP_NATIVE_TYPE(art_type, art_t,
+                    MRP_UINT32(art_t, type    , DEFAULT),
+                    MRP_STRING(art_t, artist  , DEFAULT),
+                    MRP_STRING(art_t, title   , DEFAULT),
+                    MRP_UINT16(art_t, year    , DEFAULT),
+                    MRP_STRING(art_t, location, DEFAULT),
+                    MRP_DOUBLE(art_t, price   , DEFAULT));
+    MRP_NATIVE_TYPE(person_type, person_t,
+                    MRP_STRING(person_t, name       , DEFAULT),
+                    MRP_UINT32(person_t, gender     , DEFAULT),
+                    MRP_INT   (person_t, age        , DEFAULT),
+                    MRP_ARRAY (person_t, languages  , DEFAULT, GUARDED,
+                               char *, "", .strp = NULL),
+                    MRP_UINT  (person_t, height     , DEFAULT),
+                    MRP_FLOAT (person_t, weight     , DEFAULT),
+                    MRP_STRING(person_t, nationality, INLINED),
+                    MRP_UINT32(person_t, hand       , DEFAULT),
+                    MRP_BOOL  (person_t, glasses    , DEFAULT),
+                    MRP_ARRAY (person_t, favourites , DEFAULT, SIZED,
+                               art_t, nfavourite),
+                    MRP_SIZET (person_t, nfavourite , DEFAULT));
+    MRP_NATIVE_TYPE(family_type, family_t,
+                    MRP_STRUCT(family_t, father  , DEFAULT, person_t),
+                    MRP_STRUCT(family_t, mother  , DEFAULT, person_t),
+                    MRP_ARRAY (family_t, children, DEFAULT, GUARDED,
+                               person_t, name, .strp = NULL));
+    mrp_typemap_t map[4];
+
+    uint32_t  art_type_id, person_type_id, family_type_id;
+    void     *ebuf;
+    size_t    esize;
+    int       fd;
+    void     *dbuf;
+    family_t *decoded;
+    char      dump[16 * 1024];
+
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_INFO));
+
+    art_type_id = mrp_register_native(&art_type);
+
+    if (art_type_id == MRP_INVALID_TYPE)
+        mrp_log_error("Failed to register art_t type.");
+    else
+        mrp_log_info("Type art_t sucessfully registered.");
+
+    person_type_id = mrp_register_native(&person_type);
+
+    if (person_type_id == MRP_INVALID_TYPE)
+        mrp_log_error("Failed to register person_t type.");
+    else
+        mrp_log_info("Type person_t sucessfully registered.");
+
+    family_type_id = mrp_register_native(&family_type);
+
+    if (family_type_id == MRP_INVALID_TYPE)
+        mrp_log_error("Failed to register family_t type.");
+    else
+        mrp_log_info("Type family_t sucessfully registered.");
+
+    ebuf = NULL;
+
+    map[0] = (mrp_typemap_t)MRP_TYPEMAP(1, art_type_id   );
+    map[1] = (mrp_typemap_t)MRP_TYPEMAP(2, person_type_id);
+    map[2] = (mrp_typemap_t)MRP_TYPEMAP(3, family_type_id);
+    map[3] = (mrp_typemap_t)MRP_TYPEMAP_END;
+
+    if (mrp_encode_native(&family, family_type_id, 0, &ebuf, &esize, map) < 0) {
+        mrp_log_error("Failed to encode test data.");
+        exit(1);
+    }
+    else
+        mrp_log_info("Test data successfully encoded (%zd bytes).", esize);
+
+    if ((fd = open("type-test.encoded",
+                   O_CREAT | O_TRUNC | O_WRONLY, 0644)) >= 0) {
+        if (write(fd, ebuf, esize) != (ssize_t)esize)
+            mrp_log_error("Failed to write encoded data.");
+        close(fd);
+    }
+
+    if (mrp_decode_native(&ebuf, &esize, &dbuf, &family_type_id, map) < 0) {
+        mrp_log_error("Failed to decode test data.");
+        exit(1);
+    }
+    else
+        mrp_log_info("Test data sucessfully decoded.");
+
+    decoded = dbuf;
+
+    if (mrp_print_native(dump, sizeof(dump), decoded, family_type_id) >= 0)
+        mrp_log_info("dump of decoded data: %s", dump);
+    else
+        mrp_log_error("Failed to dump decoded data.");
+
+    mrp_free_native(dbuf, family_type_id);
+
+    return 0;
+}
diff --git a/src/common/tests/path-test.c b/src/common/tests/path-test.c
new file mode 100644 (file)
index 0000000..c2b0918
--- /dev/null
@@ -0,0 +1,50 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/file-utils.h>
+
+int main(int argc, char *argv[])
+{
+    int   i;
+    size_t size;
+    char *p, buf[PATH_MAX];
+    struct stat ost, nst;
+
+    if (argc > 1) {
+        size = strtoul(argv[1], &p, 10);
+        if (*p || size > sizeof(buf))
+            size = sizeof(buf);
+    }
+    else
+        size = sizeof(buf);
+
+    for (i = 1; i < argc; i++) {
+        printf("'%s':\n", argv[i]);
+        if ((p = mrp_normalize_path(buf, size, argv[i])) != NULL) {
+            printf("    -> '%s'\n", p);
+
+            if (stat(argv[i], &ost) < 0)
+                printf("    Non-existing path, can't test in practice...\n");
+            else{
+                if (stat(buf, &nst) == 0 &&
+                    ost.st_dev == nst.st_dev && ost.st_ino == nst.st_ino)
+                    printf("    Filesystem-equality check: OK.\n");
+                else {
+                    printf("    Filesystem-equality check: FAILED\n");
+                    exit(1);
+                }
+            }
+        }
+        else {
+            printf("    failed (%d: %s)\n", errno, strerror(errno));
+            exit(1);
+        }
+    }
+
+    return 0;
+}
diff --git a/src/common/tests/process-test.c b/src/common/tests/process-test.c
new file mode 100644 (file)
index 0000000..41a55e6
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common.h>
+#include <murphy/common/process.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+static void process_watch(const char *id, mrp_process_state_t s,
+        void *userdata)
+{
+    mrp_mainloop_t *ml = (mrp_mainloop_t *) userdata;
+
+    printf("process watch received event for %s: %s (%p)\n",
+        id, s == MRP_PROCESS_STATE_READY ? "ready" : "not ready", userdata);
+
+    mrp_mainloop_quit(ml, 0);
+}
+
+
+static void test_process_watch(mrp_mainloop_t *ml)
+{
+    mrp_process_state_t s = mrp_process_query_state("foobar");
+
+    printf("initial state %s\n",
+            s == MRP_PROCESS_STATE_READY ? "ready" : "not ready");
+
+    if (mrp_process_set_state("foobar", MRP_PROCESS_STATE_READY) < 0) {
+        printf("error setting the state 1\n");
+    }
+
+    s = mrp_process_query_state("foobar");
+
+    printf("second state %s\n",
+            s == MRP_PROCESS_STATE_READY ? "ready" : "not ready");
+
+    if (mrp_process_set_state("foobar", MRP_PROCESS_STATE_NOT_READY) < 0) {
+        printf("error setting the state 2\n");
+    }
+
+    s = mrp_process_query_state("foobar");
+
+    printf("third state %s\n",
+            s == MRP_PROCESS_STATE_READY ? "ready" : "not ready");
+
+    if (mrp_process_set_watch("foobar", ml, process_watch, ml) < 0) {
+        printf("failed to register watch\n");
+    }
+
+    printf("setting state to ready\n");
+
+    if (mrp_process_set_state("foobar", MRP_PROCESS_STATE_READY) < 0) {
+        printf("error setting the state 3\n");
+    }
+
+    mrp_mainloop_run(ml);
+
+    printf("removing the watch\n");
+
+    if(mrp_process_remove_watch("foobar") < 0) {
+        printf("failed to remove watch\n");
+    }
+}
+
+static void pid_watch(pid_t pid, mrp_process_state_t s, void *userdata)
+{
+    mrp_mainloop_t *ml = (mrp_mainloop_t *) userdata;
+
+    printf("pid watch received event for %d: %s (%p)\n",
+        pid, s == MRP_PROCESS_STATE_READY ? "ready" : "not ready", userdata);
+
+    mrp_mainloop_quit(ml, 0);
+}
+
+static void test_pid_watch(mrp_mainloop_t *ml)
+{
+    pid_t pid = fork();
+
+    if (pid < 0) {
+        printf("error forking\n");
+    }
+    else if (pid > 0) {
+        mrp_pid_watch_t *w;
+
+        if (mrp_pid_query_state(pid) != MRP_PROCESS_STATE_READY) {
+            printf("failed to query the process READY state\n");
+        }
+
+        printf("setting pid watch\n");
+        w = mrp_pid_set_watch(pid, ml, pid_watch, ml);
+
+        printf("killing the process '%d'\n", pid);
+        kill(pid, 15);
+        waitpid(pid, NULL, 0);
+
+        printf("running main loop\n");
+        mrp_mainloop_run(ml);
+
+        if (mrp_pid_query_state(pid) != MRP_PROCESS_STATE_NOT_READY) {
+            printf("failed to query the process NOT READY state\n");
+        }
+        printf("removing the watch\n");
+        mrp_pid_remove_watch(w);
+    }
+}
+
+int main(int argc, char **argv) {
+    mrp_mainloop_t *ml = mrp_mainloop_create();
+
+    if (argc == 2 && strcmp(argv[1], "pid") == 0) {
+        test_pid_watch(ml);
+    }
+    else if (argc == 2 && strcmp(argv[1], "process") == 0) {
+        test_process_watch(ml);
+    }
+    else {
+        printf("Usage: process-watch-test <process|pid>\n");
+    }
+
+    mrp_mainloop_destroy(ml);
+}
+
diff --git a/src/common/tests/sdbus-error-message.c b/src/common/tests/sdbus-error-message.c
new file mode 100644 (file)
index 0000000..1e4b9f2
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <murphy/common.h>
+#include <murphy/core.h>
+#include <murphy/common/dbus-sdbus.h>
+
+static int msg_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data)
+{
+    mrp_dbus_err_t err;
+    mrp_dbus_msg_t *reply;
+    const char *member = mrp_dbus_msg_member(msg);
+    const char *iface = mrp_dbus_msg_interface(msg);
+    const char *path = mrp_dbus_msg_path(msg);
+
+    MRP_UNUSED(data);
+
+    printf("Message callback called -- member: '%s', path: '%s',"
+            " interface: '%s'\n", member, path, iface);
+
+    mrp_dbus_error_init(&err);
+    mrp_dbus_error_set(&err, "org.freedesktop.DBus.Error.Failed", "Error message");
+
+    reply = mrp_dbus_msg_error(dbus, msg, &err);
+
+    if (reply) {
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    return TRUE;
+}
+
+int main()
+{
+    mrp_dbus_t *dbus;
+    mrp_mainloop_t *ml;
+
+    ml = mrp_mainloop_create();
+
+    if (!(dbus = mrp_dbus_connect(ml, "session", NULL))) {
+        printf("Failed to connect to D-Bus\n");
+    }
+
+    if (!mrp_dbus_acquire_name(dbus, "org.example", NULL)) {
+        printf("Failed to acquire name on D-Bus\n");
+        goto error;
+    }
+
+    if (!mrp_dbus_export_method(dbus, "/example", "org.example", "member",
+                msg_cb, NULL)) {
+       printf("Failed to register method\n");
+       goto error;
+    }
+
+    printf("waiting for 'dbus-send --session --print-reply --type=method_call"
+            "--dest=org.example /example org.example.member'\n");
+
+    mrp_mainloop_run(ml);
+
+    return 0;
+
+error:
+    return 1;
+}
diff --git a/src/common/tests/sdbus-test.c b/src/common/tests/sdbus-test.c
new file mode 100644 (file)
index 0000000..f785139
--- /dev/null
@@ -0,0 +1,226 @@
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+
+#include "sd-bus.h"
+#include "bus-message.h"
+
+#define USEC_TO_MSEC(usec) ((unsigned int)((usec) / 1000))
+
+typedef struct {
+    sd_bus         *bus;
+    mrp_mainloop_t *ml;
+    mrp_subloop_t  *sl;
+} bus_t;
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    MRP_UNUSED(user_data);
+
+    switch (signum) {
+    case SIGINT:
+    case SIGTERM:
+    case SIGQUIT:
+        mrp_log_info("Received signal %d (%s), exiting...", signum,
+                     strsignal(signum));
+        mrp_mainloop_quit(mrp_get_sighandler_mainloop(h), 0);
+    }
+}
+
+
+static int bus_prepare(void *user_data)
+{
+    MRP_UNUSED(user_data);
+
+    return FALSE;
+}
+
+
+static int bus_query(void *user_data, struct pollfd *fds, int nfd,
+                     int *timeout)
+{
+    bus_t    *b = (bus_t *)user_data;
+    uint64_t  usec;
+
+    mrp_log_info("nfd: %d", nfd);
+
+    if (nfd > 0) {
+        fds[0].fd      = sd_bus_get_fd(b->bus);
+        fds[0].events  = sd_bus_get_events(b->bus) | POLLIN;
+        fds[0].revents = 0;
+
+        if (sd_bus_get_timeout(b->bus, &usec) < 0)
+            *timeout = -1;
+        else
+            *timeout = USEC_TO_MSEC(usec);
+
+        mrp_log_info("fd: %d, events: 0x%x, timeout: %u", fds[0].fd,
+                     fds[0].events, *timeout);
+    }
+
+    return 1;
+}
+
+
+static int bus_check(void *user_data, struct pollfd *fds, int nfd)
+{
+    MRP_UNUSED(user_data);
+
+    if (nfd > 0 && fds[0].revents != 0)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static void bus_dispatch(void *user_data)
+{
+    bus_t *b = (bus_t *)user_data;
+
+    if (sd_bus_process(b->bus, NULL) > 0)
+        sd_bus_flush(b->bus);
+}
+
+
+static int bus_signal_cb(sd_bus *bus, int ret, sd_bus_message *m, void *user_data)
+{
+    mrp_log_info("%s(): got bus signal...", __FUNCTION__);
+
+    bus_message_dump(m);
+
+    return 0;
+}
+
+
+static int bus_method_cb(sd_bus *bus, int ret, sd_bus_message *m, void *user_data)
+{
+    mrp_log_info("%s(): got bus method call message %p...", __FUNCTION__, m);
+
+    bus_message_dump(m);
+
+    if (!strcmp(sd_bus_message_get_member(m), "unhandled"))
+        return FALSE;
+    else
+        return TRUE;
+}
+
+
+static int bus_return_cb(sd_bus *bus, int ret, sd_bus_message *m, void *user_data)
+{
+    mrp_log_info("%s(): got bus method reply...", __FUNCTION__);
+
+    bus_message_dump(m);
+
+    return 0;
+}
+
+
+static void emit_signal(mrp_timer_t *t, void *user_data)
+{
+    sd_bus *bus = (sd_bus *)user_data;
+
+    sd_bus_emit_signal(bus, "/foo/bar", "foo.bar", "foobar", NULL);
+}
+
+
+static void call_method(mrp_timer_t *t, void *user_data)
+{
+    sd_bus         *bus = (sd_bus *)user_data;
+    sd_bus_message *msg = NULL;
+    int             r;
+    uint64_t        serial;
+
+    r = sd_bus_message_new_method_call(bus, "org.freedesktop.DBus",
+                                       "/", "org.freedesktop.DBus", "GetId",
+                                       &msg);
+
+    if (r != 0) {
+        mrp_log_error("Failed to create new method call message.");
+        return;
+    }
+
+    r = sd_bus_send_with_reply(bus, msg, bus_return_cb, NULL, 100000 * 1000, &serial);
+
+    if (r != 0)
+        mrp_log_error("Failed to call method... (r = %d)", r);
+}
+
+
+int main(int argc, char *argv[])
+{
+    static mrp_subloop_ops_t bus_ops = {
+        .prepare  = bus_prepare,
+        .query    = bus_query,
+        .check    = bus_check,
+        .dispatch = bus_dispatch
+    };
+
+    mrp_mainloop_t *ml  = NULL;
+    mrp_timer_t    *ts  = NULL;
+    mrp_timer_t    *tm  = NULL;
+    sd_bus         *bus = NULL;
+    int             r;
+    bus_t          *b;
+
+    mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_INFO));
+
+    ml = mrp_mainloop_create();
+    r  = sd_bus_open_user(&bus);
+
+    if (ml == NULL || r != 0)
+        goto fail;
+
+    mrp_add_sighandler(ml, SIGINT , signal_handler, NULL);
+    mrp_add_sighandler(ml, SIGTERM, signal_handler, NULL);
+    mrp_add_sighandler(ml, SIGQUIT, signal_handler, NULL);
+
+    b = mrp_allocz(sizeof(*b));
+
+    if (b == NULL)
+        goto fail;
+
+    sd_bus_add_match(bus, "type='signal'"       , bus_signal_cb, bus);
+#if 0
+    sd_bus_add_match(bus, "type='method_call'"  , bus_method_cb, bus);
+    sd_bus_add_match(bus, "type='method_return'", bus_return_cb, bus);
+#else
+    sd_bus_add_fallback(bus, "/", bus_method_cb, bus);
+#endif
+
+    while (sd_bus_process(bus, NULL) > 0)
+        sd_bus_flush(bus);
+
+    b->bus = bus;
+    b->ml  = ml;
+    b->sl  = mrp_add_subloop(ml, &bus_ops, b);
+
+    if (b->sl == NULL) {
+        mrp_log_error("Failed to register D-Bus subloop.");
+        exit(1);
+    }
+
+#if 0
+    if ((ts = mrp_add_timer(ml, 1000, emit_signal, bus)) == NULL) {
+        mrp_log_error("Failed to create signal emission timer.");
+        exit(1);
+    }
+#endif
+
+    if ((ts = mrp_add_timer(ml, 1000, call_method, bus)) == NULL) {
+        mrp_log_error("Failed to create method call timer.");
+        exit(1);
+    }
+
+    mrp_mainloop_run(ml);
+
+ fail:
+    mrp_del_timer(ts);
+    mrp_del_timer(tm);
+
+    sd_bus_unref(bus);
+    mrp_mainloop_destroy(ml);
+
+    return 0;
+}
diff --git a/src/common/tests/transport-test.c b/src/common/tests/transport-test.c
new file mode 100644 (file)
index 0000000..e5797a0
--- /dev/null
@@ -0,0 +1,998 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+
+
+/*
+ * tags for generic message fields
+ */
+
+#define TAG_SEQ   ((uint16_t)0x1)
+#define TAG_MSG   ((uint16_t)0x2)
+#define TAG_U8    ((uint16_t)0x3)
+#define TAG_S8    ((uint16_t)0x4)
+#define TAG_U16   ((uint16_t)0x5)
+#define TAG_S16   ((uint16_t)0x6)
+#define TAG_DBL   ((uint16_t)0x7)
+#define TAG_BLN   ((uint16_t)0x8)
+#define TAG_ASTR  ((uint16_t)0x9)
+#define TAG_AU32  ((uint16_t)0xa)
+#define TAG_RPL   ((uint16_t)0xb)
+#define TAG_END   MRP_MSG_FIELD_END
+
+#define U32_GUARD (uint32_t)-1
+
+/*
+ * our test custom data type
+ */
+
+#define TAG_CUSTOM 0x1
+
+typedef struct {
+    uint32_t   seq;
+    char      *msg;
+    uint8_t     u8;
+    int8_t      s8;
+    uint16_t   u16;
+    int16_t    s16;
+    double     dbl;
+    bool       bln;
+    char     **astr;
+    uint32_t   nstr;
+    uint32_t   fsck;
+    uint32_t  *au32;
+    char      *rpl;
+} custom_t;
+
+
+typedef custom_t native_t;
+
+static uint32_t native_id;
+
+MRP_DATA_DESCRIPTOR(custom_descr, TAG_CUSTOM, custom_t,
+                    MRP_DATA_MEMBER(custom_t,  seq, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t,  msg, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t,   u8, MRP_MSG_FIELD_UINT8 ),
+                    MRP_DATA_MEMBER(custom_t,   s8, MRP_MSG_FIELD_SINT8 ),
+                    MRP_DATA_MEMBER(custom_t,  u16, MRP_MSG_FIELD_UINT16),
+                    MRP_DATA_MEMBER(custom_t,  s16, MRP_MSG_FIELD_SINT16),
+                    MRP_DATA_MEMBER(custom_t,  dbl, MRP_MSG_FIELD_DOUBLE),
+                    MRP_DATA_MEMBER(custom_t,  bln, MRP_MSG_FIELD_BOOL  ),
+                    MRP_DATA_MEMBER(custom_t,  rpl, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_ARRAY_COUNT(custom_t, astr, nstr,
+                                         MRP_MSG_FIELD_STRING),
+                    MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD,
+                                         MRP_MSG_FIELD_UINT32));
+
+MRP_DATA_DESCRIPTOR(buggy_descr, TAG_CUSTOM, custom_t,
+                    MRP_DATA_MEMBER(custom_t,  seq, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t,  msg, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t,   u8, MRP_MSG_FIELD_UINT8 ),
+                    MRP_DATA_MEMBER(custom_t,   s8, MRP_MSG_FIELD_SINT8 ),
+                    MRP_DATA_MEMBER(custom_t,  u16, MRP_MSG_FIELD_UINT16),
+                    MRP_DATA_MEMBER(custom_t,  s16, MRP_MSG_FIELD_SINT16),
+                    MRP_DATA_MEMBER(custom_t,  dbl, MRP_MSG_FIELD_DOUBLE),
+                    MRP_DATA_MEMBER(custom_t,  bln, MRP_MSG_FIELD_BOOL  ),
+                    MRP_DATA_MEMBER(custom_t,  rpl, MRP_MSG_FIELD_STRING),
+                    MRP_DATA_MEMBER(custom_t, nstr, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_MEMBER(custom_t, fsck, MRP_MSG_FIELD_UINT32),
+                    MRP_DATA_ARRAY_COUNT(custom_t, astr, fsck,
+                                         MRP_MSG_FIELD_STRING),
+                    MRP_DATA_ARRAY_GUARD(custom_t, au32, u32, U32_GUARD,
+                                         MRP_MSG_FIELD_UINT32));
+
+
+
+mrp_data_descr_t *data_descr;
+
+
+typedef enum {
+    MODE_DEFAULT = 0,
+    MODE_MESSAGE = 1,
+    MODE_DATA    = 2,
+    MODE_RAW     = 3,
+    MODE_NATIVE  = 4,
+} msg_mode_t;
+
+
+typedef struct {
+    mrp_mainloop_t  *ml;
+    mrp_transport_t *lt, *t;
+    char            *addrstr;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    const char      *atype;
+    int              server;
+    int              sock;
+    mrp_io_watch_t  *iow;
+    mrp_timer_t     *timer;
+    int              mode;
+    int              buggy;
+    int              connect;
+    int              stream;
+    int              log_mask;
+    const char      *log_target;
+    uint32_t         seqno;
+} context_t;
+
+
+void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data);
+void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data);
+
+void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data);
+void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag,
+                   mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data);
+
+void recvraw(mrp_transport_t *t, void *data, size_t size, void *user_data);
+void recvrawfrom(mrp_transport_t *t, void *data, size_t size,
+                 mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data);
+
+
+void dump_msg(mrp_msg_t *msg, FILE *fp)
+{
+    mrp_msg_dump(msg, fp);
+}
+
+
+void recvfrom_msg(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                  socklen_t addrlen, void *user_data)
+{
+    context_t       *c = (context_t *)user_data;
+    mrp_msg_field_t *f;
+    uint32_t         seq;
+    char             buf[256];
+    int              status;
+
+    mrp_log_info("received a message");
+    dump_msg(msg, stdout);
+
+    if (c->server) {
+        seq = 0;
+        if ((f = mrp_msg_find(msg, TAG_SEQ)) != NULL) {
+            if (f->type == MRP_MSG_FIELD_UINT32)
+                seq = f->u32;
+        }
+
+        snprintf(buf, sizeof(buf), "reply to message #%u", seq);
+
+        if (!mrp_msg_append(msg, TAG_RPL, MRP_MSG_FIELD_STRING, buf,
+                            TAG_END)) {
+            mrp_log_info("failed to append to received message");
+            exit(1);
+        }
+
+        if (c->connect)
+            status = mrp_transport_send(t, msg);
+        else
+            status = mrp_transport_sendto(t, msg, addr, addrlen);
+
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+
+        /* message unreffed by transport layer */
+    }
+}
+
+
+void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    return recvfrom_msg(t, msg, NULL, 0, user_data);
+}
+
+
+void dump_custom(custom_t *msg, FILE *fp)
+{
+    uint32_t i;
+
+    mrp_data_dump(msg, data_descr, fp);
+    fprintf(fp, "{\n");
+    fprintf(fp, "    seq = %u\n"  , msg->seq);
+    fprintf(fp, "    msg = '%s'\n", msg->msg);
+    fprintf(fp, "     u8 = %u\n"  , msg->u8);
+    fprintf(fp, "     s8 = %d\n"  , msg->s8);
+    fprintf(fp, "    u16 = %u\n"  , msg->u16);
+    fprintf(fp, "    s16 = %d\n"  , msg->s16);
+    fprintf(fp, "    dbl = %f\n"  , msg->dbl);
+    fprintf(fp, "    bln = %s\n"  , msg->bln ? "true" : "false");
+    fprintf(fp, "   astr = (%u)\n", msg->nstr);
+    for (i = 0; i < msg->nstr; i++)
+        fprintf(fp, "           %s\n", msg->astr[i]);
+    fprintf(fp, "   au32 =\n");
+    for (i = 0; msg->au32[i] != U32_GUARD; i++)
+        fprintf(fp, "           %u\n", msg->au32[i]);
+    fprintf(fp, "    rpl = '%s'\n", msg->rpl);
+    fprintf(fp, "}\n");
+}
+
+
+void free_custom(custom_t *msg)
+{
+    mrp_data_free(msg, data_descr->tag);
+}
+
+
+void recvfrom_data(mrp_transport_t *t, void *data, uint16_t tag,
+                   mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    context_t *c   = (context_t *)user_data;
+    custom_t  *msg = (custom_t *)data;
+    custom_t   rpl;
+    char       buf[256];
+    uint32_t   au32[] = { 9, 8, 7, 6, 5, -1 };
+    int        status;
+
+    mrp_log_info("received custom message of type 0x%x", tag);
+    dump_custom(data, stdout);
+
+    if (tag != data_descr->tag) {
+        mrp_log_error("Tag 0x%x != our custom type (0x%x).",
+                      tag, data_descr->tag);
+        exit(1);
+    }
+
+    if (c->server) {
+        rpl = *msg;
+        snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq);
+        rpl.rpl  = buf;
+        rpl.au32 = au32;
+
+        if (c->connect)
+            status = mrp_transport_senddata(t, &rpl, data_descr->tag);
+        else
+            status = mrp_transport_senddatato(t, &rpl, data_descr->tag,
+                                              addr, addrlen);
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+    }
+
+    free_custom(msg);
+}
+
+
+void recv_data(mrp_transport_t *t, void *data, uint16_t tag, void *user_data)
+{
+    recvfrom_data(t, data, tag, NULL, 0, user_data);
+}
+
+
+void dump_raw(void *data, size_t size, FILE *fp)
+{
+    int len = (int)size;
+
+    fprintf(fp, "[%*.*s]\n", len, len, (char *)data);
+}
+
+
+void recvfrom_raw(mrp_transport_t *t, void *data, size_t size,
+                  mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    context_t *c   = (context_t *)user_data;
+    char       rpl[256];
+    size_t     rpl_size;
+    int        status;
+
+    rpl_size = snprintf(rpl, sizeof(rpl), "reply to message [%*.*s]",
+                        (int)size, (int)size, (char *)data);
+
+    mrp_log_info("received raw message");
+    dump_raw(data, size, stdout);
+
+    if (strncmp((char *)data, "reply to ", 9) != 0) {
+        if (c->connect)
+            status = mrp_transport_sendraw(t, rpl, rpl_size);
+        else
+            status = mrp_transport_sendrawto(t, rpl, rpl_size, addr, addrlen);
+
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+    }
+}
+
+
+void recv_raw(mrp_transport_t *t, void *data, size_t size, void *user_data)
+{
+    recvfrom_raw(t, data, size, NULL, 0, user_data);
+}
+
+
+void free_native(native_t *msg)
+{
+    mrp_free_native(msg, native_id);
+}
+
+
+void recvfrom_native(mrp_transport_t *t, void *data, uint32_t type_id,
+                     mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    context_t *c   = (context_t *)user_data;
+    native_t  *msg = (native_t *)data;
+    native_t   rpl;
+    char       buf[256];
+    uint32_t   au32[] = { 9, 8, 7, 6, 5, -1 };
+    int        status;
+
+    mrp_log_info("received native message of type 0x%x", type_id);
+    dump_custom(data, stdout);
+
+    if (type_id != native_id) {
+        mrp_log_error("Received type 0x%x, expected 0x%x.", type_id, native_id);
+        exit(1);
+    }
+
+    if (c->server) {
+        rpl = *msg;
+        snprintf(buf, sizeof(buf), "reply to message #%u", msg->seq);
+        rpl.rpl  = buf;
+        rpl.au32 = au32;
+
+        if (c->connect)
+            status = mrp_transport_sendnative(t, &rpl, native_id);
+        else
+            status = mrp_transport_sendnativeto(t, &rpl, native_id,
+                                                addr, addrlen);
+        if (status)
+            mrp_log_info("reply successfully sent");
+        else
+            mrp_log_error("failed to send reply");
+    }
+
+    free_native(msg);
+}
+
+
+void recv_native(mrp_transport_t *t, void *data, uint32_t type_id,
+                 void *user_data)
+{
+    recvfrom_native(t, data, type_id, NULL, 0, user_data);
+}
+
+
+void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(c);
+
+    if (error) {
+        mrp_log_error("Connection closed with error %d (%s).", error,
+                      strerror(error));
+        exit(1);
+    }
+    else {
+        mrp_log_info("Peer has closed the connection.");
+        exit(0);
+    }
+}
+
+
+void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+    int        flags;
+
+    flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+    c->t = mrp_transport_accept(lt, c, flags);
+
+    if (c->t == NULL) {
+        mrp_log_error("Failed to accept new connection.");
+        exit(1);
+    }
+}
+
+
+void type_init(context_t *c)
+{
+    if (c->buggy && c->server) {
+        data_descr = &buggy_descr;
+        mrp_log_info("Deliberately using buggy data descriptor...");
+    }
+    else
+        data_descr = &custom_descr;
+
+    if (!mrp_msg_register_type(data_descr)) {
+        mrp_log_error("Failed to register custom data type.");
+        exit(1);
+    }
+}
+
+
+void register_native(void)
+{
+    MRP_NATIVE_TYPE(native_type, native_t,
+                    MRP_UINT32(native_t, seq        , DEFAULT),
+                    MRP_STRING(native_t, msg        , DEFAULT),
+                    MRP_UINT8 (native_t, u8         , DEFAULT),
+                    MRP_INT8  (native_t, s8         , DEFAULT),
+                    MRP_UINT16(native_t, u16        , DEFAULT),
+                    MRP_INT16 (native_t, s16        , DEFAULT),
+                    MRP_DOUBLE(native_t, dbl        , DEFAULT),
+                    MRP_BOOL  (native_t, bln        , DEFAULT),
+                    MRP_ARRAY (native_t, astr       , DEFAULT, SIZED,
+                               char *, nstr),
+                    MRP_UINT32(native_t, nstr       , DEFAULT),
+                    MRP_ARRAY (native_t, au32       , DEFAULT, GUARDED,
+                               uint32_t, "", .u32 = -1),
+                    MRP_STRING(native_t, rpl        , DEFAULT));
+
+
+    if ((native_id = mrp_register_native(&native_type)) != MRP_INVALID_TYPE)
+        mrp_log_info("Successfully registered native type 'native_t'.");
+    else {
+        mrp_log_error("Failed to register native type 'native_t'.");
+        exit(1);
+    }
+}
+
+
+void server_init(context_t *c)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = NULL },
+        { .recvmsgfrom = NULL },
+        .closed        = NULL,
+        .connection    = NULL,
+    };
+
+    int flags;
+
+    type_init(c);
+
+    switch (c->mode) {
+    case MODE_DATA:
+        evt.recvdata     = recv_data;
+        evt.recvdatafrom = recvfrom_data;
+        break;
+    case MODE_RAW:
+        evt.recvraw      = recv_raw;
+        evt.recvrawfrom  = recvfrom_raw;
+        break;
+    case MODE_NATIVE:
+        evt.recvnative     = recv_native;
+        evt.recvnativefrom = recvfrom_native;
+        break;
+    case MODE_MESSAGE:
+    default:
+        evt.recvmsg      = recv_msg;
+        evt.recvmsgfrom  = recvfrom_msg;
+    }
+
+    if (c->stream) {
+        evt.connection = connection_evt;
+        evt.closed     = closed_evt;
+    }
+
+    flags = MRP_TRANSPORT_REUSEADDR;
+
+    switch (c->mode) {
+    case MODE_DATA:    flags |= MRP_TRANSPORT_MODE_DATA;   break;
+    case MODE_RAW:     flags |= MRP_TRANSPORT_MODE_RAW;    break;
+    case MODE_NATIVE:  flags |= MRP_TRANSPORT_MODE_NATIVE; break;
+    default:
+    case MODE_MESSAGE: flags |= MRP_TRANSPORT_MODE_MSG;
+    }
+
+    c->lt = mrp_transport_create(c->ml, c->atype, &evt, c, flags);
+
+    if (c->lt == NULL) {
+        mrp_log_error("Failed to create listening server transport.");
+        exit(1);
+    }
+
+    if (!mrp_transport_bind(c->lt, &c->addr, c->alen)) {
+        mrp_log_error("Failed to bind transport to address %s.", c->addrstr);
+        exit(1);
+    }
+
+    if (c->stream) {
+        if (!mrp_transport_listen(c->lt, 0)) {
+            mrp_log_error("Failed to listen on server transport.");
+            exit(1);
+        }
+    }
+}
+
+
+void send_msg(context_t *c)
+{
+    mrp_msg_t *msg;
+    uint32_t   seq;
+    char       buf[256];
+    char      *astr[] = { "this", "is", "an", "array", "of", "strings" };
+    uint32_t   au32[] = { 1, 2, 3,
+                          1 << 16, 2 << 16, 3 << 16,
+                          1 << 24, 2 << 24, 3 << 24 };
+    uint32_t   nstr = MRP_ARRAY_SIZE(astr);
+    uint32_t   nu32 = MRP_ARRAY_SIZE(au32);
+    int        status;
+
+    seq = c->seqno++;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+
+    msg = mrp_msg_create(TAG_SEQ , MRP_MSG_FIELD_UINT32, seq,
+                         TAG_MSG , MRP_MSG_FIELD_STRING, buf,
+                         TAG_U8  , MRP_MSG_FIELD_UINT8 ,   seq & 0xf,
+                         TAG_S8  , MRP_MSG_FIELD_SINT8 , -(seq & 0xf),
+                         TAG_U16 , MRP_MSG_FIELD_UINT16,   seq,
+                         TAG_S16 , MRP_MSG_FIELD_SINT16, - seq,
+                         TAG_DBL , MRP_MSG_FIELD_DOUBLE, seq / 3.0,
+                         TAG_BLN , MRP_MSG_FIELD_BOOL  , seq & 0x1,
+                         TAG_ASTR, MRP_MSG_FIELD_ARRAY_OF(STRING), nstr, astr,
+                         TAG_AU32, MRP_MSG_FIELD_ARRAY_OF(UINT32), nu32, au32,
+                         TAG_END);
+
+    if (msg == NULL) {
+        mrp_log_error("Failed to create new message.");
+        exit(1);
+    }
+
+    if (c->connect)
+        status = mrp_transport_send(c->t, msg);
+    else
+        status = mrp_transport_sendto(c->t, msg, &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", seq);
+
+    mrp_msg_unref(msg);
+}
+
+
+void send_data(context_t *c)
+{
+    uint32_t  seq = c->seqno++;
+    custom_t  msg;
+    char      buf[256];
+    char     *astr[] = { "this", "is", "a", "test", "string", "array" };
+    uint32_t  au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 };
+    int       status;
+
+    msg.seq = seq;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+    msg.msg  = buf;
+    msg.u8   =   seq & 0xf;
+    msg.s8   = -(seq & 0xf);
+    msg.u16  =   seq;
+    msg.s16  = - seq;
+    msg.dbl  =   seq / 3.0;
+    msg.bln  =   seq & 0x1;
+    msg.astr = astr;
+    msg.nstr = MRP_ARRAY_SIZE(astr);
+    msg.fsck = 1000;
+    msg.au32 = au32;
+    msg.rpl  = "";
+
+    if (c->connect)
+        status = mrp_transport_senddata(c->t, &msg, data_descr->tag);
+    else
+        status = mrp_transport_senddatato(c->t, &msg, data_descr->tag,
+                                          &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", msg.seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", msg.seq);
+}
+
+
+void send_raw(context_t *c)
+{
+    uint32_t  seq = c->seqno++;
+    char      msg[256];
+    size_t    size;
+    int       status;
+
+    size = snprintf(msg, sizeof(msg), "this is message #%u", seq);
+
+    if (c->connect)
+        status = mrp_transport_sendraw(c->t, msg, size);
+    else
+        status = mrp_transport_sendrawto(c->t, msg, size, &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send raw message #%d.", seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%u succesfully sent.", seq);
+}
+
+
+void send_native(context_t *c)
+{
+    uint32_t  seq = c->seqno++;
+    custom_t  msg;
+    char      buf[256];
+    char     *astr[] = { "this", "is", "a", "test", "string", "array" };
+    uint32_t  au32[] = { 1, 2, 3, 4, 5, 6, 7, -1 };
+    int       status;
+
+    msg.seq = seq;
+    snprintf(buf, sizeof(buf), "this is message #%u", (unsigned int)seq);
+    msg.msg  = buf;
+    msg.u8   =   seq & 0xf;
+    msg.s8   = -(seq & 0xf);
+    msg.u16  =   seq;
+    msg.s16  = - seq;
+    msg.dbl  =   seq / 3.0;
+    msg.bln  =   seq & 0x1;
+    msg.astr = astr;
+    msg.nstr = MRP_ARRAY_SIZE(astr);
+    msg.fsck = 1000;
+    msg.au32 = au32;
+    msg.rpl  = "";
+
+    if (c->connect)
+        status = mrp_transport_sendnative(c->t, &msg, native_id);
+    else
+        status = mrp_transport_sendnativeto(c->t, &msg, native_id,
+                                            &c->addr, c->alen);
+
+    if (!status) {
+        mrp_log_error("Failed to send message #%d.", msg.seq);
+        exit(1);
+    }
+    else
+        mrp_log_info("Message #%d succesfully sent.", msg.seq);
+}
+
+
+void send_cb(mrp_timer_t *t, void *user_data)
+{
+    context_t *c = (context_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    switch (c->mode) {
+    case MODE_DATA:    send_data(c);   break;
+    case MODE_RAW:     send_raw(c);    break;
+    case MODE_NATIVE:  send_native(c); break;
+    default:
+    case MODE_MESSAGE: send_msg(c);
+    }
+}
+
+
+void client_init(context_t *c)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = NULL },
+        { .recvmsgfrom = NULL },
+        .closed        = closed_evt,
+        .connection    = NULL
+    };
+
+    int flags;
+
+    type_init(c);
+
+    switch (c->mode) {
+    case MODE_DATA:
+        evt.recvdata     = recv_data;
+        evt.recvdatafrom = recvfrom_data;
+        flags            = MRP_TRANSPORT_MODE_DATA;
+        break;
+    case MODE_RAW:
+        evt.recvraw      = recv_raw;
+        evt.recvrawfrom  = recvfrom_raw;
+        flags            = MRP_TRANSPORT_MODE_RAW;
+        break;
+    case MODE_NATIVE:
+        evt.recvnative     = recv_native;
+        evt.recvnativefrom = recvfrom_native;
+        flags              = MRP_TRANSPORT_MODE_NATIVE;
+        break;
+    default:
+    case MODE_MESSAGE:
+        evt.recvmsg      = recv_msg;
+        evt.recvmsgfrom  = recvfrom_msg;
+        flags            = MRP_TRANSPORT_MODE_MSG;
+    }
+
+    c->t = mrp_transport_create(c->ml, c->atype, &evt, c, flags);
+
+    if (c->t == NULL) {
+        mrp_log_error("Failed to create new transport.");
+        exit(1);
+    }
+
+    if (!strcmp(c->atype, "unxd")) {
+        char           addrstr[] = "unxd:@stream-test-client";
+        mrp_sockaddr_t addr;
+        socklen_t      alen;
+
+        alen = mrp_transport_resolve(NULL, addrstr, &addr, sizeof(addr), NULL);
+        if (alen <= 0) {
+            mrp_log_error("Failed to resolve transport address '%s'.", addrstr);
+            exit(1);
+        }
+
+        if (!mrp_transport_bind(c->t, &addr, alen)) {
+            mrp_log_error("Failed to bind to transport address '%s'.", addrstr);
+            exit(1);
+        }
+    }
+
+    if (c->connect) {
+        if (!mrp_transport_connect(c->t, &c->addr, c->alen)) {
+            mrp_log_error("Failed to connect to %s.", c->addrstr);
+            exit(1);
+        }
+    }
+
+
+    c->timer = mrp_add_timer(c->ml, 1000, send_cb, c);
+
+    if (c->timer == NULL) {
+        mrp_log_error("Failed to create send timer.");
+        exit(1);
+    }
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options] [transport-address]\n\n"
+           "The possible options are:\n"
+           "  -s, --server                   run as test server (default)\n"
+           "  -C, --connect                  connect transport\n"
+           "      For connection-oriented transports, this is automatic.\n"
+           "  -a, --address                  address to use\n"
+           "  -c, --custom                   use custom messages\n"
+           "  -m, --message                  use generic messages (default)\n"
+           "  -r, --raw                      use raw messages\n"
+           "  -n, --native                   use native messages\n"
+           "  -b, --buggy                    use buggy data descriptors\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->addrstr    = "tcp4:127.0.0.1:3000";
+    ctx->server     = FALSE;
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "scmrnbCa:l:t:v:d:h"
+    struct option options[] = {
+        { "server"    , no_argument      , NULL, 's' },
+        { "address"   , required_argument, NULL, 'a' },
+        { "custom"    , no_argument      , NULL, 'c' },
+        { "message"   , no_argument      , NULL, 'm' },
+        { "raw"       , no_argument      , NULL, 'r' },
+        { "native"    , no_argument      , NULL, 'n' },
+        { "connect"   , no_argument      , NULL, 'C' },
+
+        { "buggy"     , no_argument      , NULL, 'b' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            ctx->server = TRUE;
+            break;
+
+        case 'c':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_DATA;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'm':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_MESSAGE;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'r':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_RAW;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'n':
+            if (ctx->mode == MODE_DEFAULT)
+                ctx->mode = MODE_NATIVE;
+            else {
+                mrp_log_error("Multiple modes requested.");
+                exit(1);
+            }
+            break;
+
+        case 'b':
+            ctx->buggy = TRUE;
+            break;
+
+        case 'C':
+            ctx->connect = TRUE;
+            break;
+
+        case 'a':
+            ctx->addrstr = optarg;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t c;
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.server)
+        mrp_log_info("Running as server, using address '%s'...", c.addrstr);
+    else
+        mrp_log_info("Running as client, using address '%s'...", c.addrstr);
+
+    switch (c.mode) {
+    case MODE_DATA:    mrp_log_info("Using custom data messages..."); break;
+    case MODE_RAW:     mrp_log_info("Using raw messages...");         break;
+    case MODE_NATIVE:
+        register_native();
+        mrp_log_info("Using native messages...");
+        break;
+    default:
+    case MODE_MESSAGE: mrp_log_info("Using generic messages...");
+    }
+
+    if (!strncmp(c.addrstr, "tcp", 3) || !strncmp(c.addrstr, "unxs", 4) ||
+        !strncmp(c.addrstr, "wsck", 4)) {
+        c.stream  = TRUE;
+        c.connect = TRUE;
+    }
+
+    c.alen = mrp_transport_resolve(NULL, c.addrstr,
+                                   &c.addr, sizeof(c.addr), &c.atype);
+    if (c.alen <= 0) {
+        mrp_log_error("Failed to resolve transport address '%s'.", c.addrstr);
+        exit(1);
+    }
+
+    c.ml = mrp_mainloop_create();
+
+    if (c.server)
+        server_init(&c);
+    else
+        client_init(&c);
+
+    mrp_mainloop_run(c.ml);
+
+    return 0;
+}
diff --git a/src/common/tlv.c b/src/common/tlv.c
new file mode 100644 (file)
index 0000000..fd216a3
--- /dev/null
@@ -0,0 +1,662 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/tlv.h>
+
+#define TLV_MIN_PREALLOC 4096
+#define TLV_MIN_CHUNK      64
+
+int mrp_tlv_setup_write(mrp_tlv_t *tlv, size_t prealloc)
+{
+    if (prealloc < TLV_MIN_PREALLOC)
+        prealloc = TLV_MIN_PREALLOC;
+
+    if ((tlv->buf = mrp_allocz(prealloc)) == NULL)
+        return -1;
+
+    tlv->size  = prealloc;
+    tlv->p     = tlv->buf;
+    tlv->write = 1;
+
+    return 0;
+}
+
+
+static inline size_t tlv_space(mrp_tlv_t *tlv)
+{
+    if (tlv->size > 0 && tlv->write)
+        return tlv->size - (tlv->p - tlv->buf);
+    else
+        return 0;
+}
+
+
+static inline size_t tlv_data(mrp_tlv_t *tlv)
+{
+    if (!tlv->write)
+        return tlv->size - (tlv->p - tlv->buf);
+    else
+        return tlv->p - tlv->buf;
+}
+
+
+int mrp_tlv_ensure(mrp_tlv_t *tlv, size_t size)
+{
+    size_t left, diff;
+
+    if (!tlv->write)
+        return -1;
+
+    if ((left = tlv_space(tlv)) < size) {
+        diff = size - left;
+
+        if (diff < TLV_MIN_CHUNK)
+            diff = TLV_MIN_CHUNK;
+
+        tlv->p -= (ptrdiff_t)tlv->buf;
+
+        if (mrp_realloc(tlv->buf, tlv->size + diff) == NULL) {
+            tlv->p += (ptrdiff_t)tlv->buf;
+
+            return -1;
+        }
+
+        memset(tlv->buf + tlv->size, 0, diff);
+
+        tlv->size += diff;
+        tlv->p    += (ptrdiff_t)tlv->buf;
+    }
+
+    return 0;
+}
+
+
+void *mrp_tlv_reserve(mrp_tlv_t *tlv, size_t size, int align)
+{
+    void      *reserved;
+    ptrdiff_t  offs, pad;
+    size_t     len;
+
+    offs = tlv->p - tlv->buf;
+
+    if (align > 1)
+        pad = align - (offs & (align - 1));
+    else
+        pad = 0;
+
+    len = size + pad;
+
+    if (mrp_tlv_ensure(tlv, len) < 0)
+        return NULL;
+
+    if (pad)
+        memset(tlv->p, 0, pad);
+
+    reserved = tlv->p + pad;
+    tlv->p  += len;
+
+    return reserved;
+}
+
+
+int mrp_tlv_setup_read(mrp_tlv_t *tlv, void *buf, size_t size)
+{
+    tlv->buf   = tlv->p = buf;
+    tlv->size  = size;
+    tlv->write = 0;
+
+    return 0;
+}
+
+
+static void *tlv_consume(mrp_tlv_t *tlv, size_t size)
+{
+    char *p;
+
+    if (tlv_data(tlv) < size)
+        return NULL;
+
+    p = tlv->p;
+    tlv->p += size;
+
+    return p;
+}
+
+
+void mrp_tlv_trim(mrp_tlv_t *tlv)
+{
+    size_t left;
+
+    if (!tlv->write)
+        return;
+
+    if ((left = tlv_space(tlv)) == 0)
+        return;
+
+    tlv->p -= (ptrdiff_t)tlv->buf;
+
+    if (mrp_realloc(tlv->buf, tlv->size - left) != NULL) {
+        tlv->size -= left;
+        tlv->p    += (ptrdiff_t)tlv->buf;
+    }
+}
+
+
+size_t mrp_tlv_offset(mrp_tlv_t *tlv)
+{
+    return (size_t)(tlv->p - tlv->buf);
+}
+
+
+void mrp_tlv_cleanup(mrp_tlv_t *tlv)
+{
+    if (tlv->write)
+        mrp_free(tlv->buf);
+
+    tlv->buf  = tlv->p = NULL;
+    tlv->size = 0;
+}
+
+
+void mrp_tlv_steal(mrp_tlv_t *tlv, void **bufp, size_t *sizep)
+{
+    if (tlv->write) {
+        *bufp  = tlv->buf;
+        *sizep = tlv->p - tlv->buf;
+
+        tlv->buf  = tlv->p = NULL;
+        tlv->size = 0;
+    }
+    else {
+        *bufp  = NULL;
+        *sizep = 0;
+    }
+}
+
+
+static inline int push_tag(mrp_tlv_t *tlv, uint32_t tag)
+{
+    uint32_t *tagp;
+
+    if (tag) {
+        if ((tagp = mrp_tlv_reserve(tlv, sizeof(*tagp), 1)) == NULL)
+            return -1;
+        else
+            *tagp = htobe32(tag);
+    }
+
+    return 0;
+}
+
+
+int mrp_tlv_push_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t v)
+{
+    int8_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = v;
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t v)
+{
+    uint8_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = v;
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t v)
+{
+    int16_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = htobe16(v);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t v)
+{
+    uint16_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = htobe16(v);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t v)
+{
+    int32_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = htobe32(v);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t v)
+{
+    uint32_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = htobe32(v);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t v)
+{
+    int64_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = htobe64(v);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t v)
+{
+    uint64_t *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = htobe64(v);
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_float(mrp_tlv_t *tlv, uint32_t tag, float v)
+{
+    float *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = v;
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_double(mrp_tlv_t *tlv, uint32_t tag, double v)
+{
+    double *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = v;
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_bool(mrp_tlv_t *tlv, uint32_t tag, bool v)
+{
+    bool *p;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = mrp_tlv_reserve(tlv, sizeof(*p), 1)) != NULL) {
+        *p = v;
+
+        return 0;
+    }
+
+    return -1;
+}
+
+
+int mrp_tlv_push_string(mrp_tlv_t *tlv, uint32_t tag, const char *str)
+{
+    uint32_t *sizep;
+    char     *strp;
+    size_t    len = str ? strlen(str) + 1 : 0;
+
+    if (push_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((sizep = mrp_tlv_reserve(tlv, sizeof(*sizep), 1)) == NULL)
+        return -1;
+
+    *sizep = htobe32((uint32_t)len);
+
+    if (len > 0) {
+        if ((strp = mrp_tlv_reserve(tlv, len, 1)) == NULL)
+            return -1;
+
+        strcpy(strp, str);
+    }
+
+    return 0;
+}
+
+
+int pull_tag(mrp_tlv_t *tlv, uint32_t tag)
+{
+    uint32_t *tagp;
+
+    if (tag) {
+        if ((tagp = tlv_consume(tlv, sizeof(*tagp))) == NULL)
+            return -1;
+
+        if (be32toh(*tagp) != tag)
+            return -1;
+    }
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t *v)
+{
+    int8_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = *p;
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t *v)
+{
+    uint8_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = *p;
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t *v)
+{
+    int16_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = be16toh(*p);
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t *v)
+{
+    uint16_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = be16toh(*p);
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t *v)
+{
+    int32_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = be32toh(*p);
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t *v)
+{
+    uint32_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = be32toh(*p);
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t *v)
+{
+    int64_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = be64toh(*p);
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t *v)
+{
+    uint64_t *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = be64toh(*p);
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_float(mrp_tlv_t *tlv, uint32_t tag, float *v)
+{
+    float *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = *p;
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_double(mrp_tlv_t *tlv, uint32_t tag, double *v)
+{
+    double *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = *p;
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_bool(mrp_tlv_t *tlv, uint32_t tag, bool *v)
+{
+    bool *p;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((p = tlv_consume(tlv, sizeof(*p))) == NULL)
+        return -1;
+
+    *v = *p;
+
+    return 0;
+}
+
+
+int mrp_tlv_pull_string(mrp_tlv_t *tlv, uint32_t tag, char **v, size_t max,
+                        void *(alloc)(size_t, void *), void *alloc_data)
+{
+    uint32_t *sizep, size;
+    char     *str;
+
+    if (pull_tag(tlv, tag) < 0)
+        return -1;
+
+    if ((sizep = tlv_consume(tlv, sizeof(*sizep))) == NULL)
+        return -1;
+
+    size = be32toh(*sizep);
+
+    if (max != (size_t)-1 && max < size) {
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    if (size > 0) {
+        if ((str = tlv_consume(tlv, size)) == NULL)
+            return -1;
+
+        if (*v == NULL)
+            if ((*v = alloc(size, alloc_data)) == NULL)
+                return -1;
+
+        strncpy(*v, str, size - 1);
+        (*v)[size - 1] = '\0';
+    }
+    else
+        *v = NULL;
+
+    return 0;
+}
diff --git a/src/common/tlv.h b/src/common/tlv.h
new file mode 100644 (file)
index 0000000..b746546
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MRP_COMMON_TLV_H__
+#define __MRP_COMMON_TLV_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <murphy/common/macros.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_TLV_UNTAGGED 0
+
+/**
+ * a tagged-value-list encoding/decoding buffer
+ */
+
+typedef struct {
+    void   *buf;                         /* actual data buffer */
+    size_t  size;                        /* allocated buffer size */
+    void   *p;                           /* encoding/decoding pointer */
+    int     write : 1;                   /* whether set up for writing */
+} mrp_tlv_t;
+
+/** Set up the given TLV buffer for encoding. */
+int mrp_tlv_setup_write(mrp_tlv_t *tlv, size_t prealloc);
+
+/** Set up the given TLV buffer for decoding. */
+int mrp_tlv_setup_read(mrp_tlv_t *tlv, void *buf, size_t size);
+
+/** Clean up the given TLV buffer. */
+void mrp_tlv_cleanup(mrp_tlv_t *tlv);
+
+/** Ensure the given amount of space is available in the TLV buffer. */
+int mrp_tlv_ensure(mrp_tlv_t *tlv, size_t size);
+
+/** Reserve the given amount of buffer space from the TLV buffer. */
+void *mrp_tlv_reserve(mrp_tlv_t *tlv, size_t size, int align);
+
+/** Take ownership of the data buffer from the TLV buffer. */
+void mrp_tlv_steal(mrp_tlv_t *tlv, void **bufp, size_t *sizep);
+
+/** Trim the data buffer of the TLV buffer to current amount of data. */
+void mrp_tlv_trim(mrp_tlv_t *tlv);
+
+/** Get the current read/write offset from the TLV buffer. */
+size_t mrp_tlv_offset(mrp_tlv_t *tlv);
+
+/** Add an int8_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t v);
+
+/** Add an uint8_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t v);
+
+/** Add an int16_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t v);
+
+/** Add an uint16_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t v);
+
+/** Add an int32_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t v);
+
+/** Add an uint32_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t v);
+
+/** Add an int64_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t v);
+
+/** Add an uint64_t with an optional tag to the TLV buffer. */
+int mrp_tlv_push_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t v);
+
+/** Add an float with an optional tag to the TLV buffer. */
+int mrp_tlv_push_float(mrp_tlv_t *tlv, uint32_t tag, float v);
+
+/** Add an double with an optional tag to the TLV buffer. */
+int mrp_tlv_push_double(mrp_tlv_t *tlv, uint32_t tag, double v);
+
+/** Add a boolean with an optional tag to the TLV buffer. */
+int mrp_tlv_push_bool(mrp_tlv_t *tlv, uint32_t tag, bool v);
+
+/** Add a string with an optional tag to the TLV buffer. */
+int mrp_tlv_push_string(mrp_tlv_t *tlv, uint32_t tag, const char *str);
+
+
+int mrp_tlv_pull_int8(mrp_tlv_t *tlv, uint32_t tag, int8_t *v);
+int mrp_tlv_pull_uint8(mrp_tlv_t *tlv, uint32_t tag, uint8_t *v);
+int mrp_tlv_pull_int16(mrp_tlv_t *tlv, uint32_t tag, int16_t *v);
+int mrp_tlv_pull_uint16(mrp_tlv_t *tlv, uint32_t tag, uint16_t *v);
+int mrp_tlv_pull_int32(mrp_tlv_t *tlv, uint32_t tag, int32_t *v);
+int mrp_tlv_pull_uint32(mrp_tlv_t *tlv, uint32_t tag, uint32_t *v);
+int mrp_tlv_pull_int64(mrp_tlv_t *tlv, uint32_t tag, int64_t *v);
+int mrp_tlv_pull_uint64(mrp_tlv_t *tlv, uint32_t tag, uint64_t *v);
+int mrp_tlv_pull_float(mrp_tlv_t *tlv, uint32_t tag, float *v);
+int mrp_tlv_pull_double(mrp_tlv_t *tlv, uint32_t tag, double *v);
+int mrp_tlv_pull_bool(mrp_tlv_t *tlv, uint32_t tag, bool *v);
+int mrp_tlv_pull_string(mrp_tlv_t *tlv, uint32_t tag, char **v, size_t max,
+                        void *(alloc)(size_t, void *), void *alloc_data);
+
+MRP_CDECL_END
+
+#endif /* __MRP_COMMON_TLV_H__ */
diff --git a/src/common/transport.c b/src/common/transport.c
new file mode 100644 (file)
index 0000000..d00c588
--- /dev/null
@@ -0,0 +1,829 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/native-types.h>
+#include <murphy/common/transport.h>
+
+static int check_destroy(mrp_transport_t *t);
+static int recv_data(mrp_transport_t *t, void *data, size_t size,
+                     mrp_sockaddr_t *addr, socklen_t addrlen);
+static inline int purge_destroyed(mrp_transport_t *t);
+
+
+static MRP_LIST_HOOK(transports);
+static mrp_sighandler_t *pipe_handler;
+
+
+static int check_request_callbacks(mrp_transport_req_t *req)
+{
+    /* XXX TODO: hmm... this probably needs more thought/work */
+
+    if (!req->open || !req->close)
+        return FALSE;
+
+    if (req->accept) {
+        if (!req->sendmsg || !req->sendraw || !req->senddata)
+            return FALSE;
+    }
+    else {
+        if (!req->sendmsgto || !req->sendrawto || !req->senddatato)
+            return FALSE;
+    }
+
+    if (( req->connect && !req->disconnect) ||
+        (!req->connect &&  req->disconnect))
+        return FALSE;
+
+    return TRUE;
+}
+
+
+int mrp_transport_register(mrp_transport_descr_t *d)
+{
+    if (!check_request_callbacks(&d->req))
+        return FALSE;
+
+    if (d->size >= sizeof(mrp_transport_t)) {
+        mrp_list_init(&d->hook);
+        mrp_list_append(&transports, &d->hook);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+void mrp_transport_unregister(mrp_transport_descr_t *d)
+{
+    mrp_list_delete(&d->hook);
+}
+
+
+static mrp_transport_descr_t *find_transport(const char *type)
+{
+    mrp_transport_descr_t *d;
+    mrp_list_hook_t       *p, *n;
+
+    mrp_list_foreach(&transports, p, n) {
+        d = mrp_list_entry(p, typeof(*d), hook);
+        if (!strcmp(d->type, type))
+            return d;
+    }
+
+    return NULL;
+}
+
+
+static int check_event_callbacks(mrp_transport_evt_t *evt)
+{
+    /*
+     * For connection-oriented transports we require a recv* callback
+     * and a closed callback.
+     *
+     * For connectionless transports we only require a recvfrom* callback.
+     * A recv* callback is optional, however the transport cannot be put
+     * to connected mode (usually for doing sender-based filtering) if
+     * recv* is omitted.
+     */
+
+    if (evt->connection != NULL) {
+        if (evt->recvmsg == NULL || evt->closed == NULL)
+            return FALSE;
+    }
+    else {
+        if (evt->recvmsgfrom == NULL)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static void sigpipe_handler(mrp_sighandler_t *h, int sig, void *user_data)
+{
+    MRP_UNUSED(h);
+    MRP_UNUSED(user_data);
+
+    mrp_debug("caught signal %d (%s)...", sig, strsignal(sig));
+}
+
+
+mrp_transport_t *mrp_transport_create(mrp_mainloop_t *ml, const char *type,
+                                      mrp_transport_evt_t *evt, void *user_data,
+                                      int flags)
+{
+    mrp_transport_descr_t *d;
+    mrp_transport_t       *t;
+
+    if (!pipe_handler)
+        pipe_handler = mrp_add_sighandler(ml, SIGPIPE, sigpipe_handler, NULL);
+
+    if (!check_event_callbacks(evt)) {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if ((d = find_transport(type)) != NULL) {
+        if ((t = mrp_allocz(d->size)) != NULL) {
+            t->descr     = d;
+            t->ml        = ml;
+            t->evt       = *evt;
+            t->user_data = user_data;
+
+            t->check_destroy = check_destroy;
+            t->recv_data     = recv_data;
+            t->flags         = flags & ~MRP_TRANSPORT_MODE_MASK;
+            t->mode          = flags &  MRP_TRANSPORT_MODE_MASK;
+
+            if (!t->descr->req.open(t)) {
+                mrp_free(t);
+                t = NULL;
+            }
+        }
+    }
+    else
+        t = NULL;
+
+    return t;
+}
+
+
+mrp_transport_t *mrp_transport_create_from(mrp_mainloop_t *ml, const char *type,
+                                           void *conn, mrp_transport_evt_t *evt,
+                                           void *user_data, int flags,
+                                           int state)
+{
+    mrp_transport_descr_t *d;
+    mrp_transport_t       *t;
+
+    if (!pipe_handler)
+        pipe_handler = mrp_add_sighandler(ml, SIGPIPE, sigpipe_handler, NULL);
+
+    if (!check_event_callbacks(evt)) {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if ((d = find_transport(type)) != NULL) {
+        if ((t = mrp_allocz(d->size)) != NULL) {
+            t->descr     = d;
+            t->ml        = ml;
+            t->evt       = *evt;
+            t->user_data = user_data;
+
+            t->check_destroy = check_destroy;
+            t->recv_data     = recv_data;
+            t->flags         = flags & ~MRP_TRANSPORT_MODE_MASK;
+            t->mode          = flags &  MRP_TRANSPORT_MODE_MASK;
+
+            t->connected = !!(state & MRP_TRANSPORT_CONNECTED);
+            t->listened  = !!(state & MRP_TRANSPORT_LISTENED);
+
+            if (t->connected && t->listened) {
+                mrp_free(t);
+                return NULL;
+            }
+
+            if (!t->descr->req.createfrom(t, conn)) {
+                mrp_free(t);
+                t = NULL;
+            }
+        }
+    }
+    else
+        t = NULL;
+
+    return t;
+}
+
+
+int mrp_transport_setopt(mrp_transport_t *t, const char *opt, const void *val)
+{
+    if (t != NULL) {
+        if (t->descr->req.setopt != NULL)
+            return t->descr->req.setopt(t, opt, val);
+        else {
+            if (t->mode == MRP_TRANSPORT_MODE_NATIVE) {
+                if (!strcmp(opt, MRP_TRANSPORT_OPT_TYPEMAP)) {
+                    t->map = (void *)val;
+                    return TRUE;
+                }
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static inline int type_matches(const char *type, const char *addr)
+{
+    while (*type == *addr)
+        type++, addr++;
+
+    return (*type == '\0' && *addr == ':');
+}
+
+
+socklen_t mrp_transport_resolve(mrp_transport_t *t, const char *str,
+                                mrp_sockaddr_t *addr, socklen_t size,
+                                const char **typep)
+{
+    mrp_transport_descr_t *d;
+    mrp_list_hook_t       *p, *n;
+    socklen_t              l;
+
+    if (t != NULL)
+        return t->descr->resolve(str, addr, size, typep);
+    else {
+        mrp_list_foreach(&transports, p, n) {
+            d = mrp_list_entry(p, typeof(*d), hook);
+            l = d->resolve(str, addr, size, typep);
+
+            if (l > 0)
+                return l;
+        }
+    }
+
+    return 0;
+}
+
+
+int mrp_transport_bind(mrp_transport_t *t, mrp_sockaddr_t *addr,
+                       socklen_t addrlen)
+{
+    if (t != NULL) {
+        if (t->descr->req.bind != NULL)
+            return t->descr->req.bind(t, addr, addrlen);
+        else
+            return TRUE;                  /* assume no binding is needed */
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_transport_listen(mrp_transport_t *t, int backlog)
+{
+    int result;
+
+    if (t != NULL) {
+        if (t->descr->req.listen != NULL) {
+            MRP_TRANSPORT_BUSY(t, {
+                    result = t->descr->req.listen(t, backlog);
+                });
+
+            purge_destroyed(t);
+
+            return result;
+        }
+    }
+
+    return FALSE;
+}
+
+
+mrp_transport_t *mrp_transport_accept(mrp_transport_t *lt,
+                                      void *user_data, int flags)
+{
+    mrp_transport_t *t;
+
+    if ((t = mrp_allocz(lt->descr->size)) != NULL) {
+        bool failed  = FALSE;
+        t->descr     = lt->descr;
+        t->ml        = lt->ml;
+        t->evt       = lt->evt;
+        t->user_data = user_data;
+
+        t->check_destroy = check_destroy;
+        t->recv_data     = recv_data;
+        t->flags         = (lt->flags & MRP_TRANSPORT_INHERIT) | flags;
+        t->flags         = t->flags & ~MRP_TRANSPORT_MODE_MASK;
+        t->mode          = lt->mode;
+        t->map           = lt->map;
+
+        MRP_TRANSPORT_BUSY(t, {
+                if (!t->descr->req.accept(t, lt)) {
+                    failed = TRUE;
+                }
+                else {
+                    t->connected = TRUE;
+                }
+            });
+
+        if (failed) {
+            mrp_free(t);
+            t = NULL;
+        }
+    }
+
+    return t;
+}
+
+
+static inline int purge_destroyed(mrp_transport_t *t)
+{
+    if (t->destroyed && !t->busy) {
+        mrp_debug("destroying transport %p...", t);
+        mrp_free(t);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+void mrp_transport_destroy(mrp_transport_t *t)
+{
+    if (t != NULL) {
+        t->destroyed = TRUE;
+
+        MRP_TRANSPORT_BUSY(t, {
+                t->descr->req.disconnect(t);
+                t->descr->req.close(t);
+            });
+
+        purge_destroyed(t);
+    }
+}
+
+
+static int check_destroy(mrp_transport_t *t)
+{
+    return purge_destroyed(t);
+}
+
+
+int mrp_transport_connect(mrp_transport_t *t, mrp_sockaddr_t *addr,
+                          socklen_t addrlen)
+{
+    int result;
+
+    if (!t->connected) {
+
+        /* make sure we can deliver reception noifications */
+        if (t->evt.recvmsg == NULL) {
+            errno = EINVAL;
+            return FALSE;
+        }
+
+        MRP_TRANSPORT_BUSY(t, {
+                if (t->descr->req.connect(t, addr, addrlen))  {
+                    t->connected = TRUE;
+                    result       = TRUE;
+                }
+                else
+                    result = FALSE;
+            });
+
+        purge_destroyed(t);
+    }
+    else {
+        errno  = EISCONN;
+        result = FALSE;
+    }
+
+    return result;
+}
+
+
+int mrp_transport_disconnect(mrp_transport_t *t)
+{
+    int result;
+
+    if (t != NULL && t->connected) {
+        MRP_TRANSPORT_BUSY(t, {
+                if (t->descr->req.disconnect(t)) {
+                    t->connected = FALSE;
+                    result       = TRUE;
+                }
+                else
+                    result = TRUE;
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_send(mrp_transport_t *t, mrp_msg_t *msg)
+{
+    int result;
+
+    if (t->connected && t->descr->req.sendmsg) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendmsg(t, msg);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendto(mrp_transport_t *t, mrp_msg_t *msg,
+                         mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    int result;
+
+    if (t->descr->req.sendmsgto) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendmsgto(t, msg, addr, addrlen);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendraw(mrp_transport_t *t, void *data, size_t size)
+{
+    int result;
+
+    if (t->connected &&
+        t->mode == MRP_TRANSPORT_MODE_RAW && t->descr->req.sendraw) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendraw(t, data, size);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendrawto(mrp_transport_t *t, void *data, size_t size,
+                            mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_RAW && t->descr->req.sendrawto) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendrawto(t, data, size, addr, addrlen);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_senddata(mrp_transport_t *t, void *data, uint16_t tag)
+{
+    int result;
+
+    if (t->connected &&
+        t->mode == MRP_TRANSPORT_MODE_DATA && t->descr->req.senddata) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.senddata(t, data, tag);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_senddatato(mrp_transport_t *t, void *data, uint16_t tag,
+                             mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_DATA && t->descr->req.senddatato) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.senddatato(t, data, tag, addr, addrlen);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendcustom(mrp_transport_t *t, void *data)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_CUSTOM && t->descr->req.sendcustom) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendcustom(t, data);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendcustomto(mrp_transport_t *t, void *data,
+                               mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_CUSTOM && t->descr->req.sendcustomto) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendcustomto(t, data, addr, addrlen);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendnative(mrp_transport_t *t, void *data, uint32_t type_id)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_NATIVE && t->descr->req.sendnative) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendnative(t, data, type_id);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendnativeto(mrp_transport_t *t, void *data, uint32_t type_id,
+                               mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_NATIVE && t->descr->req.sendnativeto) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendnativeto(t, data, type_id,
+                                                    addr, addrlen);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendjson(mrp_transport_t *t, mrp_json_t *msg)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_JSON && t->descr->req.sendjson) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendjson(t, msg);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+int mrp_transport_sendjsonto(mrp_transport_t *t, mrp_json_t *msg,
+                             mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    int result;
+
+    if (t->mode == MRP_TRANSPORT_MODE_JSON && t->descr->req.sendjsonto) {
+        MRP_TRANSPORT_BUSY(t, {
+                result = t->descr->req.sendjsonto(t, msg, addr, addrlen);
+            });
+
+        purge_destroyed(t);
+    }
+    else
+        result = FALSE;
+
+    return result;
+}
+
+
+static int recv_data(mrp_transport_t *t, void *data, size_t size,
+                     mrp_sockaddr_t *addr, socklen_t addrlen)
+{
+    mrp_data_descr_t *type;
+    uint16_t          tag;
+    mrp_msg_t        *msg;
+    uint32_t          type_id;
+    void             *decoded;
+
+    switch (t->mode) {
+    case MRP_TRANSPORT_MODE_DATA:
+        tag   = be16toh(*(uint16_t *)data);
+        data += sizeof(tag);
+        size -= sizeof(tag);
+        type  = mrp_msg_find_type(tag);
+
+        if (type != NULL) {
+            decoded = mrp_data_decode(&data, &size, type);
+
+            if (decoded != NULL && size == 0) {
+                if (t->connected && t->evt.recvdata) {
+                    MRP_TRANSPORT_BUSY(t, {
+                            t->evt.recvdata(t, decoded, tag, t->user_data);
+                        });
+                }
+                else if (t->evt.recvdatafrom) {
+                    MRP_TRANSPORT_BUSY(t, {
+                            t->evt.recvdatafrom(t, decoded, tag, addr, addrlen,
+                                                t->user_data);
+                        });
+                }
+                else
+                    mrp_free(decoded);         /* no callback, discard */
+
+                return 0;
+            }
+            else {
+                if (decoded != NULL) {
+                    mrp_free(decoded);
+                    return -EMSGSIZE;
+                }
+                else
+                    return -errno;
+            }
+        }
+        else
+            return -ENOPROTOOPT;
+        break;
+
+    case MRP_TRANSPORT_MODE_RAW:
+        if (t->connected) {
+            MRP_TRANSPORT_BUSY(t, {
+                    t->evt.recvraw(t, data, size, t->user_data);
+                });
+        }
+        else {
+            MRP_TRANSPORT_BUSY(t, {
+                    t->evt.recvrawfrom(t, data, size, addr, addrlen,
+                                       t->user_data);
+                });
+        }
+        return 0;
+
+    case MRP_TRANSPORT_MODE_MSG:
+        tag   = be16toh(*(uint16_t *)data);
+        data += sizeof(tag);
+        size -= sizeof(tag);
+
+        if (tag != MRP_MSG_TAG_DEFAULT ||
+            (msg = mrp_msg_default_decode(data, size)) == NULL) {
+            return -EPROTO;
+        }
+        else {
+            if (t->connected) {
+                MRP_TRANSPORT_BUSY(t, {
+                        t->evt.recvmsg(t, msg, t->user_data);
+                    });
+            }
+            else {
+                MRP_TRANSPORT_BUSY(t, {
+                        t->evt.recvmsgfrom(t, msg, addr, addrlen,
+                                           t->user_data);
+                    });
+            }
+
+            mrp_msg_unref(msg);
+
+            return 0;
+        }
+        break;
+
+    case MRP_TRANSPORT_MODE_CUSTOM:
+        if (t->connected) {
+            if (t->evt.recvcustom) {
+                MRP_TRANSPORT_BUSY(t, {
+                        t->evt.recvcustom(t, data, t->user_data);
+                    });
+
+                return 0;
+            }
+        }
+        else {
+            if (t->evt.recvcustomfrom) {
+                MRP_TRANSPORT_BUSY(t, {
+                        t->evt.recvcustomfrom(t, data, addr, addrlen,
+                                              t->user_data);
+                    });
+
+                return 0;
+            }
+        }
+        return -EPROTOTYPE;
+
+    case MRP_TRANSPORT_MODE_NATIVE:
+        type_id = 0;
+        if (mrp_decode_native(&data, &size, &decoded, &type_id, t->map) < 0)
+            return -EPROTO;
+
+        if (decoded == NULL || size != 0) {
+            mrp_free_native(decoded, type_id);
+            return -EPROTO;
+        }
+
+        if (t->connected) {
+            MRP_TRANSPORT_BUSY(t, {
+                    t->evt.recvnative(t, decoded, type_id, t->user_data);
+                });
+        }
+        else {
+            MRP_TRANSPORT_BUSY(t, {
+                    t->evt.recvnativefrom(t, decoded, type_id, addr, addrlen,
+                                          t->user_data);
+                });
+        }
+        return 0;
+
+    case MRP_TRANSPORT_MODE_JSON:
+        if (t->connected) {
+            if (t->evt.recvjson) {
+                MRP_TRANSPORT_BUSY(t, {
+                        t->evt.recvjson(t, data, t->user_data);
+                    });
+            }
+        }
+        else {
+            if (t->evt.recvjsonfrom) {
+                MRP_TRANSPORT_BUSY(t, {
+                        t->evt.recvjsonfrom(t, data, addr, addrlen,
+                                            t->user_data);
+                    });
+            }
+        }
+        return 0;
+
+    default:
+        return -EPROTOTYPE;
+    }
+}
+
diff --git a/src/common/transport.h b/src/common/transport.h
new file mode 100644 (file)
index 0000000..7b57a2c
--- /dev/null
@@ -0,0 +1,559 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_TRANSPORT_H__
+#define __MURPHY_TRANSPORT_H__
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/un.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/native-types.h>
+
+/*
+ * json-c and JSON-Glib have a symbol clash on json_object_get_type.
+ * Unfortunately we'd really need to include our own json.h here to
+ * get mrp_json_t defined. That however pulls in json-c's headers as
+ * our implementation uses json-c and the type itself is just a
+ * typedef'd alias to json_object.
+ *
+ * Now if some unfortunate sould ends up directly or indirectly
+ * including both our transport.h, and consequently json.h, and
+ * JSON-Glib, we'll trigger the symbol clash.
+ *
+ * As a workaround if we detect that JSON-Glib has already been
+ * included we'll compile with alternative signatures (void *,
+ * instead of mrp_json_t *) and omit including json.h. Also we
+ * let people give us a warning by defining __JSON_GLIB_DANGER__
+ * that they will or might include JSON-Glib, in which case
+ * we also compile with the alternative signatures. Oh boy...
+ */
+
+#if !defined(__JSON_TYPES_H__) && !defined(__JSON_GLIB_DANGER__)
+#  include <murphy/common/json.h>
+#else
+#  define mrp_json_t void
+#endif
+
+MRP_CDECL_BEGIN
+
+typedef struct mrp_transport_s mrp_transport_t;
+
+
+
+/*
+ * transport socket address
+ */
+
+#define MRP_SOCKADDR_SIZE 256
+
+typedef union {
+    struct sockaddr     any;
+    struct sockaddr_in  ipv4;
+    struct sockaddr_in6 ipv6;
+    struct sockaddr_un  unx;
+    char                data[MRP_SOCKADDR_SIZE];
+} mrp_sockaddr_t;
+
+
+static inline mrp_sockaddr_t *mrp_sockaddr_cpy(mrp_sockaddr_t *d,
+                                               mrp_sockaddr_t *s, socklen_t n)
+{
+    memcpy(d, s, n);
+    return d;
+}
+
+
+/*
+ * various transport flags
+ */
+
+typedef enum {
+    MRP_TRANSPORT_MODE_MSG    = 0x00,    /* generic message encoding */
+    MRP_TRANSPORT_MODE_RAW    = 0x01,    /* uses bitpipe mode */
+    MRP_TRANSPORT_MODE_DATA   = 0x02,    /* uses registered data types */
+    MRP_TRANSPORT_MODE_CUSTOM = 0x03,    /* custom message encoding */
+    MRP_TRANSPORT_MODE_NATIVE = 0x04,    /* uses registered native-types */
+    MRP_TRANSPORT_MODE_JSON   = 0x05,    /* uses JSON messages */
+} mrp_transport_mode_t;
+
+typedef enum {
+    MRP_TRANSPORT_MODE_MASK   = 0x0f,    /* mask of mode bits */
+    MRP_TRANSPORT_INHERIT     = 0x0f,    /* mask of all inherited flags */
+
+    MRP_TRANSPORT_REUSEADDR = 0x010,
+    MRP_TRANSPORT_NONBLOCK  = 0x020,
+    MRP_TRANSPORT_CLOEXEC   = 0x040,
+    MRP_TRANSPORT_CONNECTED = 0x080,
+    MRP_TRANSPORT_LISTENED  = 0x001,
+} mrp_transport_flag_t;
+
+#define MRP_TRANSPORT_MODE(t) ((t)->flags & MRP_TRANSPORT_MODE_MASK)
+
+
+#define MRP_TRANSPORT_OPT_TYPEMAP "type-map"
+
+/*
+ * transport requests
+ *
+ * Transport requests correspond to top-down event propagation in the
+ * communication stack. These requests are made by the core tansport
+ * abstraction layer to the underlying actual transport implementation
+ * to carry out the implementation-specific details of some transport
+ * operation.
+ */
+
+typedef struct {
+    /** Open a new transport. */
+    int  (*open)(mrp_transport_t *t);
+    /** Create a new transport from an existing backend object. */
+    int  (*createfrom)(mrp_transport_t *t, void *obj);
+    /** Bind a transport to a given transport-specific address. */
+    int  (*bind)(mrp_transport_t *t, mrp_sockaddr_t *addr, socklen_t addrlen);
+    /** Listen on a transport for incoming connections. */
+    int  (*listen)(mrp_transport_t *t, int backlog);
+    /** Accept a new transport connection over an existing transport. */
+    int  (*accept)(mrp_transport_t *t, mrp_transport_t *lt);
+    /** Connect a transport to an endpoint. */
+    int  (*connect)(mrp_transport_t *t, mrp_sockaddr_t *addr,
+                    socklen_t addrlen);
+    /** Disconnect a transport, if it is connection-oriented. */
+    int  (*disconnect)(mrp_transport_t *t);
+    /** Close a transport, free all resources from open/accept/connect. */
+    void (*close)(mrp_transport_t *t);
+    /** Set a (possibly type specific) transport option. */
+    int  (*setopt)(mrp_transport_t *t, const char *opt, const void *value);
+    /** Send a message over a (connected) transport. */
+    int (*sendmsg)(mrp_transport_t *t, mrp_msg_t *msg);
+    /** Send raw data over a (connected) transport. */
+    int (*sendraw)(mrp_transport_t *t, void *buf, size_t size);
+    /** Send registered data over a (connected) transport. */
+    int (*senddata)(mrp_transport_t *t, void *data, uint16_t tag);
+    /** Send data with a custom encoder over a transport. */
+    int (*sendcustom)(mrp_transport_t *t, void *data);
+    /** Send a native type over a (connected) transport. */
+    int (*sendnative)(mrp_transport_t *t, void *data, uint32_t type_id);
+    /** Send a JSON message over a (connected) transport. */
+    int (*sendjson)(mrp_transport_t *t, mrp_json_t *msg);
+
+    /** Send a message over a(n unconnected) transport. */
+    int (*sendmsgto)(mrp_transport_t *t, mrp_msg_t *msg, mrp_sockaddr_t *addr,
+                     socklen_t addrlen);
+    /** Send raw data over a(n unconnected) transport. */
+    int (*sendrawto)(mrp_transport_t *t, void *buf, size_t size,
+                     mrp_sockaddr_t *addr, socklen_t addrlen);
+    /** Send registered data over a(n unconnected) transport. */
+    int (*senddatato)(mrp_transport_t *t, void *data, uint16_t tag,
+                      mrp_sockaddr_t *addr, socklen_t addrlen);
+    /** Send data with a custom encoder over a transport. */
+    int (*sendcustomto)(mrp_transport_t *t, void *data,
+                        mrp_sockaddr_t *addr, socklen_t addrlen);
+    /** Send a native type over a transport. */
+    int (*sendnativeto)(mrp_transport_t *t, void *data, uint32_t type_id,
+                        mrp_sockaddr_t *addr, socklen_t addrlen);
+    /** Send a JSON messgae over a(n unconnected) transport. */
+    int (*sendjsonto)(mrp_transport_t *t, mrp_json_t *msg, mrp_sockaddr_t *addr,
+                      socklen_t addrlen);
+} mrp_transport_req_t;
+
+
+/*
+ * transport events
+ *
+ * Transport events correspond to bottom-up event propagation in the
+ * communication stack. These callbacks are made by the actual transport
+ * implementation to the generic transport abstraction to inform it
+ * about relevant transport events, such as the reception of data, or
+ * transport disconnection by the peer.
+ */
+
+typedef struct {
+    /** Message received on a connected transport. */
+    union {
+        /** Generic message callback for connected transports. */
+        void (*recvmsg)(mrp_transport_t *t, mrp_msg_t *msg, void *user_data);
+        /** Raw data callback for connected transports. */
+        void (*recvraw)(mrp_transport_t *t, void *data, size_t size,
+                        void *user_data);
+        /** Registered data callback for connected transports. */
+        void (*recvdata)(mrp_transport_t *t, void *data, uint16_t tag,
+                         void *user_data);
+        /** Custom encoded data callback for connected transports. */
+        void (*recvcustom)(mrp_transport_t *t, void *data,
+                           void *user_data);
+        /** Native type callback for connected transports. */
+        void (*recvnative)(mrp_transport_t *t, void *data, uint32_t type_id,
+                           void *user_data);
+        /** JSON type callback for connected transports. */
+        void (*recvjson)(mrp_transport_t *t, mrp_json_t *msg, void *user_data);
+    };
+
+    /** Message received on an unconnected transport. */
+    union {
+        /** Generic message callback for unconnected transports. */
+        void (*recvmsgfrom)(mrp_transport_t *t, mrp_msg_t *msg,
+                            mrp_sockaddr_t *addr, socklen_t addrlen,
+                            void *user_data);
+        /** Raw data callback for unconnected transports. */
+        void (*recvrawfrom)(mrp_transport_t *t, void *data, size_t size,
+                            mrp_sockaddr_t *addr, socklen_t addrlen,
+                            void *user_data);
+        /** Registered data callback for unconnected transports. */
+        void (*recvdatafrom)(mrp_transport_t *t, void *data, uint16_t tag,
+                             mrp_sockaddr_t *addr, socklen_t addrlen,
+                             void *user_data);
+        /** Custom encoded data callback for unconnected transports. */
+        void (*recvcustomfrom)(mrp_transport_t *t, void *data,
+                               mrp_sockaddr_t *addr, socklen_t addrlen,
+                               void *user_data);
+        /** Native type callback for unconnected transports. */
+        void (*recvnativefrom)(mrp_transport_t *t, void *data, uint32_t type_id,
+                               mrp_sockaddr_t *addr, socklen_t addrlen,
+                               void *user_data);
+        /** JSON type callback for unconnected transports. */
+        void (*recvjsonfrom)(mrp_transport_t *t, mrp_json_t *msg,
+                             mrp_sockaddr_t *addr, socklen_t addrlen,
+                             void *user_data);
+    };
+    /** Connection closed by peer. */
+    void (*closed)(mrp_transport_t *t, int error, void *user_data);
+    /** Connection attempt on a socket being listened on. */
+    void (*connection)(mrp_transport_t *t, void *user_data);
+} mrp_transport_evt_t;
+
+
+/*
+ * transport descriptor
+ */
+
+typedef struct {
+    const char          *type;           /* transport type name */
+    size_t               size;           /* full transport struct size */
+    mrp_transport_req_t  req;            /* transport requests */
+    socklen_t          (*resolve)(const char *str, mrp_sockaddr_t *addr,
+                                  socklen_t addrlen, const char **typep);
+    mrp_list_hook_t      hook;           /* to list of registered transports */
+} mrp_transport_descr_t;
+
+
+/*
+ * transport
+ */
+
+#define MRP_TRANSPORT_PUBLIC_FIELDS                                       \
+    mrp_mainloop_t          *ml;                                          \
+    mrp_transport_descr_t   *descr;                                       \
+    mrp_transport_evt_t      evt;                                         \
+    int                    (*check_destroy)(mrp_transport_t *t);          \
+    int                    (*recv_data)(mrp_transport_t *t, void *data,   \
+                                        size_t size,                      \
+                                        mrp_sockaddr_t *addr,             \
+                                        socklen_t addrlen);               \
+    void                    *user_data;                                   \
+    mrp_typemap_t           *map;                                         \
+    int                      flags;                                       \
+    int                      mode;                                        \
+    int                      busy;                                        \
+    int                      connected : 1;                               \
+    int                      listened : 1;                                \
+    int                      destroyed : 1                                \
+
+
+struct mrp_transport_s {
+    MRP_TRANSPORT_PUBLIC_FIELDS;
+};
+
+
+/*
+ * Notes:
+ *
+ *    Transports can get destructed in two slightly different ways.
+ *
+ *    1)
+ *      Someone calls mrp_transport_destroy while the transport is
+ *      idle, ie. with no callbacks or operations being active. This
+ *      is simple and straightforward:
+ *         - mrp_transport_destroy calls req.disconnect
+ *         - mrp_transport_destroy calls req.close
+ *         - mrp_transport_destroy check and sees the transport is idle
+ *           so it frees the transport
+ *
+ *    2)
+ *      Someone calls mrp_tansport_destroy while the transport is
+ *      busy, ie. it has an unfinished callback or operation running.
+ *      This typically happens when an operation or callback function,
+ *      or a user function called from either of those calls
+ *      mrp_transport_destroy as a result of a received message, or a
+ *      (communication) error. In this case destroying the transport
+ *      is less straightforward and needs to get delayed to avoid
+ *      shooting out the transport underneath the active operation or
+ *      callback.
+ *
+ *    To handle the latter case, the generic (ie. top-level) transport
+ *    layer has a member function check_destroy. This function checks
+ *    for pending destroy requests and destroys the transport if it
+ *    is not busy. All transport backends MUST CALL this function and
+ *    CHECK ITS RETURN VALUE, whenever a user callback or a transport
+ *    callback (ie. bottom-up event propagation) function invoked by
+ *    the backend returns.
+ *
+ *    If the transport has been left intact, check_destroy returns
+ *    FALSE and processing can continue normally, taking into account
+ *    that any transport state stored locally in the stack frame of the
+ *    backend function might have changed during the callback. However,
+ *    if check_destroy returns TRUE, it has nuked the transport and the
+ *    backend MUST NOT touch or try to dereference the transport any more
+ *    as its resources have already been released.
+ */
+
+
+/*
+ * convenience macros
+ */
+
+/**
+ * Macro to mark a transport busy while running a block of code.
+ *
+ * The backend needs to make sure the transport is not freed while a
+ * transport request or event callback function is active. Similarly,
+ * the backend needs to check if the transport has been marked for
+ * destruction whenever an event callback returns and trigger the
+ * destruction if it is necessary and possible (ie. the above criterium
+ * of not being active is fullfilled).
+ *
+ * These are the easiest to accomplish using the provided MRP_TRANSPORT_BUSY
+ * macro and the check_destroy callback member provided by mrp_transport_t.
+ *
+ *     1) Use the provided MRP_TRANSPORT_BUSY macro to enclose al blocks of
+ *        code that invoke event callbacks. Do not do a return directly
+ *        from within the enclosed call blocks, rather just set a flag
+ *        within the block, check it after the block and do the return
+ *        from there if necessary.
+ *
+ *     2) Call mrp_transport_t->check_destroy after any call to an event
+ *        callback. check_destroy will check for any pending destroy
+ *        request and perform the actual destruction if it is necessary
+ *        and possible. If the transport has been left intact, check_destroy
+ *        returns FALSE. However, if the transport has been destroyed and
+ *        freed it returns TRUE, in which case the caller must not attempt
+ *        to use or dereference the transport data structures any more.
+ */
+
+
+#ifndef __MRP_TRANSPORT_DISABLE_CODE_CHECK__
+#  define W mrp_log_error
+#  define __TRANSPORT_CHK_BLOCK(...) do {                                  \
+        static int __checked = FALSE, __warned = FALSE;                    \
+                                                                           \
+        if (MRP_UNLIKELY(!__checked)) {                                    \
+            __checked = TRUE;                                              \
+            if (MRP_UNLIKELY(!__warned &&                                  \
+                             strstr(#__VA_ARGS__, "return") != NULL)) {    \
+                W("*********************** WARNING ********************"); \
+                W("* You seem to directly do a return from a block of *"); \
+                W("* code protected by MRP_TRANSPORT_BUSY. Are you    *"); \
+                W("* absolutely sure you know what you are doing and  *"); \
+                W("* that you are also doing it correctly ?           *"); \
+                W("****************************************************"); \
+                W("The suspicious code block is located at: ");            \
+                W("  %s@%s:%d", __FUNCTION__, __FILE__, __LINE__);         \
+                W("and it looks like this:");                              \
+                W("---------------------------------------------");        \
+                W("%s", #__VA_ARGS__);                                     \
+                W("---------------------------------------------");        \
+                W("If you understand what MRP_TRANSPORT_BUSY does and");   \
+                W("how, and you are sure about the corretness of your");   \
+                W("code you can disable this error message by");           \
+                W("#defining __MRP_TRANSPORT_DISABLE_CODE_CHECK__");       \
+                W("when compiling %s.", __FILE__);                         \
+                __warned = TRUE;                                           \
+            }                                                              \
+        }                                                                  \
+    } while (0)
+#else
+#  define __TRANSPORT_CHK_BLOCK(...) do { } while (0)
+#endif
+
+#define MRP_TRANSPORT_BUSY(t, ...) do {                \
+        __TRANSPORT_CHK_BLOCK(__VA_ARGS__);        \
+        (t)->busy++;                                \
+        __VA_ARGS__                                \
+        (t)->busy--;                                \
+    } while (0)
+
+
+
+/** Automatically register a transport on startup. */
+#define MRP_REGISTER_TRANSPORT(_prfx, _typename, _structtype, _resolve,   \
+                               _open, _createfrom, _close, _setopt,       \
+                               _bind, _listen, _accept,                   \
+                               _connect, _disconnect,                     \
+                               _sendmsg, _sendmsgto,                      \
+                               _sendraw, _sendrawto,                      \
+                               _senddata, _senddatato,                    \
+                               _sendcustom, _sendcustomto,                \
+                               _sendnative, _sendnativeto,                \
+                               _sendjson, _sendjsonto)                    \
+    static void _prfx##_register_transport(void)                          \
+         __attribute__((constructor));                                    \
+                                                                          \
+    static void _prfx##_register_transport(void) {                        \
+        static mrp_transport_descr_t descriptor = {                       \
+            .type    = _typename,                                         \
+            .size    = sizeof(_structtype),                               \
+            .resolve = _resolve,                                          \
+            .req     = {                                                  \
+                .open         = _open,                                    \
+                .createfrom   = _createfrom,                              \
+                .bind         = _bind,                                    \
+                .listen       = _listen,                                  \
+                .accept       = _accept,                                  \
+                .close        = _close,                                   \
+                .setopt       = _setopt,                                  \
+                .connect      = _connect,                                 \
+                .disconnect   = _disconnect,                              \
+                .sendmsg      = _sendmsg,                                 \
+                .sendmsgto    = _sendmsgto,                               \
+                .sendraw      = _sendraw,                                 \
+                .sendrawto    = _sendrawto,                               \
+                .senddata     = _senddata,                                \
+                .senddatato   = _senddatato,                              \
+                .sendcustom   = _sendcustom,                              \
+                .sendcustomto = _sendcustomto,                            \
+                .sendnative   = _sendnative,                              \
+                .sendnativeto = _sendnativeto,                            \
+                .sendjson     = _sendjson,                                \
+                .sendjsonto   = _sendjsonto,                              \
+            },                                                            \
+        };                                                                \
+                                                                          \
+        if (!mrp_transport_register(&descriptor))                         \
+            mrp_log_error("Failed to register transport '%s'.",           \
+                          _typename);                                     \
+        else                                                              \
+            mrp_log_info("Registered transport '%s'.", _typename);        \
+    }                                                                     \
+    struct mrp_allow_trailing_semicolon
+
+
+
+/** Register a new transport type. */
+int mrp_transport_register(mrp_transport_descr_t *d);
+
+/** Unregister a transport. */
+void mrp_transport_unregister(mrp_transport_descr_t *d);
+
+/** Create a new transport. */
+mrp_transport_t *mrp_transport_create(mrp_mainloop_t *ml, const char *type,
+                                      mrp_transport_evt_t *evt,
+                                      void *user_data, int flags);
+
+/** Create a new transport from a backend object. */
+mrp_transport_t *mrp_transport_create_from(mrp_mainloop_t *ml, const char *type,
+                                           void *conn, mrp_transport_evt_t *evt,
+                                           void *user_data, int flags,
+                                           int state);
+
+/** Set a (possibly type-specific) transport option. */
+int mrp_transport_setopt(mrp_transport_t *t, const char *opt, const void *val);
+
+/** Resolve an address string to a transport-specific address. */
+socklen_t mrp_transport_resolve(mrp_transport_t *t, const char *str,
+                                mrp_sockaddr_t *addr, socklen_t addrlen,
+                                const char **type);
+
+/** Bind a given transport to a transport-specific address. */
+int mrp_transport_bind(mrp_transport_t *t, mrp_sockaddr_t *addr,
+                       socklen_t addrlen);
+
+/** Listen for incoming connection on the given transport. */
+int  mrp_transport_listen(mrp_transport_t *t, int backlog);
+
+/** Accept and create a new transport connection. */
+mrp_transport_t *mrp_transport_accept(mrp_transport_t *t,
+                                      void *user_data, int flags);
+
+/** Destroy a transport. */
+void mrp_transport_destroy(mrp_transport_t *t);
+
+/** Connect a transport to the given address. */
+int mrp_transport_connect(mrp_transport_t *t, mrp_sockaddr_t  *addr,
+                          socklen_t addrlen);
+
+/** Disconnect a transport. */
+int mrp_transport_disconnect(mrp_transport_t *t);
+
+/** Send a message through the given (connected) transport. */
+int mrp_transport_send(mrp_transport_t *t, mrp_msg_t *msg);
+
+/** Send a message through the given transport to the remote address. */
+int mrp_transport_sendto(mrp_transport_t *t, mrp_msg_t *msg,
+                         mrp_sockaddr_t *addr, socklen_t addrlen);
+
+/** Send raw data through the given (connected) transport. */
+int mrp_transport_sendraw(mrp_transport_t *t, void *data, size_t size);
+
+/** Send raw data through the given transport to the remote address. */
+int mrp_transport_sendrawto(mrp_transport_t *t, void *data, size_t size,
+                            mrp_sockaddr_t *addr, socklen_t addrlen);
+
+/** Send registered data through the given (connected) transport. */
+int mrp_transport_senddata(mrp_transport_t *t, void *data, uint16_t tag);
+
+/** Send registered data through the given transport to the remote address. */
+int mrp_transport_senddatato(mrp_transport_t *t, void *data, uint16_t tag,
+                             mrp_sockaddr_t *addr, socklen_t addrlen);
+
+/** Send custom data through the given (connected) transport. */
+int mrp_transport_sendcustom(mrp_transport_t *t, void *data);
+
+/** Send registered data through the given transport to the remote address. */
+int mrp_transport_sendcustomto(mrp_transport_t *t, void *data,
+                               mrp_sockaddr_t *addr, socklen_t addrlen);
+
+/** Send a native type through the given (connected) transport. */
+int mrp_transport_sendnative(mrp_transport_t *t, void *data, uint32_t type_id);
+
+/** Send a native type through the given transport to the remote address. */
+int mrp_transport_sendnativeto(mrp_transport_t *t, void *data, uint32_t type_id,
+                               mrp_sockaddr_t *addr, socklen_t addrlen);
+
+/** Send a JSON message through the given (connected) transport. */
+int mrp_transport_sendjson(mrp_transport_t *t, mrp_json_t *msg);
+
+/** Send a JSON message through the given transport to the remote address. */
+int mrp_transport_sendjsonto(mrp_transport_t *t, mrp_json_t *msg,
+                             mrp_sockaddr_t *addr, socklen_t addrlen);
+MRP_CDECL_END
+
+#endif /* __MURPHY_TRANSPORT_H__ */
diff --git a/src/common/utils.c b/src/common/utils.c
new file mode 100644 (file)
index 0000000..8fd1f76
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/log.h>
+#include <murphy/common/utils.h>
+
+#define MSG_OK "OK"
+
+static int notify_parent(int fd, const char *fmt, ...)
+{
+    va_list ap;
+    int     len;
+
+    va_start(ap, fmt);
+    len = vdprintf(fd, fmt, ap);
+    va_end(ap);
+
+    return (len > 0);
+}
+
+
+int mrp_daemonize(const char *dir, const char *new_out, const char *new_err)
+{
+    pid_t pid;
+    int   in, out, err;
+    char  msg[1024];
+    int   chnl[2], len;
+
+    /*
+     * create a pipe for communicating back the child status
+     */
+
+    if (pipe(chnl) == -1) {
+        mrp_log_error("Failed to create pipe to get child status (%d: %s).",
+                      errno, strerror(errno));
+        return FALSE;
+    }
+
+
+    /*
+     * fork, change to our new working directory and create a new session
+     */
+
+    switch ((pid = fork())) {
+    case -1: /* failed */
+        mrp_log_error("Could not daemonize, fork failed (%d: %s).",
+                      errno, strerror(errno));
+        return FALSE;
+
+    case 0:  /* child */
+        close(chnl[0]);
+        break;
+
+    default: /* parent */
+        close(chnl[1]);
+
+        /*
+         * wait for and check the status report from the child
+         */
+
+        len = read(chnl[0], msg, sizeof(msg) - 1);
+
+        if (len > 0) {
+            msg[len] = '\0';
+
+            if (!strcmp(msg, MSG_OK)) {
+                mrp_log_info("Successfully daemonized.");
+                exit(0);
+            }
+            else
+                mrp_log_error("Daemonizing failed after fork: %s.", msg);
+        }
+        else
+            mrp_log_error("Daemonizing failed in forked child.");
+
+        return FALSE;
+    }
+
+
+    if (chdir(dir) != 0) {
+        mrp_log_error("Could not daemonize, failed to chdir to %s (%d: %s).",
+                      dir, errno, strerror(errno));
+        return FALSE;
+    }
+
+    if (setsid() < 0) {
+        notify_parent(chnl[1], "Failed to create new session (%d: %s).",
+                      errno, strerror(errno));
+        exit(1);
+    }
+
+
+    /*
+     * fork again and redirect our stdin, stdout, and stderr
+     */
+
+    switch ((pid = fork())) {
+    case -1: /* failed */
+        notify_parent(chnl[1], "Could not daemonize, fork failed (%d: %s).",
+                      errno, strerror(errno));
+        exit(1);
+
+    case 0: /* child */
+        break;
+
+    default: /* parent */
+        close(chnl[1]);
+        exit(0);
+    }
+
+
+    if ((in = open("/dev/null", O_RDONLY)) < 0) {
+        notify_parent(chnl[1], "Failed to open /dev/null (%d: %s).", errno,
+                      strerror(errno));
+        exit(1);
+    }
+
+    if ((out = open(new_out, O_WRONLY)) < 0) {
+        notify_parent(chnl[1], "Failed to open %s (%d: %s).", new_out, errno,
+                      strerror(errno));
+        exit(1);
+    }
+
+    if ((err = open(new_err, O_WRONLY)) < 0) {
+        notify_parent(chnl[1], "Failed to open %s (%d: %s).", new_err, errno,
+                      strerror(errno));
+        exit(1);
+    }
+
+    if (dup2(in, fileno(stdin)) < 0) {
+        notify_parent(chnl[1], "Failed to redirect stdin (%d: %s).", errno,
+                     strerror(errno));
+        exit(1);
+    }
+
+    if (dup2(out, fileno(stdout)) < 0) {
+        notify_parent(chnl[1], "Failed to redirect stdout (%d: %s).", errno,
+                     strerror(errno));
+        exit(1);
+    }
+
+    if (dup2(err, fileno(stderr)) < 0) {
+        notify_parent(chnl[1], "Failed to redirect stderr (%d: %s).", errno,
+                     strerror(errno));
+        exit(1);
+    }
+
+    close(in);
+    close(out);
+    close(err);
+
+    notify_parent(chnl[1], "%s", MSG_OK);
+
+    return TRUE;
+}
+
+
+int mrp_string_comp(const void *key1, const void *key2)
+{
+    return strcmp(key1, key2);
+}
+
+
+uint32_t mrp_string_hash(const void *key)
+{
+    uint32_t    h;
+    const char *p;
+
+    for (h = 0, p = key; *p; p++) {
+        h <<= 1;
+        h  ^= *p;
+    }
+
+    return h;
+}
diff --git a/src/common/utils.h b/src/common/utils.h
new file mode 100644 (file)
index 0000000..fef28a8
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_UTILS_H__
+#define __MURPHY_UTILS_H__
+
+#include <stdint.h>
+
+int mrp_daemonize(const char *dir, const char *new_out, const char *new_err);
+
+int mrp_string_comp(const void *key1, const void *key2);
+uint32_t mrp_string_hash(const void *key);
+
+#endif /* __MURPHY_UTILS_H__ */
diff --git a/src/common/websocket.c b/src/common/websocket.c
new file mode 100644 (file)
index 0000000..25d3dc8
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/websocket.h>
+
+
+void mrp_websock_set_loglevel(mrp_websock_loglevel_t mask)
+{
+    wsl_set_loglevel(mask);
+}
+
+
+mrp_websock_context_t *mrp_websock_create_context(mrp_mainloop_t *ml,
+                                                  mrp_websock_config_t *cfg)
+{
+    return wsl_create_context(ml, cfg);
+}
+
+
+mrp_websock_context_t *mrp_websock_ref_context(mrp_websock_context_t *ctx)
+{
+    return wsl_ref_context(ctx);
+}
+
+
+int mrp_websock_unref_context(mrp_websock_context_t *ctx)
+{
+    return wsl_unref_context(ctx);
+}
+
+
+mrp_websock_t *mrp_websock_connect(mrp_websock_context_t *ctx,
+                                   struct sockaddr *sa, const char *protocol,
+                                   mrp_wsl_ssl_t ssl, void *user_data)
+{
+    return wsl_connect(ctx, sa, protocol, ssl, user_data);
+}
+
+
+mrp_websock_t *mrp_websock_accept_pending(mrp_websock_context_t *ctx,
+                                          void *user_data)
+{
+    return wsl_accept_pending(ctx, user_data);
+}
+
+
+void mrp_websock_reject_pending(mrp_websock_context_t *ctx)
+{
+    wsl_reject_pending(ctx);
+}
+
+
+void *mrp_websock_close(mrp_websock_t *sck)
+{
+    return wsl_close(sck);
+}
+
+
+int mrp_websock_send(mrp_websock_t *sck, void *payload, size_t size)
+{
+    return wsl_send(sck, payload, size);
+}
+
+
+int mrp_websock_server_http_file(mrp_websock_t *sck, const char *path,
+                                 const char *mime)
+{
+    return wsl_serve_http_file(sck, path, mime);
+}
diff --git a/src/common/websocket.h b/src/common/websocket.h
new file mode 100644 (file)
index 0000000..cd380f2
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_WEBSOCKET_H__
+#define __MURPHY_WEBSOCKET_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/websocklib.h>
+
+MRP_CDECL_BEGIN
+
+/*
+ * websocket types (mapped)
+ */
+
+typedef wsl_ctx_cfg_t   mrp_websock_config_t;
+typedef wsl_ctx_t       mrp_websock_context_t;
+typedef wsl_sck_t       mrp_websock_t;
+typedef wsl_callbacks_t mrp_websock_evt_t;
+typedef wsl_proto_t     mrp_websock_proto_t;
+typedef wsl_ssl_t       mrp_wsl_ssl_t;
+
+/*
+ * websocket log levels (mapped)
+ */
+
+typedef enum {
+#define MAP(mrp, wsl) MRP_WEBSOCK_LOG_##mrp = WSL_LOG_##wsl
+    MAP(NONE   , NONE),
+    MAP(ERROR  , ERROR),
+    MAP(WARNING, WARNING),
+    MAP(INFO   , INFO),
+    MAP(DEBUG  , DEBUG),
+    MAP(ALL    , ALL),
+    MAP(PARSER , PARSER),
+    MAP(EXT    , EXT),
+    MAP(CLIENT , CLIENT),
+    MAP(EXTRA  , EXTRA),
+    MAP(VERBOSE, VERBOSE)
+#undef MAP
+} mrp_websock_loglevel_t;
+
+
+
+/*
+ * websocket function prototypes
+ */
+
+/** Set websocket logging level. */
+void mrp_websock_set_loglevel(mrp_websock_loglevel_t mask);
+
+/** Create a websocket context. */
+mrp_websock_context_t *mrp_websock_create_context(mrp_mainloop_t *ml,
+                                                  mrp_websock_config_t *cfg);
+
+/** Add a reference to a websocket context. */
+mrp_websock_context_t *mrp_websock_ref_context(mrp_websock_context_t *ctx);
+
+/** Remove a context reference. */
+int mrp_websock_unref_context(mrp_websock_context_t *ctx);
+
+/** Create and connect a websocket to a given address. */
+mrp_websock_t *mrp_websock_connect(mrp_websock_context_t *ctx,
+                                   struct sockaddr *sa, const char *protocol,
+                                   mrp_wsl_ssl_t ssl, void *user_data);
+
+/** Accept a pending connection of a context. */
+mrp_websock_t *mrp_websock_accept_pending(mrp_websock_context_t *ctx,
+                                          void *user_data);
+
+/** Reject a pending connection of a context. */
+void mrp_websock_reject_pending(mrp_websock_context_t *ctx);
+
+/** Close a websocket. Return the user_data of it's associated context. */
+void *mrp_websock_close(mrp_websock_t *sck);
+
+/** Send data over a connected websocket. */
+int mrp_websock_send(mrp_websock_t *sck, void *payload, size_t size);
+
+/** Serve the given file, with MIME type, over the given websocket. */
+int mrp_websock_server_http_file(mrp_websock_t *sck, const char *path,
+                                 const char *mime);
+
+MRP_CDECL_END
+
+
+#endif /* __MURPHY_WEBSOCKET_H__ */
diff --git a/src/common/websocklib.c b/src/common/websocklib.c
new file mode 100644 (file)
index 0000000..0595c46
--- /dev/null
@@ -0,0 +1,2105 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/refcnt.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/fragbuf.h>
+#include <murphy/common/hashtbl.h>
+
+#include "websocklib.h"
+
+#define LWS_EVENT_OK     0               /* event handler result: ok */
+#define LWS_EVENT_DENY   1               /* event handler result: deny */
+#define LWS_EVENT_ERROR  1               /* event handler result: error */
+#define LWS_EVENT_CLOSE -1               /* event handler result: close */
+
+/* libwebsocket status used to close sockets upon error */
+#ifndef WEBSOCKETS_OLD
+#    define LWS_INTERNAL_ERROR LWS_CLOSE_STATUS_UNEXPECTED_CONDITION
+#else
+#    define LWS_INTERNAL_ERROR LWS_CLOSE_STATUS_PROTOCOL_ERR /* arrghmm... */
+#endif
+
+/* SSL modes */
+#define LWS_NO_SSL         0             /* no SSL at all */
+#define LWS_SSL            1             /* SSL, deny self-signed certs */
+#define LWS_SSL_SELFSIGNED 2             /* SSL, allow self-signed certs */
+
+/*
+ * define shorter aliasen for libwebsocket types
+ */
+
+typedef struct libwebsocket                  lws_t;
+typedef struct libwebsocket_context          lws_ctx_t;
+typedef struct libwebsocket_extension        lws_ext_t;
+typedef struct libwebsocket_protocols        lws_proto_t;
+typedef enum   libwebsocket_callback_reasons lws_event_t;
+#ifdef WEBSOCKETS_CONTEXT_INFO
+    typedef struct lws_context_creation_info lws_cci_t;
+#else /* !WEBSOCKETS_CONTEXT_INFO */
+typedef struct {
+    int port;
+    const char *iface;
+    struct libwebsocket_protocols *protocols;
+    struct libwebsocket_extension *extensions;
+    const char *ssl_cert_filepath;
+    const char *ssl_private_key_filepath;
+    const char *ssl_ca_filepath;
+    const char *ssl_cipher_list;
+    int gid;
+    int uid;
+    unsigned int options;
+    void *user;
+    int ka_time;
+    int ka_probes;
+    int ka_interval;
+} lws_cci_t;
+#endif /* !WEBSOCKETS_CONTEXT_INFO */
+
+static lws_ext_t *lws_get_internal_extensions(void);
+
+/*
+ * a libwebsocket fd we (e)poll
+ *
+ * Unfortunately the mechanism offered by libwebsockets for external
+ * mainloop integration uses event mask diffs when asking the mainloop
+ * to modify what an fd is polled for. This forces us to do double
+ * bookkeeping: we need to to keep track of the current event mask for
+ * all descriptors just to figure out the new mask when libwebsockets
+ * hands us a diff.
+ */
+
+typedef struct {
+    int      fd;                         /* libwebsocket file descriptor */
+    uint32_t events;                     /* monitored (epoll) events */
+} pollfd_t;
+
+
+typedef enum {
+    POLLFD_SET    = 0,
+    POLLFD_CLEAR  = TRUE,
+    POLLFD_CHANGE,
+} pollfd_op_t;
+
+/*
+ * a websocket context
+ */
+
+struct wsl_ctx_s {
+    lws_ctx_t       *ctx;                 /* libwebsocket context */
+    wsl_proto_t     *http;                /* has HTTP as upper layer protocol */
+    wsl_proto_t     *protos;              /* protocols */
+    int              nproto;              /* number of protocols */
+    lws_proto_t     *lws_protos;          /* libwebsocket protocols */
+    mrp_refcnt_t     refcnt;              /* reference count */
+    int              epollfd;             /* epoll descriptor */
+    mrp_io_watch_t  *w;                   /* I/O watch for epollfd */
+    mrp_mainloop_t  *ml;                  /* pumping mainloop */
+    pollfd_t        *fds;                 /* polled descriptors */
+    int              nfd;                 /* number descriptors */
+    void            *user_data;           /* opaque user data */
+    lws_t           *pending;             /* pending connection */
+    void            *pending_user;        /* user_data of pending */
+    wsl_proto_t     *pending_proto;       /* protocol of pending */
+    mrp_list_hook_t  pure_http;           /* pure HTTP sockets */
+};
+
+/*
+ * a websocket instance
+ */
+
+struct wsl_sck_s {
+    wsl_ctx_t       *ctx;                /* associated context */
+    lws_t           *sck;                /* libwebsocket instance */
+    wsl_proto_t     *proto;              /* protocol data */
+    wsl_sendmode_t   send_mode;          /* libwebsocket write mode */
+    mrp_fragbuf_t   *buf;                /* fragment collection buffer */
+    void            *user_data;          /* opaque user data */
+    wsl_sck_t      **sckptr;             /* back pointer from sck to us */
+    int              closing : 1;        /* close in progress */
+    int              pure_http : 1;      /* pure HTTP socket */
+    int              busy;               /* upper-layer callback(s) active */
+    mrp_list_hook_t  hook;               /* to pure HTTP list, if such */
+};
+
+
+/*
+ * mark a socket busy while executing a piece of code
+ */
+
+#define SOCKET_BUSY_REGION(sck, ...) do {       \
+        (sck)->busy++;                          \
+        __VA_ARGS__;                            \
+        (sck)->busy--;                          \
+    } while (0)
+
+
+
+static int http_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event,
+                      void *user, void *in, size_t len);
+static int wsl_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event,
+                     void *user, void *in, size_t len);
+static void destroy_context(wsl_ctx_t *ctx);
+
+static void MRP_EXIT destroy_context_table(void);
+
+
+
+static inline uint32_t map_poll_to_event(int in)
+{
+    uint32_t mask = 0;
+
+    if (in & POLLIN)  mask |= MRP_IO_EVENT_IN;
+    if (in & POLLOUT) mask |= MRP_IO_EVENT_OUT;
+    if (in & POLLHUP) mask |= MRP_IO_EVENT_HUP;
+    if (in & POLLERR) mask |= MRP_IO_EVENT_ERR;
+
+    return mask;
+
+}
+
+
+static inline short map_event_to_poll(uint32_t in)
+{
+    short mask = 0;
+
+    if (in & MRP_IO_EVENT_IN)  mask |= POLLIN;
+    if (in & MRP_IO_EVENT_OUT) mask |= POLLOUT;
+    if (in & MRP_IO_EVENT_HUP) mask |= POLLHUP;
+    if (in & MRP_IO_EVENT_ERR) mask |= POLLERR;
+
+    return mask;
+}
+
+
+static int add_fd(wsl_ctx_t *wsc, int fd, int events)
+{
+    struct epoll_event e;
+
+    if (wsc != NULL) {
+        e.data.u64 = 0;
+        e.data.fd  = fd;
+        e.events   = map_poll_to_event(events);
+
+        if (epoll_ctl(wsc->epollfd, EPOLL_CTL_ADD, fd, &e) == 0) {
+            if (mrp_reallocz(wsc->fds, wsc->nfd, wsc->nfd + 1) != NULL) {
+                wsc->fds[wsc->nfd].fd     = fd;
+                wsc->fds[wsc->nfd].events = e.events;
+                wsc->nfd++;
+
+                return TRUE;
+            }
+            else
+                epoll_ctl(wsc->epollfd, EPOLL_CTL_DEL, fd, &e);
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int del_fd(wsl_ctx_t *wsc, int fd)
+{
+    struct epoll_event e;
+    int                i;
+
+    if (wsc != NULL) {
+        e.data.u64 = 0;
+        e.data.fd  = fd;
+        e.events   = 0;
+        epoll_ctl(wsc->epollfd, EPOLL_CTL_DEL, fd, &e);
+
+        for (i = 0; i < wsc->nfd; i++) {
+            if (wsc->fds[i].fd == fd) {
+                if (i < wsc->nfd - 1)
+                    memmove(wsc->fds + i, wsc->fds + i + 1,
+                            (wsc->nfd - i - 1) * sizeof(*wsc->fds));
+
+                mrp_reallocz(wsc->fds, wsc->nfd, wsc->nfd - 1);
+                wsc->nfd--;
+
+                return TRUE;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static pollfd_t *find_fd(wsl_ctx_t *wsc, int fd)
+{
+    int i;
+
+    if (wsc != NULL) {
+        for (i = 0; i < wsc->nfd; i++)
+            if (wsc->fds[i].fd == fd)
+                return wsc->fds + i;
+    }
+
+    return NULL;
+}
+
+
+static int mod_fd(wsl_ctx_t *wsc, int fd, int events, int op)
+{
+    struct epoll_event  e;
+    pollfd_t           *wfd;
+
+    if (wsc != NULL) {
+        wfd = find_fd(wsc, fd);
+
+        if (wfd != NULL) {
+            e.data.u64 = 0;
+            e.data.fd  = fd;
+
+            switch (op) {
+            case POLLFD_CLEAR:
+                e.events = wfd->events & ~map_poll_to_event(events);
+                break;
+            case POLLFD_SET:
+                e.events = wfd->events |  map_poll_to_event(events);
+                break;
+            case POLLFD_CHANGE:
+                e.events = wfd->events =  map_poll_to_event(events);
+                break;
+            default:
+                return FALSE;
+            }
+
+            if (epoll_ctl(wsc->epollfd, EPOLL_CTL_MOD, fd, &e) == 0)
+                return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void purge_fds(wsl_ctx_t *wsc)
+{
+    if (wsc != NULL) {
+        mrp_free(wsc->fds);
+        wsc->fds = NULL;
+        wsc->nfd = 0;
+    }
+}
+
+
+static void epoll_event(mrp_io_watch_t *w, int fd, mrp_io_event_t mask,
+                        void *user_data)
+{
+    wsl_ctx_t          *wsc = (wsl_ctx_t *)user_data;
+    pollfd_t           *wfd;
+    struct epoll_event *events, *e;
+    int                 nevent, n, i;
+    struct pollfd       pollfd;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(fd);
+
+    if (wsc->nfd <= 0 || !(mask & MRP_IO_EVENT_IN))
+        return;
+
+    nevent = wsc->nfd;
+    events = alloca(nevent * sizeof(*events));
+
+    while ((n = epoll_wait(wsc->epollfd, events, nevent, 0)) > 0) {
+        mrp_debug("got %d epoll events for websocket context %p", n, wsc);
+
+        for (i = 0, e = events; i < n; i++, e++) {
+            wfd = find_fd(wsc, e->data.fd);
+
+            if (wfd != NULL) {
+                pollfd.fd      = wfd->fd;
+                pollfd.events  = map_event_to_poll(wfd->events);
+                pollfd.revents = map_event_to_poll(e->events);
+
+                mrp_debug("delivering events 0x%x to websocket fd %d",
+                          pollfd.revents, pollfd.fd);
+
+                libwebsocket_service_fd(wsc->ctx, &pollfd);
+            }
+        }
+    }
+}
+
+
+/*
+ * context handling
+ */
+
+#ifdef WEBSOCKETS_OLD
+
+/*
+ * Notes:
+ *     In some environments we might be forced to run with really old
+ *     versions of libwebsockets. This causes some amount of pain as
+ *     some of the recent features in libwebsockets are essential for
+ *     building a reasonable abstraction on top of it.
+ *
+ *     Most notably, versions prior to 0291eb3..d764e84 (Oct 19 2012)
+ *     do not support per-context user data. Since we need to associate
+ *     our context with that of libwebsockets we have to build an extra
+ *     mechanism for mapping between the two when user data support is
+ *     not available. We use an extra hash table to store our context
+ *     and use directly the (low 32-bits of the) libwebsocket context
+ *     pointer as the key to store and fetch it.
+ *
+ *     To minimize unreadibility and other code uglification factors,
+ *     most of the code that deals with this version-dependent extra
+ *     mechanism is put into the contamination chamber formed by the
+ *     triplet of {set,get,clear}_context_userdata routines below.
+ *
+ *     Note that since we decided (maybe unecessarily) to do mainloop
+ *     integration also on a per-context basis, with old versions we
+ *     also have a phase error: the first pollfd manipulation events
+ *     for a context being created come __before__ the libwebsockets
+ *     context creation routine returns, IOW before we get a chance to
+ *     administer the reverse mapping between the contexts. This is
+ *     unfortunate because if we cannot find our context for a pollfd
+ *     request/event we just cannot handle it. This in turn would result
+ *     in a practically useless context as its socket would not be
+ *     (e)polled at all.
+ *
+ *     The ugly pending_userdata kludge below is to overcome this problem.
+ *     While creating a context, we register its intended userdata as the
+ *     pending userdata before calling libwebsockets context creation
+ *     routine and clear it afterwards. get_context_userdata knows to
+ *     return the pending_userdata if it cannot do the reverse mapping
+ *     using the libwebsocket context pointer. While this is quite ugly,
+ *     I really don't see any other way. A limitation of this is that we
+ *     cannot have two unfinished contexts being created in parallel, but
+ *     that really should not happen under any circumstances anyway.
+ */
+
+
+static mrp_htbl_t *ctxtbl;
+static void       *pending_userdata;
+
+static int ctx_cmp(const void *ctx1, const void *ctx2)
+{
+    return ctx2 - ctx1;
+}
+
+static uint32_t ctx_hash(const void *ctx)
+{
+    uint64_t h;
+
+    h = (ptrdiff_t)ctx;
+
+    return (uint32_t)(h & 0xffffffff);
+}
+
+static int create_context_table(void)
+{
+    mrp_htbl_config_t hcfg;
+
+    if (ctxtbl == NULL) {
+        mrp_clear(&hcfg);
+
+        hcfg.comp = ctx_cmp;
+        hcfg.hash = ctx_hash;
+        hcfg.free = NULL;
+
+        ctxtbl = mrp_htbl_create(&hcfg);
+
+        return (ctxtbl != NULL);
+    }
+    else
+        return TRUE;
+}
+
+static void destroy_context_table(void)
+{
+    if (ctxtbl != NULL)
+        mrp_htbl_destroy(ctxtbl, FALSE);
+}
+
+
+static int set_pending_userdata(void *ptr)
+{
+    if (pending_userdata == NULL) {
+        pending_userdata = ptr;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static void clear_pending_userdata(void *ptr)
+{
+    if (pending_userdata == ptr)
+        pending_userdata = NULL;
+}
+
+
+static void *get_pending_userdata(void)
+{
+    return pending_userdata;
+}
+
+
+static int set_context_userdata(lws_ctx_t *ws_ctx, wsl_ctx_t *ctx)
+{
+    if (ctxtbl == NULL)
+        create_context_table();
+
+    if (ctxtbl != NULL)
+        return mrp_htbl_insert(ctxtbl, ws_ctx, ctx);
+    else
+        return FALSE;
+}
+
+
+static wsl_ctx_t *get_context_userdata(lws_ctx_t *ws_ctx)
+{
+    wsl_ctx_t *ctx;
+
+    if (ctxtbl != NULL)
+        ctx = (wsl_ctx_t *)mrp_htbl_lookup(ctxtbl, (void *)ws_ctx);
+    else
+        ctx = NULL;
+
+    if (ctx != NULL)
+        return ctx;
+    else
+        return get_pending_userdata();
+}
+
+
+static void clear_context_userdata(lws_ctx_t *ws_ctx)
+{
+    if (ctxtbl != NULL)
+        mrp_htbl_remove(ctxtbl, ws_ctx, FALSE);
+}
+
+
+static lws_ctx_t *lws_create_ctx(lws_cci_t *cci)
+{
+    lws_ctx_t *ws_ctx;
+
+    set_pending_userdata(cci->user);
+
+    ws_ctx = libwebsocket_create_context(cci->port, cci->iface,
+                                         cci->protocols, cci->extensions,
+                                         cci->ssl_cert_filepath,
+                                         cci->ssl_private_key_filepath,
+                                         /* no ssl_ca */
+                                         cci->gid, cci->uid, cci->options
+                                         /*no user_data*/);
+    if (ws_ctx != NULL)
+        set_context_userdata(ws_ctx, cci->user);
+
+    clear_pending_userdata(cci->user);
+
+    return ws_ctx;
+}
+
+#else /* !WEBSOCKETS_OLD */
+
+static void destroy_context_table(void)
+{
+    return;
+}
+
+
+static wsl_ctx_t *get_context_userdata(lws_ctx_t *ws_ctx)
+{
+    return libwebsocket_context_user(ws_ctx);
+}
+
+
+static void clear_context_userdata(lws_ctx_t *ws_ctx)
+{
+    MRP_UNUSED(ws_ctx);
+}
+
+
+static lws_ctx_t *lws_create_ctx(lws_cci_t *cci)
+{
+#ifdef WEBSOCKETS_CONTEXT_INFO
+    return libwebsocket_create_context(cci);
+#else
+    return libwebsocket_create_context(cci->port, cci->iface,
+                                       cci->protocols, cci->extensions,
+                                       cci->ssl_cert_filepath,
+                                       cci->ssl_private_key_filepath,
+                                       cci->ssl_ca_filepath,
+                                       cci->gid, cci->uid, cci->options,
+                                       cci->user);
+#endif
+}
+
+#endif /* !WEBSOCKETS_OLD */
+
+static lws_ext_t *lws_get_internal_extensions(void)
+{
+#ifdef WEBSOCKETS_QUERY_EXTENSIONS
+    return libwebsocket_get_internal_extensions();
+#else
+    return libwebsocket_internal_extensions;
+#endif
+}
+
+static int find_device(struct sockaddr *sa, char *buf, size_t size)
+{
+    struct sockaddr *ia;
+    struct ifreq     ifreq[64];
+    struct ifconf    ifconf;
+    int              status, sck, n, i;
+
+    /*
+     * XXX FIXME: we only handle primary addresses at the moment...
+     */
+
+    if (size < IFNAMSIZ) {
+        errno = ENOBUFS;
+        return -1;
+    }
+
+    if (sa->sa_family != AF_INET) {      /* libwebsockets can't handle IPv6 */
+        errno = EAFNOSUPPORT;
+        return -1;
+    }
+
+    if (((struct sockaddr_in *)sa)->sin_addr.s_addr == 0x0) {
+        *buf = '\0';
+        return 0;
+    }
+
+    sck = socket(AF_INET, SOCK_DGRAM, 0);
+
+    if (sck < 0)
+        return -1;
+
+    ifconf.ifc_len = sizeof(ifreq);
+    ifconf.ifc_buf = (char *)&ifreq[0];
+
+    status = ioctl(sck, SIOCGIFCONF, &ifconf);
+    close(sck);
+
+    if (status < 0)
+        return -1;
+
+    n = ifconf.ifc_len / sizeof(ifreq[0]);
+
+    for (i = 0; i < n; i++) {
+        ia = &ifreq[i].ifr_addr;
+
+        if (ia->sa_family == sa->sa_family) {
+            if (((struct sockaddr_in *)sa)->sin_addr.s_addr ==
+                ((struct sockaddr_in *)ia)->sin_addr.s_addr) {
+                strncpy(buf, ifreq[i].ifr_name, IFNAMSIZ - 1);
+                buf[IFNAMSIZ - 1] = '\0';
+                return 0;
+            }
+        }
+    }
+
+    errno = EADDRNOTAVAIL;
+    return -1;
+}
+
+
+wsl_ctx_t *wsl_create_context(mrp_mainloop_t *ml, wsl_ctx_cfg_t *cfg)
+{
+    lws_ext_t      *builtin = lws_get_internal_extensions();
+    lws_cci_t       cci;
+    wsl_ctx_t      *ctx;
+    wsl_proto_t    *up, *http;
+    lws_proto_t    *lws_protos, *lp;
+    int             lws_nproto;
+    mrp_io_event_t  events;
+    char            ifname[IFNAMSIZ + 1];
+    int             i;
+
+    ctx = NULL;
+    mrp_clear(&cci);
+
+    if (cfg->addr != NULL) {
+        if (find_device(cfg->addr, ifname, sizeof(ifname)) < 0)
+            return NULL;
+        else {
+            mrp_debug("address mapped to device '%s'",
+                      *ifname ? ifname : "<any>");
+
+            cci.iface = ifname;
+
+            switch (cfg->addr->sa_family) {
+            case AF_INET:
+                cci.port = ntohs(((struct sockaddr_in *)cfg->addr)->sin_port);
+                break;
+            case AF_INET6:
+                cci.port = ntohs(((struct sockaddr_in6 *)cfg->addr)->sin6_port);
+                break;
+            default:
+                goto fail;
+            }
+        }
+    }
+
+    ctx = mrp_allocz(sizeof(*ctx));
+
+    if (ctx == NULL)
+        goto fail;
+
+    mrp_refcnt_init(&ctx->refcnt);
+    mrp_list_init(&ctx->pure_http);
+
+    ctx->protos = cfg->protos;
+    ctx->nproto = cfg->nproto;
+
+    if (!strcmp(cfg->protos[0].name, "http") ||
+        !strcmp(cfg->protos[0].name, "http-only"))
+        http = &cfg->protos[0];
+    else
+        http = NULL;
+
+    lws_nproto = (http ? cfg->nproto : cfg->nproto + 1) + 1;
+    lws_protos = mrp_allocz_array(lws_proto_t, lws_nproto);
+
+    if (lws_protos == NULL)
+        goto fail;
+
+    lws_protos[0].name     = "http";
+    lws_protos[0].callback = http_event;
+    if (!http)
+        lws_protos[0].per_session_data_size = sizeof(void *);
+    else
+        lws_protos[0].per_session_data_size = sizeof(void *);
+
+    lp = lws_protos + 1;
+    up = cfg->protos + (http ? 1 : 0);
+
+    for (i = (http ? 1 : 0); i < cfg->nproto; i++) {
+        lp->name                  = up->name;
+        lp->callback              = wsl_event;
+        lp->per_session_data_size = sizeof(void *);
+
+        lp++;
+        up++;
+    }
+
+    ctx->lws_protos = lws_protos;
+    ctx->http       = http;
+
+    ctx->epollfd = epoll_create1(EPOLL_CLOEXEC);
+
+    if (ctx->epollfd < 0)
+        goto fail;
+
+    events  = MRP_IO_EVENT_IN;
+    ctx->ml = ml;
+    ctx->w  = mrp_add_io_watch(ml, ctx->epollfd, events, epoll_event, ctx);
+
+    if (ctx->w == NULL)
+        goto fail;
+
+    cci.protocols  = lws_protos;
+    cci.extensions = builtin;
+    cci.user       = ctx;
+    cci.gid        = cfg->gid;
+    cci.uid        = cfg->uid;
+
+    cci.ssl_cert_filepath        = cfg->ssl_cert;
+    cci.ssl_private_key_filepath = cfg->ssl_pkey;
+    cci.ssl_ca_filepath          = cfg->ssl_ca;
+    cci.ssl_cipher_list          = cfg->ssl_ciphers;
+
+    cci.options     = 0;
+    cci.ka_time     = cfg->timeout;
+    cci.ka_probes   = cfg->nprobe;
+    cci.ka_interval = cfg->interval;
+
+    ctx->ctx = lws_create_ctx(&cci);
+
+    if (ctx->ctx != NULL) {
+        ctx->user_data = cfg->user_data;
+
+        return ctx;
+    }
+
+ fail:
+    if (ctx != NULL) {
+        if (ctx->epollfd >= 0) {
+            mrp_del_io_watch(ctx->w);
+            close(ctx->epollfd);
+        }
+
+        mrp_free(ctx);
+    }
+
+    return NULL;
+}
+
+
+wsl_ctx_t *wsl_ref_context(wsl_ctx_t *ctx)
+{
+    return mrp_ref_obj(ctx, refcnt);
+}
+
+
+int wsl_unref_context(wsl_ctx_t *ctx)
+{
+    if (mrp_unref_obj(ctx, refcnt)) {
+        mrp_debug("refcount of context %p dropped to zero", ctx);
+        destroy_context(ctx);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static void destroy_context(wsl_ctx_t *ctx)
+{
+    if (ctx != NULL) {
+        mrp_debug("destroying context %p", ctx);
+
+        mrp_del_io_watch(ctx->w);
+        ctx->w = NULL;
+
+        close(ctx->epollfd);
+        ctx->epollfd = -1;
+
+        purge_fds(ctx);
+
+        if (ctx->ctx != NULL) {
+            clear_context_userdata(ctx->ctx);
+            libwebsocket_context_destroy(ctx->ctx);
+        }
+
+        mrp_free(ctx->lws_protos);
+        mrp_free(ctx);
+    }
+}
+
+
+static wsl_proto_t *find_context_protocol(wsl_ctx_t *ctx, const char *protocol)
+{
+    wsl_proto_t *up;
+    int          i;
+
+    if (protocol != NULL) {
+        for (i = 0, up = ctx->protos; i < ctx->nproto; i++, up++)
+            if (!strcmp(up->name, protocol))
+                return up;
+    }
+
+    return NULL;
+}
+
+
+static wsl_sck_t *find_pure_http(wsl_ctx_t *ctx, lws_t *ws)
+{
+    mrp_list_hook_t *p, *n;
+    wsl_sck_t       *sck;
+
+    /*
+     * Notes:
+     *     We expect an extremely low number of concurrent pure
+     *     HTTP connections so we do asimple linear search here.
+     *     We can change this if this turns out to be a false
+     *     assumption.
+     */
+
+    mrp_list_foreach(&ctx->pure_http, p, n) {
+        sck = mrp_list_entry(p, typeof(*sck), hook);
+
+        if (sck->sck == ws)
+            return sck;
+    }
+
+    return NULL;
+}
+
+
+wsl_sck_t *wsl_connect(wsl_ctx_t *ctx, struct sockaddr *sa,
+                       const char *protocol, wsl_ssl_t ssl, void *user_data)
+{
+    wsl_sck_t   *sck, **ptr;
+    wsl_proto_t *up;
+    int          port;
+    void        *aptr;
+    char         abuf[256];
+    const char  *astr;
+
+    switch (sa->sa_family) {
+    case AF_INET:
+        aptr = &((struct sockaddr_in *)sa)->sin_addr;
+        port = ntohs(((struct sockaddr_in *)sa)->sin_port);
+        break;
+    case AF_INET6:
+        aptr = &((struct sockaddr_in6 *)sa)->sin6_addr;
+        port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
+        break;
+    default:
+        errno = EINVAL;
+        return NULL;
+    }
+
+    astr = inet_ntop(sa->sa_family, aptr, abuf, sizeof(abuf));
+
+    if (astr == NULL)
+        return NULL;
+
+    up = find_context_protocol(ctx, protocol);
+
+    if (up == NULL) {
+        errno = ENOPROTOOPT;
+        return NULL;
+    }
+
+    sck = mrp_allocz(sizeof(*sck));
+    ptr = mrp_allocz(sizeof(*ptr));
+
+    if (sck != NULL && ptr != NULL) {
+        /*
+         * Now we need to create and connect a new libwebsocket instance
+         * within the given context. We also need to set up a one-to-one
+         * mapping between the underlying libwebsocket and our wsl_sck_t
+         * so that we can handle both top-down (sending) and bottom-up
+         * (receiving) event propagation in the stack.
+         *
+         * We use the user data associated with the libwebsocket instance
+         * to store a back pointer to us. Whenever the socket instance
+         * is deleted locally (as opposed to our peer closing the session)
+         * we need to prevent the propagation of any potentially pending
+         * events to our deleted wsl_sck_t (which might have been freed).
+         * This we do by clearing the back pointer from the instance to us.
+         *
+         * However, since libwebsockets does not provide an API for this,
+         * as a trick we use an indirect back pointer and store a pointer
+         * to the actual back pointer also in wsl_sck_t here. This way we
+         * can always clear the back pointer when we need to.
+         *
+         * Also note, that memory management for the associated user data
+         * is asymmetric in many sense. For client connections, we allocate
+         * the data buffer and pass it on to libwebsockets. For incoming
+         * connections the user data buffer is allocated by libwebsockets
+         * and we only get a chance to fill it in the event handler for
+         * connection establishment. However, for both incoming and outgoing
+         * connections libwebsockets will free the buffer on behalf of us.
+         *
+         * The exact same notes apply to wsl_accept_pending below...
+         */
+
+        mrp_list_init(&sck->hook);
+        sck->ctx   = wsl_ref_context(ctx);
+        sck->proto = up;
+        sck->buf   = mrp_fragbuf_create(/*up->framed*/TRUE, 0);
+
+        if (sck->buf != NULL) {
+            sck->user_data = user_data;
+
+            if (strncmp(protocol, "http", 4)) { /* Think harder, Homer ! */
+                *ptr           = sck;
+                sck->sckptr    = ptr;
+            }
+            else
+                mrp_list_append(&ctx->pure_http, &sck->hook);
+
+            sck->sck = libwebsocket_client_connect_extended(ctx->ctx,
+                                                            astr, port,
+                                                            ssl,
+                                                            "/", astr, astr,
+                                                            protocol, -1,
+                                                            ptr);
+
+            if (sck->sck != NULL)
+                return sck;
+
+            mrp_fragbuf_destroy(sck->buf);
+            mrp_list_delete(&sck->hook);
+        }
+
+        wsl_unref_context(ctx);
+        mrp_free(ptr);
+        mrp_free(sck);
+    }
+
+    return NULL;
+}
+
+
+wsl_sck_t *wsl_accept_pending(wsl_ctx_t *ctx, void *user_data)
+{
+    wsl_sck_t  *sck, **ptr;
+
+    if (ctx->pending == NULL || ctx->pending_proto == NULL)
+        return NULL;
+
+    mrp_debug("accepting pending websocket connection %p/%p", ctx->pending,
+              ctx->pending_user);
+
+    sck = mrp_allocz(sizeof(*sck));
+
+    if (sck != NULL) {
+        mrp_list_init(&sck->hook);
+
+        /*
+         * Notes:
+         *     The same notes apply here for context creation as for
+         *     wsl_connect above...
+         */
+        sck->ctx = wsl_ref_context(ctx);
+        sck->buf = mrp_fragbuf_create(/*ctx->pending_proto->framed*/TRUE, 0);
+
+        if (sck->buf != NULL) {
+            sck->proto     = ctx->pending_proto;
+            sck->user_data = user_data;
+            sck->sck       = ctx->pending;
+            ptr            = (wsl_sck_t **)ctx->pending_user;
+            sck->sckptr    = ptr;
+
+            mrp_debug("pending connection was a %s websocket",
+                      ptr != NULL ? "real" : "HTTP");
+
+            if (ptr != NULL)             /* genuine websocket */
+                *ptr = sck;
+            else                         /* pure http socket */
+                mrp_list_append(&ctx->pure_http, &sck->hook);
+
+            /* let the event handler know we accepted the client */
+            ctx->pending       = NULL;
+            /* for pure http communicate sck back in pending_user */
+            ctx->pending_user  = (ptr == NULL ? sck : NULL);
+            ctx->pending_proto = NULL;
+
+            return sck;
+        }
+
+        wsl_unref_context(ctx);
+        mrp_free(sck);
+    }
+
+    return NULL;
+}
+
+
+void wsl_reject_pending(wsl_ctx_t *ctx)
+{
+    mrp_debug("reject pending websocket (%s) connection %p/%p",
+              ctx->pending_proto->name, ctx->pending, ctx->pending_user);
+
+    /*
+     * Nothing to do here really... just don't clear ctx->pending so the
+     * event handler will know to reject once it regains control.
+     */
+}
+
+
+#ifdef WEBSOCKETS_CLOSE_SESSION
+
+/*
+ * WTF ? The prototype for this has been moved from libwebsockets.h to
+ * the uninstalled private-libwebsockets.h. If this is really going to
+ * be made private eventually, how is one supposed to close a websocket
+ * without closing its context and a side effect all other websockets
+ * associated with the same context ?
+ */
+extern void libwebsocket_close_and_free_session(struct libwebsocket_context *,
+                                                struct libwebsocket *,
+                                                enum lws_close_status);
+
+
+void *wsl_close(wsl_sck_t *sck)
+{
+    wsl_ctx_t *ctx;
+    void      *user_data;
+    int        status;
+
+    user_data = NULL;
+
+    if (sck != NULL) {
+        if (sck->sck != NULL && sck->busy <= 0) {
+            mrp_debug("closing websocket %p/%p", sck, sck->sck);
+
+            status = LWS_CLOSE_STATUS_NORMAL;
+            ctx    = sck->ctx;
+
+            sck->closing = TRUE;
+            libwebsocket_close_and_free_session(ctx->ctx, sck->sck, status);
+            sck->sck     = NULL;
+
+            if (sck->sckptr != NULL)     /* genuine websocket */
+                *sck->sckptr = NULL;
+            else                         /* pure http socket */
+                mrp_list_delete(&sck->hook);
+
+            if (ctx != NULL) {
+                user_data = ctx->user_data;
+                wsl_unref_context(ctx);
+                sck->ctx = NULL;
+            }
+
+            mrp_fragbuf_destroy(sck->buf);
+            sck->buf = NULL;
+
+            mrp_debug("freeing websocket %p", sck);
+            mrp_free(sck);
+        }
+        else {
+            mrp_debug("marking websocket %p/%p for closing", sck, sck->sck);
+            sck->closing = TRUE;
+        }
+    }
+
+    return user_data;
+}
+
+
+#else /* !WEBSOCKET_CLOSE_SESSION */
+
+void *wsl_close(wsl_sck_t *sck)
+{
+    lws_ctx_t *ws_ctx;
+    lws_t     *ws;
+    void      *user_data;
+
+    /*
+     * With recent libwebsockets libwebsocket_close_and_free_session has
+     * been fully turned into a private library symbol. According to the
+     * docs the official way to trigger closing a websocket from the
+     * 'upper layers' (ie. outside of libwebsocket event callbacks) is to
+     *   1) administer the fact that the websocket should be closed
+     *   2) enable pollouts for the websocket (callback_on_writable)
+     *   3) hope that libwebsockets will not decide to omit delivering a
+     *      LWS_CALLBACK_{CLIENT,SERVER}_WRITEABLE event, and
+     *   4) in the event callback check if the websocket is marked for
+     *      deletion, and if it is reutrn -1 to indicate libwebsockets that
+     *      it should close the socket
+     * Hmm... I guess simple elegance was not one of the design principles.
+     *
+     * Anyway, here's our second attempt to implement this indirect socket
+     * closing scheme without too much memory corruption and leaks... Argh.
+     *
+     * Notes: XXX TODO
+     *     Currently we only check and handle pending deletion when
+     *     dealing with *_WRITEABLE events. Probably we should also do
+     *     it for a few other events as well, for instance for *_RECEIVE
+     *     and *_CALLBACK_HTTP).
+     */
+
+    user_data = NULL;
+
+    if (sck != NULL) {
+        if (sck->sck != NULL && sck->busy <= 0) {
+            mrp_debug("closing %s websocket %p/%p",
+                      sck->sckptr ? "real" : "HTTP", sck->sck, sck);
+
+            ws       = sck->sck;
+            sck->sck = NULL;
+            sck->closing = TRUE;
+
+            /* clear the back pointer to us */
+            if (sck->sckptr != NULL)
+                *sck->sckptr = NULL;
+            else
+                mrp_list_delete(&sck->hook);
+
+            if (sck->ctx != NULL) {
+                ws_ctx    = sck->ctx->ctx;
+                user_data = sck->ctx->user_data;
+                wsl_unref_context(sck->ctx);
+                sck->ctx = NULL;
+            }
+            else
+                ws_ctx = NULL;
+
+            mrp_fragbuf_destroy(sck->buf);
+            sck->buf = NULL;
+
+            mrp_debug("freeing websocket %p", sck);
+            mrp_free(sck);
+
+            if (ws_ctx != NULL)
+                libwebsocket_callback_on_writable(ws_ctx, ws);
+        }
+        else
+            sck->closing = TRUE;
+    }
+
+    return user_data;
+}
+
+
+#endif /* !WEBSOCKET_CLOSE_SESSION */
+
+
+static int check_closed(wsl_sck_t *sck)
+{
+    if (sck != NULL) {
+        if (sck->closing && sck->busy <= 0) {
+            wsl_close(sck);
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+int wsl_set_sendmode(wsl_sck_t *sck, wsl_sendmode_t mode)
+{
+    const char *name;
+
+    switch (mode) {
+    case WSL_SEND_TEXT:   name = "text";   break;
+    case WSL_SEND_BINARY: name = "binary"; break;
+    default:                               return FALSE;
+    }
+
+    mrp_debug("websocket %p/%p mode changed to %s", sck, sck->sck, name);
+    sck->send_mode = mode;
+
+    return TRUE;
+}
+
+
+int wsl_send(wsl_sck_t *sck, void *payload, size_t size)
+{
+    unsigned char *buf;
+    size_t         pre, post, total;
+    uint32_t      *len;
+
+    if (sck != NULL && sck->sck != NULL) {
+        if (sck->proto->framed) {
+            pre  = LWS_SEND_BUFFER_PRE_PADDING;
+            post = LWS_SEND_BUFFER_POST_PADDING;
+            buf  = alloca(pre + sizeof(*len) + size + post);
+            len  = (uint32_t *)(buf + pre);
+            *len = htobe32(size);
+
+            memcpy(buf + pre + sizeof(*len), payload, size);
+            total = sizeof(*len) + size;
+        }
+        else {
+            pre  = LWS_SEND_BUFFER_PRE_PADDING;
+            post = LWS_SEND_BUFFER_POST_PADDING;
+            buf  = alloca(pre + size + post);
+
+            memcpy(buf + pre, payload, size);
+            total = size;
+        }
+
+#if (WSL_SEND_TEXT != 0)
+        if (!sck->send_mode)
+            sck->send_mode = WSL_SEND_TEXT;
+#endif
+
+        if (libwebsocket_write(sck->sck, buf + pre, total, sck->send_mode) >= 0)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+int wsl_serve_http_file(wsl_sck_t *sck, const char *path, const char *type)
+{
+    mrp_debug("serving file '%s' (%s) over websocket %p", path, type, sck->sck);
+
+#ifndef WEBSOCKETS_OLD
+#  ifdef WEBSOCKETS_SERVE_FILE_EXTRAARG
+    if (libwebsockets_serve_http_file(sck->ctx->ctx, sck->sck, path,
+                                      type, NULL) == 0)
+        return TRUE;
+    else
+        return FALSE;
+#  else
+    if (libwebsockets_serve_http_file(sck->ctx->ctx, sck->sck, path, type) == 0)
+        return TRUE;
+    else
+        return FALSE;
+#  endif
+#else
+    if (libwebsockets_serve_http_file(sck->sck, path, type) == 0)
+        return TRUE;
+    else
+        return FALSE;
+#endif
+}
+
+
+#ifdef LWS_OPENSSL_SUPPORT
+
+static void load_extra_certs(wsl_ctx_t *ctx, void *user, lws_event_t event)
+{
+    int is_server;
+
+    if (ctx != NULL && ctx->load_certs != NULL) {
+        if (event == LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS)
+            is_server = TRUE;
+        else
+            is_server = FALSE;
+
+        ctx->load_certs(ctx, (SSL_CTX *)user, is_server);
+    }
+}
+
+
+static int verify_client_cert(void *user, void *in, size_t len)
+{
+    X509_STORE_CTX *x509_ctx;
+    SSL            *ssl;
+    int             pre_ok;
+
+    if (verify_client_cert_cb != NULL) {
+        x509_ctx = (X509_STORE_CTX *)user;
+        ssl      = (SSL *)in;
+        pre_ok   = (int)len;
+
+        if (verify_client_cert_cb(x509_ctx, ssl, pre_ok))
+            return TRUE;
+        else
+            return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+#else /* !LWS_OPENSSL_SUPPORT */
+
+static void load_extra_certs(wsl_ctx_t *ctx, void *user, lws_event_t event)
+{
+    MRP_UNUSED(ctx);
+    MRP_UNUSED(user);
+    MRP_UNUSED(event);
+
+    return;
+}
+
+
+static int verify_client_cert(void *user, void *in, size_t len)
+{
+    MRP_UNUSED(user);
+    MRP_UNUSED(in);
+    MRP_UNUSED(len);
+
+    return TRUE;
+}
+
+#endif
+
+
+
+static int http_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event,
+                      void *user, void *in, size_t len)
+{
+    wsl_ctx_t   *ctx = get_context_userdata(ws_ctx);
+    wsl_sck_t   *sck;
+    wsl_proto_t *up;
+    const char  *ext, *uri;
+    int          fd, mask, status, accepted;
+
+    switch (event) {
+    case LWS_CALLBACK_ESTABLISHED:
+        mrp_debug("client-handshake completed on websocket %p/%p", ws, user);
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLOSED:
+        mrp_debug("websocket %p/%p closed", ws, user);
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_ESTABLISHED:
+        mrp_debug("server-handshake completed on websocket %p/%p", ws, user);
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+        mrp_debug("client connection failed");
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_RECEIVE:
+        mrp_debug("received HTTP data from client");
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_RECEIVE:
+        mrp_debug("recived HTTP data from server");
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_RECEIVE_PONG:
+        mrp_debug("client received pong");
+        return LWS_EVENT_OK;
+
+        /*
+         * mainloop integration
+         */
+#ifdef WEBSOCKETS_CHANGE_MODE_POLL_FD
+
+    case LWS_CALLBACK_ADD_POLL_FD: {
+        struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in;
+        fd   = pa->fd;
+        mask = pa->events;
+
+        mrp_debug("start polling fd %d for events 0x%x", fd, mask);
+        if (add_fd(ctx, fd, mask))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+    }
+
+    case LWS_CALLBACK_DEL_POLL_FD: {
+        struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in;
+        fd = pa->fd;
+
+        mrp_debug("stop polling fd %d", fd);
+        if (del_fd(ctx, fd))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+    }
+
+    case LWS_CALLBACK_CHANGE_MODE_POLL_FD: {
+        struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in;
+        fd   = pa->fd;
+        mask = pa->events;
+
+        mrp_debug("setting poll events to 0x%x for fd %d", mask, fd);
+        if (mod_fd(ctx, fd, mask, FALSE))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+    }
+
+#else /* WEBSOCKETS_CHANGE_MODE_POLL_FD */
+
+    case LWS_CALLBACK_ADD_POLL_FD:
+#ifdef WEBSOCKETS_CONTEXT_INFO           /* just brilliant... */
+        fd   = (ptrdiff_t)in;
+#else
+        fd   = (ptrdiff_t)user;
+#endif
+        mask = (int)len;
+        mrp_debug("start polling fd %d for events 0x%x", fd, mask);
+        if (add_fd(ctx, fd, mask))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+
+    case LWS_CALLBACK_DEL_POLL_FD:
+#ifdef WEBSOCKETS_CONTEXT_INFO           /* just brilliant... */
+        fd = (ptrdiff_t)in;
+#else
+        fd = (ptrdiff_t)user;
+#endif
+        mrp_debug("stop polling fd %d", fd);
+        if (del_fd(ctx, fd))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+
+    case LWS_CALLBACK_SET_MODE_POLL_FD:
+#ifdef WEBSOCKETS_CONTEXT_INFO           /* just brilliant... */
+        fd   = (ptrdiff_t)in;
+#else
+        fd   = (ptrdiff_t)user;
+#endif
+        mask = (int)len;
+        mrp_debug("enable poll events 0x%x for fd %d", mask, fd);
+        if (mod_fd(ctx, fd, mask, FALSE))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+
+    case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
+#ifdef WEBSOCKETS_CONTEXT_INFO           /* just brilliant... */
+        fd   = (ptrdiff_t)in;
+#else
+        fd   = (ptrdiff_t)user;
+#endif
+        mask = (int)len;
+        mrp_debug("disable poll events 0x%x for fd %d", mask, fd);
+        if (mod_fd(ctx, fd, mask, TRUE))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_ERROR;
+
+#endif /* WEBSOCKETS_CHANGE_MODE_POLL_FD */
+
+    case LWS_CALLBACK_SERVER_WRITEABLE:
+#ifndef WEBSOCKETS_CLOSE_SESSION
+        sck = find_pure_http(ctx, ws);
+
+        if (sck == NULL) {
+            mrp_debug("asking to close unassociated websocket %p", ws);
+            return LWS_EVENT_CLOSE;
+        }
+#endif
+        mrp_debug("socket server side writeable again");
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_WRITEABLE:
+#ifndef WEBSOCKETS_CLOSE_SESSION
+        sck = find_pure_http(ctx, ws);
+
+        if (sck == NULL) {
+            mrp_debug("asking to close unassociated websocket %p", ws);
+            return LWS_EVENT_CLOSE;
+        }
+#endif
+        mrp_debug("socket client side writeable again");
+        return LWS_EVENT_OK;
+
+        /*
+         * clients wanting to stay pure HTTP clients
+         *
+         * Notes:
+         *     Clients that stay pure HTTP clients (ie. do not negotiate a
+         *     websocket connection) never get an LWS_CALLBACK_ESTABLISHED
+         *     event emitted for. This is a bit unfortunate, since that is
+         *     the event we map to the incoming connection event of our
+         *     transport layer.
+         *
+         *     However, we'd really like to keep pure HTTP and websocket
+         *     connections as much equal as possible. First and foremost
+         *     this means that we'd like to associate our own websocklib
+         *     wsl_sck_t socket context to lws_t and vice versa. Also
+         *     similarly to websocket connections we want to give the upper
+         *     layer a chance to accept or reject the connection.
+         *
+         *     Since there is no ESTABLISHED event for pure HTTP clients,
+         *     we have to emulate one such here. We need to check if test
+         *     ws belongs to a known connection by checking if it has an
+         *     associated wsl_sck_t. If not we need to call the upper layer
+         *     to let it accept or reject the connection. If it has already
+         *     we need to call the reception handler of the upper layer.
+         *
+         *     However, unfortunately libwebsockets never allocates user
+         *     data for the HTTP websockets even we specify a non-zero size
+         *     for protocol 0. Hence, we cannot use our normal mechanism of
+         *     associating the upper layer wsl_sck_t context using the ws
+         *     user data. Instead we need to separately keep track of HTTP
+         *     websockets and look up the associated wsl_sck_t using this
+         *     secondary bookkeeping.
+         */
+
+
+#ifdef WEBSOCKETS_FILTER_HTTP_CONNECTION
+    case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
+        return 0;
+#endif
+
+    case LWS_CALLBACK_HTTP:
+        uri = (const char *)in;
+
+        if (ctx->http == NULL) {
+            mrp_debug("denying HTTP request of '%s' for httpless context", uri);
+            return LWS_EVENT_DENY;
+        }
+
+        sck = find_pure_http(ctx, ws);
+
+        if (sck != NULL) {               /* known socket, deliver event */
+        deliver_event:
+            up = sck->proto;
+
+            if (up != NULL) {
+                SOCKET_BUSY_REGION(sck, {
+                        up->cbs.recv(sck, in, strlen(uri), sck->user_data,
+                                     up->proto_data);
+                        up->cbs.check(sck, sck->user_data, up->proto_data);
+                    });
+
+                sck = find_pure_http(ctx, ws);
+
+                if (check_closed(sck))
+                    return 0;
+            }
+
+            status = LWS_EVENT_OK;
+        }
+        else {                           /* unknown socket, needs to accept */
+            if (ctx->pending != NULL) {
+                mrp_log_error("Multiple pending connections, rejecting.");
+                return LWS_EVENT_DENY;
+            }
+
+            up = ctx->http;
+
+            ctx->pending       = ws;
+            ctx->pending_user  = NULL;
+            ctx->pending_proto = up;
+
+            wsl_ref_context(ctx);
+            up->cbs.connection(ctx, "XXX TODO dig out peer address", up->name,
+                               ctx->user_data, up->proto_data);
+            sck = ctx->pending_user;
+            ctx->pending_user = NULL;
+
+            /* XXX TODO
+             * check if sockets gets properly closed and freed if
+             * cb->connection calls close on the 'listening' websocket in
+             * the transport layer...
+             */
+
+            accepted = (ctx->pending == NULL);
+            wsl_unref_context(ctx);
+
+            if (accepted)
+                goto deliver_event;
+            else
+                status = LWS_EVENT_DENY;
+        }
+
+        return status;
+
+#ifndef WEBSOCKETS_OLD
+    case LWS_CALLBACK_HTTP_FILE_COMPLETION:
+        uri = (const char *)in;
+        if (uri != NULL)
+            mrp_debug("serving '%s' over HTTP completed", uri);
+        else
+            mrp_debug("serving HTTP content completed");
+
+        sck = find_pure_http(ctx, ws);
+
+        if (sck != NULL) {               /* known socket, deliver event */
+            up = sck->proto;
+
+            if (up != NULL) {
+                SOCKET_BUSY_REGION(sck, {
+                        up->cbs.http_done(sck, in, sck->user_data,
+                                          up->proto_data);
+                        up->cbs.check(sck, sck->user_data, up->proto_data);
+                    });
+
+                sck = find_pure_http(ctx, ws);
+
+                if (check_closed(sck))
+                    return 0;
+            }
+
+            status = LWS_EVENT_OK;
+        }
+
+        return LWS_EVENT_OK;
+#endif
+
+        /*
+         * events always routed to protocols[0]
+         *
+         * XXX TODO: we need to open up for the upper layers using
+         *     optionally settable wsl_ctx_t-level callbacks at least
+         *
+         *   FILTER_NETWORK_CONNECTION
+         *   FILTER_PROTOCOL_CONNECTION
+         *   OPENSSL_*
+         *
+         * Probably for the sake of completeness we should open up
+         * all of these...
+         */
+
+    case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
+        fd = (ptrdiff_t)user;
+        /* we don't filter based on the socket/address */
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
+        /* we don't filter based on headers */
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
+        load_extra_certs(ctx, user, event);
+        return LWS_EVENT_OK;
+
+#ifdef LWS_OPENSSL_SUPPORT
+        if (ctx != NULL && ctx->load_certs != NULL)
+            ctx->load_certs(ctx, user, FALSE);
+#endif
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS:
+        load_extra_certs(ctx, user, TRUE);
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION:
+        if (verify_client_cert(user, in, len))
+            return LWS_EVENT_OK;
+        else
+            return LWS_EVENT_DENY;
+
+    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
+        /* no extra headers we'd like to add */
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
+        ext = (const char *)in;
+        /* deny all extensions on the server side */
+        mrp_debug("denying server extension '%s'", ext);
+        return LWS_EVENT_DENY;
+
+    case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
+        ext = (const char *)in;
+        /* deny all extensions on the client side */
+        mrp_debug("denying client extension '%s'", ext);
+        return LWS_EVENT_DENY;
+
+    default:
+        break;
+    }
+
+    return LWS_EVENT_DENY;
+}
+
+
+static int wsl_event(lws_ctx_t *ws_ctx, lws_t *ws, lws_event_t event,
+                     void *user, void *in, size_t len)
+{
+    wsl_ctx_t   *ctx = get_context_userdata(ws_ctx);
+    wsl_sck_t   *sck;
+    wsl_proto_t *up;
+    void        *data;
+    size_t       size;
+    uint32_t     total;
+    const char  *ext;
+    lws_proto_t *proto;
+    int          status;
+
+    MRP_UNUSED(ext);
+    MRP_UNUSED(ws_ctx);
+
+    switch (event) {
+    case LWS_CALLBACK_ESTABLISHED:
+        mrp_debug("client-handshake completed on websocket %p/%p", ws, user);
+
+        /*
+         * Connection acceptance is a bit tricky. Once libwebsockets
+         * has completed its handshaking phase with the client it lets
+         * us know about a new established connection. This is what we
+         * want to map to an incoming connection attempt. Since we don't
+         * want to know about the internals of the upper layer, neither
+         * want the upper layer to know about our internals, the only
+         * way to pass information about the connection around in the
+         * context at this point.
+         *
+         * To keep things simple we only prepare and handle once
+         * outstanding connection attemp at a time. This is equivalent
+         * to listening on a stream-socket with a backlog of 1. Since we
+         * run single-threaded it shouldn't ever be possible to have more
+         * than one pending connection if the upper layer does things
+         * right but we do check for this and reject multiple pending
+         * connections here...
+         *
+         * We store the pending websocket instance and its associated
+         * user data in the context then call the connection notifier
+         * callback. If the upper layer wants to accept the connection
+         * it calls wsl_accept_pending. That in turn digs these out from
+         * the context to set up and hook together things properly. If all
+         * goes fine wsl_accept_pending clears pending and pending_user
+         * from the context. If something fails or the upper layer decides
+         * not to accept the connection, pending and pending_user stay
+         * intact in which case we'll reject the client here once the
+         * callback returns.
+         */
+
+        if (ctx->pending != NULL) {
+            mrp_log_error("Multiple pending connections, rejecting.");
+            return LWS_EVENT_DENY;
+        }
+
+
+        proto = (lws_proto_t *)libwebsockets_get_protocol(ws);
+        up    = find_context_protocol(ctx, proto->name);
+
+        if (up == NULL) {
+            mrp_debug("unknown protocol '%s' requested, rejecting",
+                      proto ? proto->name : "<none>");
+            return LWS_EVENT_DENY;
+        }
+        else
+            mrp_debug("found descriptor %p for protocol '%s'", up, up->name);
+
+        ctx->pending       = ws;
+        ctx->pending_user  = user;
+        ctx->pending_proto = up;
+
+        wsl_ref_context(ctx);
+        up->cbs.connection(ctx, "XXX TODO dig out peer address", up->name,
+                           ctx->user_data, up->proto_data);
+
+        /* XXX TODO
+         * check if sockets gets properly closed and freed if
+         * cb->connection calls close on the 'listening' websocket in
+         * the transport layer...
+         */
+
+        if (ctx->pending == NULL)        /* connection accepted */
+            status = LWS_EVENT_OK;
+        else                             /* connection rejected */
+            status = LWS_EVENT_DENY;
+        wsl_unref_context(ctx);
+
+        return status;
+
+    case LWS_CALLBACK_CLOSED:
+        proto = (lws_proto_t *)libwebsockets_get_protocol(ws);
+        up    = find_context_protocol(ctx, proto->name);
+        mrp_debug("websocket %p/%p (%s) closed", ws, user,
+                  up ? up->name : "<unknown>");
+
+        sck = *(wsl_sck_t **)user;
+        up  = sck ? sck->proto : NULL;
+
+        if (up != NULL) {
+            SOCKET_BUSY_REGION(sck, {
+                    up->cbs.closed(sck, 0, sck->user_data, up->proto_data);
+                    up->cbs.check(sck, sck->user_data, up->proto_data);
+                });
+
+            check_closed(sck);
+        }
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_ESTABLISHED:
+        mrp_debug("server-handshake completed on websocket %p/%p", ws, user);
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
+        mrp_debug("client connection failed");
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_RECEIVE:
+    case LWS_CALLBACK_CLIENT_RECEIVE:
+        mrp_debug("%zu bytes received on websocket %p/%p", len, ws, user);
+        mrp_debug("%zd remaining from this message",
+                  libwebsockets_remaining_packet_payload(ws));
+
+        sck = *(wsl_sck_t **)user;
+        up  = sck ? sck->proto : NULL;
+
+        if (up != NULL) {
+            if (!up->framed && !mrp_fragbuf_missing(sck->buf)) {
+                /* new packet of an unframed protocol, push message size */
+                total = len + libwebsockets_remaining_packet_payload(ws);
+                mrp_debug("unframed protocol, total message size %u", total);
+
+                total = htobe32(total);
+                mrp_fragbuf_push(sck->buf, &total, sizeof(total));
+            }
+
+            if (mrp_fragbuf_push(sck->buf, in, len)) {
+                data = NULL;
+                size = 0;
+
+                while (mrp_fragbuf_pull(sck->buf, &data, &size)) {
+                    mrp_debug("websocket %p/%p has a message of %zd bytes",
+                              ws, user, size);
+
+                    SOCKET_BUSY_REGION(sck, {
+                            up->cbs.recv(sck, data, size, sck->user_data,
+                                         up->proto_data);
+                            up->cbs.check(sck, sck->user_data, up->proto_data);
+                        });
+
+                    if (check_closed(sck))
+                        break;
+                }
+            }
+            else {
+                mrp_log_error("failed to push data to fragment buffer");
+
+                SOCKET_BUSY_REGION(sck, {
+                        wsl_close(sck);
+#if 0 /*
+       * XXX Hmm... calling wsl_close instead of this now. Should be tested
+       *     if that really works.
+       */
+                        sck->closing = TRUE;    /* make sure sck gets closed */
+                        up->cbs.closed(sck, ENOBUFS, sck->user_data,
+                                       up->proto_data);
+                        libwebsocket_close_and_free_session(ctx->ctx, sck->sck,
+                                                            LWS_INTERNAL_ERROR);
+                        up->cbs.check(sck, sck->user_data, up->proto_data);
+#endif
+                    });
+
+                check_closed(sck);
+                return -1;
+            }
+        }
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_SERVER_WRITEABLE:
+#ifndef WEBSOCKETS_CLOSE_SESSION
+        sck = *(wsl_sck_t **)user;
+
+        if (sck == NULL) {
+            mrp_debug("asking to close unassociated websocket %p", ws);
+
+            return LWS_EVENT_CLOSE;
+        }
+#endif
+        mrp_debug("socket server side writeable again");
+        return LWS_EVENT_OK;
+
+    case LWS_CALLBACK_CLIENT_WRITEABLE:
+#ifndef WEBSOCKETS_CLOSE_SESSION
+        sck = *(wsl_sck_t **)user;
+
+        if (sck == NULL) {
+            mrp_debug("asking to close unassociated websocket %p", ws);
+
+            return LWS_EVENT_CLOSE;
+        }
+#endif
+        mrp_debug("socket client side writeable again");
+        return LWS_EVENT_OK;
+
+    default:
+        break;
+    }
+
+    return LWS_EVENT_OK;
+}
+
+
+
+/*
+ * logging
+ */
+
+#ifndef WEBSOCKETS_OLD
+
+#ifdef WEBSOCKETS_LOG_WITH_LEVEL
+static void libwebsockets(int level, const char *line)
+#else
+static void libwebsockets(const char *line)
+#endif
+{
+    const char *ts, *ll;
+    const char *b, *e, *lvl;
+    int         l, ls;
+    uint32_t    mask;
+
+#ifdef WEBSOCKETS_LOG_WITH_LEVEL
+    MRP_UNUSED(level);
+#else
+    /*
+     * If our (shaky) configure-time check gives a false negative,
+     * we'll expect only line but will be passed both level and line.
+     * Try catching it instead of crashing on it here...
+     */
+    if (line < (const char *)(LLL_CLIENT << 3))
+        return;
+#endif
+
+    if ((mask = mrp_log_get_mask()) == 0)
+        return;
+
+    /*
+     * Notes:
+     *     libwebsockets logging infrastructure has independently maskable
+     *     log classes and supports overriding its default logger. The log
+     *     classes are the regular error, warning, info, and debug classes
+     *     plus the libwebsockets-specific parser, header, extension, and
+     *     client classes. The logging infra filters the messages based on
+     *     their class, then formats the message and passes it on to the
+     *     (default builtin, or externally set) logger function. This gets
+     *     a fully formatted log message that consists of a timestamp, a
+     *     log class prefix and the message itself which typically contains
+     *     at least one terminating newline.
+     *
+     *     Because of the semantic content of the messages coming from
+     *     libwebsockets we'd like to preserve the class of errors and
+     *     warnings but convert the rest to debug messages. Additionally,
+     *     we'd like to keep the message format as consistent with the
+     *     murphy infra as possible with a reasonable effort. This means
+     *     stripping the timestamp and log class, as these are provided
+     *     by the murphy infra (if configured so). However, for the
+     *     libwebsockets-specific parser-, header-, extension-, and client-
+     *     classes we want to keep the extra information carried by the
+     *     log class as part of the message.
+     *
+     *     Because the libwebsockets log messages are terminated by '\n',
+     *     we also prepare here to properly bridge multiline messages to
+     *     the murphy infra (although I'm not sure the library ever issues
+     *     such messages).
+     *
+     *     So to sum it up the necessary steps to bridge messages here are:
+     *       1) strip timestamp,
+     *       2) dig out and strip log class
+     *       3) map log class to murphy infra, ie.
+     *          keep errors and warnings, squash the rest to debug
+     *       4) break multiline messages to lines
+     *       5) pass each line on to the murphy infra,
+     *          for parser-, header-, extension-, and client-messages
+     *          prefix each line with the class
+     *
+     */
+
+    lvl = "???";
+    ls  = 3;
+
+    ts = strchr(line, '[');
+    ll = ts != NULL ? strchr(ts, ']') : NULL;
+
+    /* strip timestamp, dig out log level, find beginning of the message */
+    if (ll != NULL && ll[1] == ' ') {
+        ll += 2;
+        b   = strchr(ll, ':');
+
+        if (b != NULL && b[1] == ' ') {
+            b += 2;
+
+            while (*b == ' ')
+                b++;
+
+            /* map log level: debug, info, err, warn, or other */
+            switch (*ll) {
+            case 'D':
+                if (!(mask & MRP_LOG_MASK_DEBUG))
+                    return;
+                lvl = "d";
+                break;
+            case 'I':
+                if (!(mask & MRP_LOG_MASK_INFO))
+                    return;
+                lvl = "i";
+                break;
+            case 'W':
+                if (!(mask & MRP_LOG_MASK_WARNING))
+                    return;
+                lvl = "w";
+                break;
+            case 'E':
+                if (ll[1] == 'R') {
+                    if (!(mask & MRP_LOG_MASK_ERROR))
+                        return;
+                    lvl = "e";
+                }
+                else {
+                other:
+                    if (!(mask & MRP_LOG_MASK_DEBUG))
+                        return;
+                    lvl = ll;
+                    e  = strchr(lvl, ':');
+
+                    if (e != NULL)
+                        ls = e - lvl;
+                    else {
+                        lvl = "???:";
+                        ls  = 4;
+                    }
+                }
+                break;
+
+            default:
+                goto other;
+            }
+        }
+        else
+            goto unknown;
+    }
+    else {
+    unknown:
+        /* if we get confused with the format, default to logging it all */
+        lvl = NULL;
+        b   = line;
+    }
+
+#ifdef WEBSOCKETS_LOG_WITH_LEVEL
+    switch (level) {
+    case LLL_ERR:     lvl = "e"; ls = 0; break;
+    case LLL_WARN:    lvl = "w"; ls = 0; break;
+    case LLL_INFO:    lvl = "i"; ls = 0; break;
+    case LLL_DEBUG:   lvl = "d"; ls = 0; break;
+    case LLL_NOTICE:  lvl = "d"; ls = 0; break;
+    case LLL_PARSER:  lvl = "parser" ; ls = 6; break;
+    case LLL_HEADER:  lvl = "header" ; ls = 6; break;
+    case LLL_EXT:     lvl = "ext"    ; ls = 3; break;
+    case LLL_CLIENT:  lvl = "client" ; ls = 6; break;
+    case LLL_LATENCY: lvl = "latency"; ls = 7; break;
+    default:          lvl = "???"    ; ls = 3; break;
+    }
+
+    b = line;
+    while (*b == ' ' || *b == '\t')
+        b++;
+#endif
+
+    /* break the message to lines and pass it on to the murphy infra */
+    e = strchr(b, '\n');
+    while (e || b) {
+        if (e)
+            l = e - b;
+        else
+            l = strlen(b);
+
+        if (!l)
+            break;
+
+        switch (lvl[0] | (lvl[1] << 8)) {
+        case 'd': mrp_debug("%*.*s", l, l, b);                      break;
+        case 'i': mrp_debug("%*.*s", l, l, b);                      break;
+        case 'w': mrp_log_warning("libwebsockets: %*.*s", l, l, b); break;
+        case 'e': mrp_log_error("libwebsockets: %*.*s", l, l, b);   break;
+        default:  mrp_debug("[%*.*s] %*.*s", ls, ls, lvl, l, l, b);
+        }
+
+        if (e != NULL) {
+            b = e + 1;
+            e = strchr(b, '\n');
+        }
+        else
+            b = NULL;
+    }
+}
+
+
+void wsl_set_loglevel(wsl_loglevel_t mask)
+{
+    lws_set_log_level(mask, libwebsockets);
+}
+
+
+#else /* WEBSOCKETS_OLD */
+
+void wsl_set_loglevel(wsl_loglevel_t mask)
+{
+    MRP_UNUSED(mask);
+
+    mrp_log_warning("libwebsockets too old to redirect logs...");
+}
+
+#endif /* WEBSOCKETS_OLD */
diff --git a/src/common/websocklib.h b/src/common/websocklib.h
new file mode 100644 (file)
index 0000000..83dbb95
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_WEBSOCKLIB_H__
+#define __MURPHY_WEBSOCKLIB_H__
+
+#include <sys/socket.h>
+
+#include <libwebsockets.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mainloop.h>
+
+MRP_CDECL_BEGIN
+
+/*
+ * websocket context
+ *
+ * A websocket context is basically a libwebsocket_context plus the
+ * additional glue data and code necessary to integrate the context
+ * into our mainloop. For our transport abstraction, we create one
+ * context per transport instance. However, accepted transports do
+ * share their context with the listening transport (ie. the server-
+ * side libwebsocket) they were accepted on.
+ *
+ * XXX TODO We probably need to change this so that we create one
+ *    context per address/port (or in libwebsockets case device/port).
+ *
+ */
+
+typedef struct wsl_ctx_s wsl_ctx_t;
+
+
+/*
+ * websocket
+ *
+ * A websocket is a libwebsocket instance together with its
+ * associated websocket context.
+ */
+typedef struct wsl_sck_s wsl_sck_t;
+
+
+/*
+ * websocket event callbacks to the upper transport layer
+ *
+ * These callbacks are used to deliver events from the underlying
+ * websocket transport layer to the upper murphy transport layer.
+ */
+typedef struct {
+    /** Connection attempt on a websocket. */
+    void (*connection)(wsl_ctx_t *ctx, char *addr, const char *protocol,
+                       void *user_data, void *proto_data);
+    /** Websocket connection closed by peer. */
+    void (*closed)(wsl_sck_t *sck, int error, void *user_data,
+                   void *proto_data);
+    /** Data received on websocket. */
+    void (*recv)(wsl_sck_t *sck, void *data, size_t size, void *user_data,
+                 void *proto_data);
+    /** Check if transport should be destroyed. */
+    int  (*check)(wsl_sck_t *sck, void *user_data, void *proto_data);
+
+    /** HTTP (content) request completed. */
+    void (*http_done)(wsl_sck_t *sck, const char *uri, void *user_data,
+                      void *proto_data);
+
+#ifdef LWS_OPENSSL_SUPPORT
+    /** Load extra client or server certificates, if necessary. */
+    void (*load_certs)(wsl_ctx_t *ctx, SSL_CTX *ssl, int is_server);
+#else
+    void (*load_certs)(wsl_ctx_t *, void *, int);
+#endif
+} wsl_callbacks_t;
+
+
+/*
+ * websocket protocol
+ *
+ * A websocket protocol is a protocol name together with protocol-specific
+ * upper-layer callbacks.
+ */
+typedef struct {
+    const char      *name;               /* protocol name */
+    wsl_callbacks_t  cbs;                /* event/request callbacks */
+    int              framed;             /* whether a framed protocol */
+    void            *proto_data;         /* protocol-specific user data */
+} wsl_proto_t;
+
+
+/*
+ * websocket write modes
+ */
+
+typedef enum {
+    WSL_SEND_TEXT   = LWS_WRITE_TEXT,    /* text mode */
+    WSL_SEND_BINARY = LWS_WRITE_BINARY,  /* binary/blob mode */
+#if 0
+    WSL_SEND_HTTP   = LWS_WRITE_HTTP     /* HTTP mode */
+#endif
+
+#define WSL_SEND_TEXT WSL_SEND_TEXT
+
+} wsl_sendmode_t;
+
+
+/*
+ * logging levels
+ */
+
+#ifndef WEBSOCKETS_OLD
+
+typedef enum {
+    WSL_LOG_NONE    = 0x0,
+    WSL_LOG_ERROR   = LLL_ERR,
+    WSL_LOG_WARNING = LLL_WARN,
+    WSL_LOG_INFO    = LLL_INFO,
+    WSL_LOG_DEBUG   = LLL_DEBUG,
+    WSL_LOG_ALL     = LLL_ERR | LLL_WARN | LLL_INFO | LLL_DEBUG,
+    WSL_LOG_PARSER  = LLL_PARSER,
+    WSL_LOG_HEADER  = LLL_HEADER,
+    WSL_LOG_EXT     = LLL_EXT,
+    WSL_LOG_CLIENT  = LLL_CLIENT,
+    WSL_LOG_EXTRA   = LLL_PARSER | LLL_HEADER | LLL_EXT | LLL_CLIENT,
+    WSL_LOG_VERBOSE = WSL_LOG_ALL | WSL_LOG_EXTRA
+} wsl_loglevel_t;
+
+#else /* !WEBSOCKETS_OLD */
+
+typedef enum {
+    WSL_LOG_NONE    = 0x0,
+    WSL_LOG_ERROR   = 0x0,
+    WSL_LOG_WARNING = 0x0,
+    WSL_LOG_INFO    = 0x0,
+    WSL_LOG_DEBUG   = 0x0,
+    WSL_LOG_ALL     = 0x0,
+    WSL_LOG_PARSER  = 0x0,
+    WSL_LOG_HEADER  = 0x0,
+    WSL_LOG_EXT     = 0x0,
+    WSL_LOG_CLIENT  = 0x0,
+    WSL_LOG_EXTRA   = 0x0,
+    WSL_LOG_VERBOSE = 0x0,
+} wsl_loglevel_t;
+
+#endif /* !WEBSOCKETS_OLD */
+
+typedef enum {
+    WSL_NO_SSL         = 0,              /* plain connection, no SSL */
+    WSL_SSL            = 1,              /* SSL, deny self-signed certs */
+    WSL_SSL_SELFSIGNED = 2,              /* SSL, allow self-signed certs */
+} wsl_ssl_t;
+
+
+/*
+ * websockets context configuration
+ */
+
+#define WSL_NO_GID -1
+#define WSL_NO_UID -1
+
+typedef struct {
+    struct sockaddr *addr;               /* address/port to listen on */
+    wsl_proto_t     *protos;             /* protocols to serve */
+    int              nproto;             /* number of protocols */
+    const char      *ssl_cert;           /* SSL certificate path */
+    const char      *ssl_pkey;           /* SSL private key path */
+    const char      *ssl_ca;             /* SSL CA path */
+    const char      *ssl_ciphers;        /* SSL cipher list */
+    int              gid;                /* group ID to change to, or -1 */
+    int              uid;                /* user ID to change to, or -1 */
+    void            *user_data;          /* opaque user data */
+    int              timeout;            /* keepalive timeout */
+    int              nprobe;             /* number of keepalive probes */
+    int              interval;           /* keepalive probe interval */
+} wsl_ctx_cfg_t;
+
+
+/** Set libwebsock logging level _and_ redirect to murphy logging infra. */
+void wsl_set_loglevel(wsl_loglevel_t mask);
+
+/** Create a websocket context. */
+wsl_ctx_t *wsl_create_context(mrp_mainloop_t *ml, wsl_ctx_cfg_t *cfg);
+
+/** Add a reference to a context. */
+wsl_ctx_t *wsl_ref_context(wsl_ctx_t *ctx);
+
+/** Remove a context reference, destroying it once the last is gone. */
+int wsl_unref_context(wsl_ctx_t *ctx);
+
+/** Create a new websocket connection using a given protocol. */
+wsl_sck_t *wsl_connect(wsl_ctx_t *ctx, struct sockaddr *sa,
+                       const char *protocol, wsl_ssl_t ssl, void *user_data);
+
+/** Accept a pending connection. */
+wsl_sck_t *wsl_accept_pending(wsl_ctx_t *ctx, void *user_data);
+
+/** Reject a pending connection. */
+void wsl_reject_pending(wsl_ctx_t *ctx);
+
+/** Close a websocket connection. Return user_data of the associated context. */
+void *wsl_close(wsl_sck_t *sck);
+
+/** Set websocket write mode (binary or text). */
+int wsl_set_sendmode(wsl_sck_t *sck, wsl_sendmode_t mode);
+
+/** Send data over a wbesocket. */
+int wsl_send(wsl_sck_t *sck, void *payload, size_t size);
+
+/** Serve the given file over the given socket. */
+int wsl_serve_http_file(wsl_sck_t *sck, const char *path, const char *mime);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_WEBSOCKLIB_H__ */
diff --git a/src/common/wsck-transport.c b/src/common/wsck-transport.c
new file mode 100644 (file)
index 0000000..fe44271
--- /dev/null
@@ -0,0 +1,995 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <arpa/inet.h>
+
+#include <libwebsockets.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/json.h>
+
+#include "websocklib.h"
+#include "wsck-transport.h"
+
+#define WSCKP "wsck"                     /* websocket transport prefix */
+#define WSCKL 4                          /* websocket transport prefix length */
+
+
+/*
+ * a websocket transport instance
+ */
+
+typedef struct {
+    MRP_TRANSPORT_PUBLIC_FIELDS;         /* common transport fields */
+    wsl_ctx_t          *ctx;             /* websocket context */
+    wsl_sck_t          *sck;             /* websocket instance */
+    int                 send_mode;       /* websocket send mode */
+    const char         *http_root;       /* HTTP content root */
+    mrp_wsck_urimap_t  *uri_table;       /* URI-to-path table */
+    mrp_wsck_mimemap_t *mime_table;      /* suffix to MIME-type table */
+    const char         *ssl_cert;        /* path to SSL certificate */
+    const char         *ssl_pkey;        /* path to SSL private key */
+    const char         *ssl_ca;          /* path to SSL CA */
+    wsl_ssl_t           ssl;             /* SSL mode (wsl_ssl_t) */
+    char               *protocol;        /* websocket protocol name */
+    wsl_proto_t         proto[2];        /* protocol setup */
+    mrp_list_hook_t     http_clients;    /* pure HTTP clients */
+} wsck_t;
+
+
+/*
+ * a pure HTTP client instance
+ */
+
+typedef struct {
+    wsl_sck_t          *sck;             /* websocket towards client */
+    mrp_list_hook_t     hook;            /* hook to listening socket */
+    const char         *http_root;       /* HTTP content root */
+    mrp_wsck_urimap_t  *uri_table;       /* URI to path mapping */
+    mrp_wsck_mimemap_t *mime_table;      /* suffix to MIME type mapping */
+} http_client_t;
+
+
+/*
+ * default file suffix to MIME type mapping table
+ */
+
+static mrp_wsck_mimemap_t mime_table[] = {
+    { "js"  , "application/javascript" },
+    { "html", "text/html"              },
+    { "htm ", "text/html"              },
+    { "txt" , "text/plain"             },
+    { NULL, NULL }
+};
+
+
+static int resolve_address(const char *str, mrp_wsckaddr_t *wa, socklen_t alen);
+
+static void connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol,
+                          void *user_data, void *proto_data);
+static void closed_cb(wsl_sck_t *sck, int error, void *user_data,
+                      void *proto_data);
+static void recv_cb(wsl_sck_t *sck, void *data, size_t size, void *user_data,
+                    void *proto_data);
+static int  check_cb(wsl_sck_t *sck, void *user_data, void *proto_data);
+
+static void http_connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol,
+                               void *user_data, void *proto_data);
+static void http_closed_cb(wsl_sck_t *sck, int error, void *user_data,
+                           void *proto_data);
+static void http_req_cb(wsl_sck_t *sck, void *data, size_t size,
+                        void *user_data, void *proto_data);
+static int  http_check_cb(wsl_sck_t *sck, void *user_data, void *proto_data);
+static void http_done_cb(wsl_sck_t *sck, const char *uri, void *user_data,
+                         void *proto_data);
+
+static socklen_t wsck_resolve(const char *str, mrp_sockaddr_t *addr,
+                              socklen_t size, const char **typep)
+{
+    mrp_wsckaddr_t *wa = (mrp_wsckaddr_t *)addr;
+    socklen_t       len;
+
+    len = resolve_address(str, wa, size);
+
+    if (len <= 0)
+        return 0;
+    else {
+        if (typep != NULL)
+            *typep = WSCKP;
+
+        return len;
+    }
+}
+
+
+static int wsck_open(mrp_transport_t *mt)
+{
+    wsck_t *t = (wsck_t *)mt;
+
+    mrp_list_init(&t->http_clients);
+    wsl_set_loglevel(WSL_LOG_ALL/* | WSL_LOG_EXTRA*/);
+
+    return TRUE;
+}
+
+
+static int wsck_createfrom(mrp_transport_t *mt, void *conn)
+{
+    wsck_t *t = (wsck_t *)mt;
+
+    MRP_UNUSED(conn);
+
+    mrp_list_init(&t->http_clients);
+
+    return FALSE;
+}
+
+
+static void wsck_close(mrp_transport_t *mt)
+{
+    wsck_t    *t   = (wsck_t *)mt;
+    wsl_ctx_t *ctx = t->ctx;
+    wsl_sck_t *sck = t->sck;
+    void      *user_data;
+
+    t->sck = NULL;
+    t->ctx = NULL;
+    mrp_free(t->protocol);
+    t->protocol = NULL;
+
+    user_data = wsl_close(sck);
+
+    if (user_data == t)                  /* was our associated context */
+        wsl_unref_context(ctx);
+}
+
+
+static int wsck_setopt(mrp_transport_t *mt, const char *opt, const void *val)
+{
+    wsck_t *t = (wsck_t *)mt;
+    int     success;
+
+    if (!strcmp(opt, MRP_WSCK_OPT_SENDMODE) && val != NULL) {
+        if (!strcmp(val, "binary"))
+            t->send_mode = WSL_SEND_BINARY;
+        else if (!strcmp(val, "text"))
+            t->send_mode = WSL_SEND_TEXT;
+        else
+            return FALSE;
+
+        if (t->sck != NULL)
+            return wsl_set_sendmode(t->sck, t->send_mode);
+        else
+            return TRUE;
+    }
+
+    success = TRUE;
+
+    if (!strcmp(opt, MRP_WSCK_OPT_HTTPDIR))
+        t->http_root = val;
+    else if (!strcmp(opt, MRP_WSCK_OPT_MIMEMAP))
+        t->mime_table = (void *)val;
+    else if (!strcmp(opt, MRP_WSCK_OPT_URIMAP))
+        t->uri_table = (void *)val;
+    else if (!strcmp(opt, MRP_WSCK_OPT_SSL_CERT))
+        t->ssl_cert = (const char *)val;
+    else if (!strcmp(opt, MRP_WSCK_OPT_SSL_PKEY))
+        t->ssl_pkey = (const char *)val;
+    else if (!strcmp(opt, MRP_WSCK_OPT_SSL_CA))
+        t->ssl_ca = (const char *)val;
+    else if (!strcmp(opt, MRP_WSCK_OPT_SSL))
+        t->ssl = *(wsl_ssl_t *)val;
+    else
+        success = FALSE;
+
+    return success;
+}
+
+
+static int wsck_bind(mrp_transport_t *mt, mrp_sockaddr_t *addr,
+                     socklen_t addrlen)
+{
+    wsck_t      *t       = (wsck_t *)mt;
+    wsl_proto_t  proto[] = {
+        {
+            .name       = "http",
+            .cbs        = { .connection = http_connection_cb,
+                            .closed     = http_closed_cb,
+                            .recv       = http_req_cb,
+                            .check      = http_check_cb,
+                            .http_done  = http_done_cb,
+                            .load_certs = NULL,               },
+            .framed     = FALSE,
+            .proto_data = NULL
+        },
+        {
+            .name       = "murphy",
+            .cbs        = { .connection = connection_cb,
+                            .closed     = closed_cb,
+                            .recv       = recv_cb,
+                            .check      = check_cb,
+                            .http_done  = NULL,
+                            .load_certs = NULL,               },
+            .framed     = FALSE,
+            .proto_data = NULL
+        }
+    };
+    wsl_ctx_cfg_t    cfg;
+    mrp_wsckaddr_t  *wa;
+    struct sockaddr *sa;
+
+    if (addr->any.sa_family != MRP_AF_WSCK || addrlen != sizeof(*wa))
+        return FALSE;
+
+    if (t->ctx != NULL)
+        return FALSE;
+
+    wa = (mrp_wsckaddr_t *)addr;
+
+    switch (wa->wsck_addr.family) {
+    case AF_INET:  sa = (struct sockaddr *)&wa->wsck_addr.v4; break;
+    case AF_INET6: sa = (struct sockaddr *)&wa->wsck_addr.v6; break;
+    default:
+        errno = EAFNOSUPPORT;
+        return FALSE;
+    }
+
+    if ((t->protocol = mrp_strdup(wa->wsck_proto)) == NULL)
+        return FALSE;
+
+    t->proto[0] = proto[0];
+    t->proto[1] = proto[1];
+
+    t->proto[1].name = t->protocol;
+
+    mrp_clear(&cfg);
+    cfg.addr      = sa;
+    cfg.protos    = &t->proto[0];
+    cfg.nproto    = MRP_ARRAY_SIZE(t->proto);
+    cfg.ssl_cert  = t->ssl_cert;
+    cfg.ssl_pkey  = t->ssl_pkey;
+    cfg.ssl_ca    = t->ssl_ca;
+    cfg.gid       = WSL_NO_GID;
+    cfg.uid       = WSL_NO_UID;
+    cfg.user_data = t;
+
+    t->ctx = wsl_create_context(t->ml, &cfg);
+
+    if (t->ctx != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static int wsck_listen(mrp_transport_t *mt, int backlog)
+{
+    MRP_UNUSED(mt);
+    MRP_UNUSED(backlog);
+
+    mt->listened = TRUE;
+
+    return TRUE;
+}
+
+
+static int wsck_accept(mrp_transport_t *mt, mrp_transport_t *mlt)
+{
+    wsck_t *lt = (wsck_t *)mlt;
+    wsck_t *t  = (wsck_t *)mt;
+
+    t->sck = wsl_accept_pending(lt->ctx, t);
+
+    if (t->sck != NULL) {
+        mrp_debug("accepted websocket connection %p", mlt);
+
+        /* default to mode inherited from listening transport */
+        t->send_mode = lt->send_mode;
+        wsl_set_sendmode(t->sck, t->send_mode);
+
+        /* inherit pure HTTP settings by default */
+        t->http_root  = lt->http_root;
+        t->uri_table  = lt->uri_table;
+        t->mime_table = lt->mime_table;
+
+        return TRUE;
+    }
+    else {
+        mrp_debug("failed to accept websocket connection on %p", mlt);
+
+        return FALSE;
+    }
+}
+
+
+static int wsck_connect(mrp_transport_t *mt, mrp_sockaddr_t *addr,
+                        socklen_t addrlen)
+{
+    wsck_t      *t     = (wsck_t *)mt;
+    wsl_proto_t  proto = {
+        .name       = "murphy",
+        .cbs        = { .connection = connection_cb,
+                        .closed     = closed_cb,
+                        .recv       = recv_cb,
+                        .check      = check_cb,      },
+        .framed     = FALSE,
+        .proto_data = NULL
+    };
+
+    wsl_ctx_cfg_t    cfg;
+    mrp_wsckaddr_t  *wa;
+    struct sockaddr *sa;
+    if (addr->any.sa_family != MRP_AF_WSCK || addrlen != sizeof(*wa))
+        return FALSE;
+
+    if (t->ctx != NULL)
+        return FALSE;
+
+    wa = (mrp_wsckaddr_t *)addr;
+
+    switch (wa->wsck_addr.family) {
+    case AF_INET:  sa = (struct sockaddr *)&wa->wsck_addr.v4; break;
+    case AF_INET6: sa = (struct sockaddr *)&wa->wsck_addr.v6; break;
+    default:
+        errno = EAFNOSUPPORT;
+        return FALSE;
+    }
+
+    if ((t->protocol = mrp_strdup(wa->wsck_proto)) == NULL)
+        return FALSE;
+
+    proto.name  = t->protocol;
+    t->proto[0] = proto;
+
+    mrp_clear(&cfg);
+    cfg.addr      = NULL;
+    cfg.protos    = &t->proto[0];
+    cfg.nproto    = 1;
+    cfg.ssl_cert  = t->ssl_cert;
+    cfg.ssl_pkey  = t->ssl_pkey;
+    cfg.ssl_ca    = t->ssl_ca;
+    cfg.gid       = WSL_NO_GID;
+    cfg.uid       = WSL_NO_UID;
+    cfg.user_data = t;
+
+    t->ctx = wsl_create_context(t->ml, &cfg);
+
+    if (t->ctx == NULL)
+        return FALSE;
+
+    t->sck = wsl_connect(t->ctx, sa, t->protocol, t->ssl, t);
+
+    if (t->sck != NULL) {
+        t->connected = TRUE;
+
+        return TRUE;
+    }
+    else {
+        wsl_unref_context(t->ctx);
+        t->ctx = NULL;
+    }
+
+    return FALSE;
+}
+
+
+static int wsck_disconnect(mrp_transport_t *mt)
+{
+    wsck_t    *t   = (wsck_t *)mt;
+    wsl_ctx_t *ctx = t->ctx;
+    wsl_sck_t *sck = t->sck;
+    void      *user_data;
+
+    t->sck = NULL;
+    t->ctx = NULL;
+
+    user_data = wsl_close(sck);
+
+    if (user_data == t)                  /* was our associated context */
+        wsl_unref_context(ctx);
+
+    return TRUE;
+}
+
+
+static int wsck_send(mrp_transport_t *mt, mrp_msg_t *msg)
+{
+    wsck_t  *t = (wsck_t *)mt;
+    void    *buf;
+    ssize_t  size;
+    int      success;
+
+    size = mrp_msg_default_encode(msg, &buf);
+
+    if (wsl_send(t->sck, buf, size))
+        success = TRUE;
+    else
+        success = FALSE;
+
+    mrp_free(buf);
+
+    return success;
+}
+
+
+static int wsck_sendraw(mrp_transport_t *mt, void *data, size_t size)
+{
+    wsck_t  *t = (wsck_t *)mt;
+
+    return wsl_send(t->sck, data, size);
+}
+
+
+static int wsck_senddata(mrp_transport_t *mt, void *data, uint16_t tag)
+{
+    wsck_t           *t = (wsck_t *)mt;
+    mrp_data_descr_t *type;
+    void             *buf;
+    size_t            size, reserve;
+    uint16_t         *tagp;
+    int               status;
+
+    type = mrp_msg_find_type(tag);
+
+    if (type != NULL) {
+        reserve = sizeof(*tagp);
+        size    = mrp_data_encode(&buf, data, type, reserve);
+
+        if (size > 0) {
+            tagp  = buf;
+            *tagp = htobe16(tag);
+
+            status = wsl_send(t->sck, buf, size);
+
+            mrp_free(buf);
+            return status;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int wsck_sendcustom(mrp_transport_t *mt, void *data)
+{
+    wsck_t     *t    = (wsck_t *)mt;
+    mrp_json_t *json = (mrp_json_t *)data;
+    const char *s;
+    int         status;
+
+    s = mrp_json_object_to_string(json);
+
+    /*
+     * Notes:
+     *     Although json-c internally counts the length of the serialized
+     *     object, it does not provide an API to get it out together with
+     *     the string. Great...
+     */
+
+    if (s != NULL)
+        status = wsl_send(t->sck, (void *)s, strlen(s));
+    else
+        status = FALSE;
+
+    return status;
+}
+
+
+static inline int looks_ipv4(const char *p)
+{
+    if (isdigit(p[0])) {
+        if (p[1] == '.')
+            return TRUE;
+
+        if (isdigit(p[1])) {
+            if (p[2] == '.')
+                return TRUE;
+
+            if (isdigit(p[2])) {
+                if (p[3] == '.')
+                    return TRUE;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int resolve_address(const char *str, mrp_wsckaddr_t *wa, socklen_t alen)
+{
+    struct addrinfo *ai, hints;
+    const char      *node, *port, *proto;
+    char             nbuf[256], pbuf[32];
+    int              family, status;
+    size_t           len;
+
+    if (strncmp(str, WSCKP":", WSCKL + 1) != 0)
+        return 0;
+    else
+        str += WSCKL + 1;
+
+    node = (char *)str;
+
+    if (node[0] == '[') {
+        node++;
+        family = AF_INET6;
+        port   = strchr(node, ']');
+    }
+    else if (looks_ipv4(node)) {
+        family = AF_INET;
+        port   = strchr(node, ':');
+    }
+    else {
+        family = AF_UNSPEC;
+        port   = strrchr(node, ':');
+    }
+
+    if (port == NULL || (*port != ':' && *port != ']')) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    len = port - node;
+
+    if (len > sizeof(nbuf) - 1) {
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    strncpy(nbuf, node, len);
+    nbuf[len] = '\0';
+
+    if (*port == ']')
+        port++;
+
+    if (*port != ':') {
+        errno = EINVAL;
+        return -1;
+    }
+
+    port++;
+    proto = strchr(port, '/');
+
+    if (proto != NULL) {
+        len = proto - port;
+
+        if (len > sizeof(pbuf) - 1) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        strncpy(pbuf, port, len);
+        pbuf[len] = '\0';
+
+        proto++;
+        if (strlen(proto) > sizeof(wa->wsck_proto) - 1) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+    }
+    else {
+        proto = MRP_WSCK_DEFPROTO;
+        len   = strlen(port);
+
+        if (len > sizeof(pbuf) - 1) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        strcpy(pbuf, port);
+    }
+
+    mrp_clear(&hints);
+    hints.ai_family = family;
+
+    status = getaddrinfo(nbuf, pbuf, &hints, &ai);
+
+    switch (status) {
+    case 0:
+        if (ai->ai_addrlen <= alen) {
+            wa->wsck_family = MRP_AF_WSCK;
+            memcpy(&wa->wsck_addr, ai->ai_addr, ai->ai_addrlen);
+            strcpy(wa->wsck_proto, proto);
+
+            len = sizeof(*wa);
+        }
+        else {
+            errno = EOVERFLOW;
+            len   = -1;
+        }
+
+        freeaddrinfo(ai);
+        return len;
+
+#define MAP_ERROR(ai_err, err)                  \
+        case EAI_##ai_err:                      \
+            errno = err;                        \
+            return -1
+
+        MAP_ERROR(AGAIN     , EAGAIN);
+        MAP_ERROR(BADFLAGS  , EADDRNOTAVAIL);
+        MAP_ERROR(FAIL      , EHOSTUNREACH);
+        MAP_ERROR(FAMILY    , EPFNOSUPPORT);
+        MAP_ERROR(MEMORY    , ENOMEM);
+        MAP_ERROR(NONAME    , EHOSTUNREACH);
+        MAP_ERROR(SERVICE   , EAFNOSUPPORT);
+        MAP_ERROR(SOCKTYPE  , EHOSTUNREACH);
+        MAP_ERROR(SYSTEM    , EHOSTUNREACH);
+#ifdef EAI_ADDRFAMILY
+        MAP_ERROR(ADDRFAMILY, EHOSTUNREACH);
+#endif
+#ifdef EAI_NODATA
+        MAP_ERROR(NODATA    , EHOSTUNREACH);
+#endif
+
+    default:
+        errno = EHOSTUNREACH;
+    }
+
+    return -1;
+}
+
+
+#if 0
+static int print_address(char *buf, size_t size, mrp_wsckaddr_t *wa)
+{
+    struct sockaddr *saddr;
+    socklen_t        salen;
+    char             nbuf[256], pbuf[32], *b, *e;
+    int              status;
+
+    if (wa->wsck_family != MRP_AF_WSCK) {
+    invalid:
+        errno = EINVAL;
+        return -1;
+    }
+
+    switch (wa->wsck_addr.family) {
+    case AF_INET:
+        saddr = (struct sockaddr *)&wa->wsck_addr.v4;
+        salen = sizeof(wa->wsck_addr.v4);
+        b     = "";
+        e     = "";
+        break;
+    case AF_INET6:
+        saddr = (struct sockaddr *)&wa->wsck_addr.v6;
+        salen = sizeof(wa->wsck_addr.v6);
+        b     = "[";
+        e     = "]";
+        break;
+    default:
+        goto invalid;
+    }
+
+    status = getnameinfo(saddr, salen, nbuf, sizeof(nbuf), pbuf, sizeof(pbuf),
+                         NI_NUMERICHOST | NI_NUMERICSERV);
+
+    if (status == 0)
+        return snprintf(buf, size, "wsck:%s%s%s:%s/%s",
+                        b, nbuf, e, pbuf, wa->wsck_proto);
+    else {
+        printf("error: %d: %s\n", status, gai_strerror(status));
+
+        errno = EINVAL;
+        return -1;
+    }
+}
+#endif
+
+static void connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol,
+                          void *user_data, void *proto_data)
+{
+    wsck_t *t = (wsck_t *)user_data;
+
+    MRP_UNUSED(addr);
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("incoming connection (%s) for context %p", protocol, ctx);
+
+    if (t->listened) {
+        MRP_TRANSPORT_BUSY(t, {
+                t->evt.connection((mrp_transport_t *)t, t->user_data);
+            });
+    }
+    else
+        mrp_log_error("connection attempt on non-listened transport %p", t);
+}
+
+
+static void closed_cb(wsl_sck_t *sck, int error, void *user_data,
+                      void *proto_data)
+{
+    wsck_t *t = (wsck_t *)user_data;
+
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("websocket %p closed", sck);
+
+    if (t->evt.closed != NULL)
+        MRP_TRANSPORT_BUSY(t, {
+                t->evt.closed((mrp_transport_t *)t, error, t->user_data);
+            });
+}
+
+
+static void recv_cb(wsl_sck_t *sck, void *data, size_t size, void *user_data,
+                    void *proto_data)
+{
+    wsck_t *t = (wsck_t *)user_data;
+
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("%zu bytes on websocket %p", size, sck);
+
+    MRP_TRANSPORT_BUSY(t, {
+            if (t->mode != MRP_TRANSPORT_MODE_CUSTOM)
+                t->recv_data((mrp_transport_t *)t, data, size, NULL, 0);
+            else {
+                mrp_json_t *json = mrp_json_string_to_object(data, size);
+
+                if (json != NULL) {
+                    t->recv_data((mrp_transport_t *)t, json, 0, NULL, 0);
+                    mrp_json_unref(json);
+                }
+            }
+        });
+}
+
+
+static int check_cb(wsl_sck_t *sck, void *user_data, void *proto_data)
+{
+    wsck_t *t = (wsck_t *)user_data;
+
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("checking if transport %p (%p) has been destroyed", t, sck);
+
+    if (t != NULL) {
+        if (t->check_destroy((mrp_transport_t *)t)) {
+            mrp_debug("transport has been destroyed");
+            return TRUE;
+        }
+        else
+            mrp_debug("transport has not been destroyed");
+    }
+
+    return FALSE;
+}
+
+
+static http_client_t *http_create_client(wsck_t *lt)
+{
+    http_client_t *c;
+
+    c = mrp_allocz(sizeof(*c));
+
+    if (c != NULL) {
+        mrp_list_init(&c->hook);
+        c->sck = wsl_accept_pending(lt->ctx, c);
+
+        if (c->sck != NULL) {
+            c->http_root  = lt->http_root;
+            c->uri_table  = lt->uri_table;
+            c->mime_table = lt->mime_table;
+
+            return c;
+        }
+        else {
+            mrp_free(c);
+            c = NULL;
+        }
+    }
+
+    return c;
+}
+
+
+static void http_destroy_client(http_client_t *c)
+{
+    if (c != NULL) {
+        mrp_list_delete(&c->hook);
+        wsl_close(c->sck);
+        mrp_free(c);
+    }
+}
+
+
+const char *http_mapuri(http_client_t *c, const char *uri,
+                        char *buf, size_t size)
+{
+    mrp_wsck_urimap_t  *um;
+    mrp_wsck_mimemap_t *mm;
+    const char         *suff, *root, *r, *s;
+
+    root = c->http_root ? c->http_root : "/";
+
+    if (c->uri_table != NULL) {
+        for (um = c->uri_table; um->uri != NULL; um++) {
+            if (!strcmp(uri, um->uri)) {
+                if (um->path[0] != '/') {
+                    r = root;
+                    s = "/";
+                }
+                else {
+                    r = "";
+                    s = "";
+                }
+
+                if (snprintf(buf, size, "%s%s%s", r, s, um->path) < (int)size)
+                    return um->type;
+                else
+                    return NULL;
+            }
+        }
+    }
+
+    if (c->http_root != NULL) {
+        if (snprintf(buf, size, "%s/%s", root, uri) >= (int)size)
+            return NULL;
+
+        suff = strrchr(uri, '.');
+
+        if (suff == NULL)
+            return "text/plain";
+        else
+            suff++;
+
+        if (c->mime_table != NULL) {
+            for (mm = c->mime_table; mm->suffix != NULL; mm++) {
+                if (!strcmp(mm->suffix, suff))
+                    return mm->type;
+            }
+        }
+
+        for (mm = mime_table; mm->suffix != NULL; mm++) {
+            if (!strcmp(mm->suffix, suff))
+                return mm->type;
+        }
+    }
+
+    return NULL;
+}
+
+
+static void http_connection_cb(wsl_ctx_t *ctx, char *addr, const char *protocol,
+                               void *user_data, void *proto_data)
+{
+    wsck_t        *t = (wsck_t *)user_data;
+    http_client_t *c;
+
+    MRP_UNUSED(addr);
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("incoming %s connection for context %p", protocol, ctx);
+
+    if (t->http_root != NULL || t->uri_table != NULL) {
+        c = http_create_client(t);
+
+        if (c != NULL)
+            mrp_debug("accepted pure HTTP client for context %p", ctx);
+        else
+            mrp_log_error("failed to create new HTTP client");
+    }
+    else
+        mrp_debug("rejecting pure HTTP client for context %p", ctx);
+}
+
+
+static void http_closed_cb(wsl_sck_t *sck, int error, void *user_data,
+                           void *proto_data)
+{
+    http_client_t *c = (http_client_t *)user_data;
+
+    MRP_UNUSED(proto_data);
+    MRP_UNUSED(error);
+
+    if (error)
+        mrp_debug("HTTP client socket %p closed with error %d", sck, error);
+    else
+        mrp_debug("HTTP client socket %p closed", sck);
+
+    http_destroy_client(c);
+}
+
+
+static void http_req_cb(wsl_sck_t *sck, void *data, size_t size,
+                        void *user_data, void *proto_data)
+{
+    http_client_t *c   = (http_client_t *)user_data;
+    const char    *uri = (const char *)data;
+    const char    *type;
+    char           path[PATH_MAX];
+
+    MRP_UNUSED(size);
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("HTTP request for URI '%s' on socket %p", uri, c->sck);
+
+    type = http_mapuri(c, uri, path, sizeof(path));
+
+    if (type != NULL) {
+        mrp_debug("mapped to '%s' (%s)", path, type);
+        wsl_serve_http_file(sck, path, type);
+    }
+    else
+        mrp_debug("failed to map URI");
+}
+
+
+static int http_check_cb(wsl_sck_t *sck, void *user_data, void *proto_data)
+{
+    http_client_t *c = (http_client_t *)user_data;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(sck);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(proto_data);
+
+    return FALSE;
+}
+
+
+static void http_done_cb(wsl_sck_t *sck, const char *uri, void *user_data,
+                         void *proto_data)
+{
+    http_client_t *c = (http_client_t *)user_data;
+
+    MRP_UNUSED(proto_data);
+
+    mrp_debug("HTTP request for '%s' done, closing socket %p.", uri, sck);
+
+    http_destroy_client(c);
+}
+
+
+MRP_REGISTER_TRANSPORT(wsck, WSCKP, wsck_t, wsck_resolve,
+                       wsck_open, wsck_createfrom, wsck_close, wsck_setopt,
+                       wsck_bind, wsck_listen, wsck_accept,
+                       wsck_connect, wsck_disconnect,
+                       wsck_send, NULL,
+                       wsck_sendraw, NULL,
+                       wsck_senddata, NULL,
+                       wsck_sendcustom, NULL,
+                       NULL, NULL,
+                       NULL, NULL);
diff --git a/src/common/wsck-transport.h b/src/common/wsck-transport.h
new file mode 100644 (file)
index 0000000..c35f29f
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_WEBSOCKET_TRANSPORT_H__
+#define __MURPHY_WEBSOCKET_TRANSPORT_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/transport.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_AF_WSCK 0xDC                 /* stolen address family */
+
+
+/*
+ * websocket transport address
+ */
+
+#define MRP_WSCKADDR_BASE                                               \
+    __SOCKADDR_COMMON(wsck_);            /* wsck_family: MRP_AF_WSCK */ \
+    union {                              /* websocket address */        \
+        sa_family_t         family;                                     \
+        struct sockaddr_in  v4;                                         \
+        struct sockaddr_in6 v6;                                         \
+    } wsck_addr                                                         \
+
+typedef struct {
+    MRP_WSCKADDR_BASE;
+} _mrp_wsckaddr_base_t;
+
+
+#define MRP_WSCK_DEFPROTO "murphy"
+#define MRP_WSCK_PROTOLEN (MRP_SOCKADDR_SIZE - sizeof(_mrp_wsckaddr_base_t))
+
+
+typedef struct {
+    MRP_WSCKADDR_BASE;                   /* websocket address */
+    char wsck_proto[MRP_WSCK_PROTOLEN];  /* websocket protocol */
+} mrp_wsckaddr_t;
+
+
+/*
+ * websocket transport options and values
+ */
+
+#define MRP_WSCK_OPT_SENDMODE    "send-mode"  /* sendmode option name */
+#define MRP_WSCK_SENDMODE_TEXT   "text"       /* sendmode text option */
+#define MRP_WSCK_SENDMODE_BINARY "binary"     /* sendmode blob option */
+
+
+#define MRP_WSCK_OPT_HTTPDIR  "http-dir"      /* HTTP content root */
+#define MRP_WSCK_OPT_MIMEMAP  "mime-map"      /* suffix-MIME table */
+#define MRP_WSCK_OPT_URIMAP   "uri-map"       /* URI-path table */
+#define MRP_WSCK_OPT_SSL_CERT "ssl-cert"      /* path to SSL certificate */
+#define MRP_WSCK_OPT_SSL_PKEY "ssl-pkey"      /* path to SSL priv. key */
+#define MRP_WSCK_OPT_SSL_CA   "ssl-ca"        /* path to SSL CA */
+#define MRP_WSCK_OPT_SSL      "ssl"           /* whether to connect with SSL */
+
+/*
+ * It is also possible to serve content over HTTP on a websocket transport.
+ *
+ * This is primarily intended for serving javascript API libraries to
+ * clients talking to you via the same websocket transport. The served
+ * libraries hide the details of the underlying communication protocol
+ * and present a more developer-friendly conventional javascript API.
+ *
+ * Currently the websocket transport provides two mechanisms for
+ * configuring HTTP content serving.
+ *
+ * 1) You can put all the files you're willing to expose via HTTP to a
+ *    dedicated directory and configure it to the transport as the
+ *    MRP_WSCK_OPT_HTTPROOT option. If you serve any other types of
+ *    files than HTML (*.htm, *.html), javascript (*.js), or text
+ *    (*.txt) files than you should also push down a table to map
+ *    the extra file suffices to MIME types. You can do this using
+ *    the MRP_SCK_OPT_MIMEMAP transport option.
+ *
+ * 2) You can use a mapping table that maps URIs to file path / mime
+ *    type pairs. You can push this table down to the transport as
+ *    the MRP_WSCK_URIMAP transport option.
+ *
+ *  HTTPROOT takes a char *, URIMAP takes a mrp_wsck_urimap_t *, and
+ *  MIMEMAP takes a mrp_wsck_mimemap_t * as their values. Both URI
+ *  and MIME type tables need to be NULL-terminated. If you set both
+ *  HTTPROOT and URIMAP, URIMAP entries with relative path names will
+ *  be treated relative to HTTPROOT.
+ *
+ * Notes:
+ *
+ *     If you push down any of these options, the websocket backend
+ *     will use the provided values as such __without__ making an
+ *     internal copy. IOW, you better make sure that the passed values
+ *     are valid throughout the full lifetime of the transport (and
+ *     if that is a transport you listen on also the lifetime of all
+ *     transports accepted on that transport) otherwise you'll end up
+ *     with severe memory corruption.
+ *
+ */
+
+typedef struct {
+    const char *uri;                     /* exported URI */
+    const char *path;                    /* path to file */
+    const char *type;                    /* MIME type to use */
+} mrp_wsck_urimap_t;
+
+typedef struct {
+    const char *suffix;                  /* filename suffix */
+    const char *type;                    /* MIME type */
+} mrp_wsck_mimemap_t;
+
+
+
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_WEBSOCKET_TRANSPORT_H__ */
diff --git a/src/console-client/Makefile b/src/console-client/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/console-client/client.c b/src/console-client/client.c
new file mode 100644 (file)
index 0000000..1346691
--- /dev/null
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <murphy/common.h>
+#include <murphy/plugins/console-protocol.h>
+
+#include <breedline/breedline-murphy.h>
+
+#define client_info  mrp_log_info
+#define client_warn  mrp_log_warning
+#define client_error mrp_log_error
+
+#define DEFAULT_PROMPT  "murphy"
+#define DEFAULT_ADDRESS "unxs:@murphy-console"
+
+
+/*
+ * message types
+ */
+
+typedef enum {
+    MSG_UNKNOWN,                         /* unknown message */
+    MSG_PROMPT,                          /* set new prompt */
+    MSG_COMMAND,                         /* client command */
+    MSG_ECHO,                            /* output from server */
+    MSG_COMPLETIONS,                     /* get/set completion results */
+} msg_type_t;
+
+
+/*
+ * client receive buffer
+ */
+
+#define RECVBUF_MAXSIZE                  /* maximum buffer size */
+
+typedef struct {
+    char *buf;                           /* incoming data buffer */
+    int   size;                          /* size of buffer */
+    char *in;                            /* write pointer */
+    char *out;                           /* read pointer */
+} recvbuf_t;
+
+
+/*
+ * client context
+ */
+
+typedef struct {
+    const char      *server;             /* server address */
+    int              log_mask;           /* log mask */
+    const char      *log_target;         /* log target */
+    mrp_mainloop_t  *ml;                 /* murphy mainloop */
+    mrp_transport_t *t;                  /* transport to server */
+    int              seqno;              /* sequence number */
+    recvbuf_t        buf;                /* receive buffer */
+    brl_t           *brl;                /* breedline for terminal input */
+    char           **cmds;               /* commands to run */
+    int              ncmd;               /* number of commands */
+    int              ccmd;               /* current command */
+} client_t;
+
+
+int send_cmd(client_t *c, const char *cmd)
+{
+    mrp_msg_t *msg;
+    uint16_t   tag, type;
+    uint32_t   len;
+    int        success;
+
+    len = cmd ? strlen(cmd) + 1 : 0;
+
+    if (len > 1) {
+        tag  = MRP_CONSOLE_INPUT;
+        type = MRP_MSG_FIELD_BLOB;
+        msg  = mrp_msg_create(tag, type, len, cmd, NULL);
+
+        if (msg != NULL) {
+            success = mrp_transport_send(c->t, msg);
+            mrp_msg_unref(msg);
+            return success;
+        }
+
+        return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+void input_cb(brl_t *brl, const char *input, void *user_data)
+{
+    client_t *c   = (client_t *)user_data;
+    int       len = input ? strlen(input) + 1 : 0;
+
+    if (len > 1) {
+        brl_add_history(brl, input);
+        brl_hide_prompt(brl);
+
+        send_cmd(c, input);
+
+        brl_show_prompt(brl);
+    }
+}
+
+
+static int input_setup(client_t *c)
+{
+    int         fd;
+    const char *prompt;
+
+    fd     = fileno(stdin);
+    prompt = DEFAULT_PROMPT;
+    c->brl = brl_create_with_murphy(fd, prompt, c->ml, input_cb, c);
+
+    if (c->brl != NULL) {
+        brl_show_prompt(c->brl);
+        return TRUE;
+    }
+    else {
+        mrp_log_error("Failed to breedline for console input.");
+        return FALSE;
+    }
+}
+
+
+static void input_cleanup(client_t *c)
+{
+    if (c->brl != NULL) {
+        brl_destroy(c->brl);
+        c->brl = NULL;
+    }
+}
+
+
+static void hide_prompt(client_t *c)
+{
+    if (c->brl)
+        brl_hide_prompt(c->brl);
+}
+
+
+static void set_prompt(client_t *c, const char *prompt)
+{
+    if (c->brl)
+        brl_set_prompt(c->brl, prompt);
+}
+
+
+static void show_prompt(client_t *c)
+{
+    if (c->brl)
+        brl_show_prompt(c->brl);
+}
+
+
+void recvfrom_evt(mrp_transport_t *t, mrp_msg_t *msg,
+                  mrp_sockaddr_t *addr, socklen_t addrlen, void *user_data)
+{
+    client_t        *c = (client_t *)user_data;
+    mrp_msg_field_t *f;
+    char           *prompt, *output;
+    size_t          size;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    hide_prompt(c);
+
+    if ((f = mrp_msg_find(msg, MRP_CONSOLE_OUTPUT)) != NULL) {
+        output = f->str;
+        size   = f->size[0];
+        printf("%.*s", (int)size, output);
+    }
+    else if ((f = mrp_msg_find(msg, MRP_CONSOLE_PROMPT)) != NULL) {
+        prompt = f->str;
+        set_prompt(c, prompt);
+    }
+    else if ((f = mrp_msg_find(msg, MRP_CONSOLE_BYE)) != NULL) {
+        mrp_mainloop_quit(c->ml, 0);
+        return;
+    }
+
+    if (c->cmds != NULL) {
+        if (c->ccmd < c->ncmd)
+            send_cmd(c, c->cmds[c->ccmd++]);
+        else
+            mrp_mainloop_quit(c->ml, 0);
+    }
+
+    show_prompt(c);
+}
+
+
+
+void recv_evt(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    recvfrom_evt(t, msg, NULL, 0, user_data);
+}
+
+
+void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(c);
+
+    if (error) {
+        mrp_log_error("Connection closed with error %d (%s).", error,
+                      strerror(error));
+        exit(1);
+    }
+    else {
+        mrp_log_info("Peer has closed the connection.");
+        mrp_mainloop_quit(c->ml, 0);
+    }
+}
+
+
+int client_setup(client_t *c)
+{
+    static mrp_transport_evt_t evt;
+
+    mrp_sockaddr_t  addr;
+    socklen_t       addrlen;
+    const char     *type;
+
+    addrlen = mrp_transport_resolve(NULL, c->server,
+                                    &addr, sizeof(addr), &type);
+
+    if (addrlen > 0) {
+        evt.closed      = closed_evt;
+        evt.recvmsg     = recv_evt;
+        evt.recvmsgfrom = recvfrom_evt;
+
+        c->t = mrp_transport_create(c->ml, type, &evt, c, 0);
+
+        if (c->t == NULL) {
+            mrp_log_error("Failed to create new transport.");
+            return FALSE;
+        }
+
+        if (!mrp_transport_connect(c->t, &addr, addrlen)) {
+            mrp_log_error("Failed to connect to %s.", c->server);
+            mrp_transport_destroy(c->t);
+            c->t = NULL;
+            return FALSE;
+        }
+
+        return TRUE;
+    }
+    else
+        mrp_log_error("Failed to resolve address '%s'.", c->server);
+
+    return FALSE;
+}
+
+
+static void client_cleanup(client_t *c)
+{
+    mrp_transport_destroy(c->t);
+    c->t = NULL;
+}
+
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h);
+
+    MRP_UNUSED(user_data);
+
+    switch (signum) {
+    case SIGINT:
+        mrp_log_info("Got SIGINT, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+    }
+}
+
+
+static void client_set_defaults(client_t *c)
+{
+    mrp_clear(c);
+    c->seqno      = 1;
+    c->server     = DEFAULT_ADDRESS;
+    c->log_mask   = MRP_LOG_UPTO(MRP_LOG_INFO);
+    c->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list     ap;
+    const char *exe;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    exe = strrchr(argv0, '/');
+
+    printf("usage: %s [options] [console-commands]\n\n"
+           "The possible options are:\n"
+           "  -s, --server <address>         server transport to connect to\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable debug messages\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+    printf("\n");
+    printf("If commands are given on the command line, the console will ");
+    printf("first execute\nthem then exit after receiving a response to ");
+    printf("the last command. If no commands\n");
+    printf("are given on the command line, the console will prompt for ");
+    printf("commands to execute.\nFor a short summary of commands ");
+    printf("try running '%s help'.\n", exe ? exe + 1 : argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+int parse_cmdline(client_t *c, int argc, char **argv)
+{
+#   define OPTIONS "s:l:t:v:d:h"
+    struct option options[] = {
+        { "server"    , required_argument, NULL, 's' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 's':
+            c->server = optarg;
+            break;
+
+        case 'v':
+            c->log_mask <<= 1;
+            c->log_mask  |= 1;
+            break;
+
+        case 'l':
+            c->log_mask = mrp_log_parse_levels(optarg);
+            if (c->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            c->log_target = mrp_log_parse_target(optarg);
+            if (!c->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            c->log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return optind;
+}
+
+
+int main(int argc, char *argv[])
+{
+    client_t c;
+    int      next;
+
+    client_set_defaults(&c);
+    next = parse_cmdline(&c, argc, argv);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    c.seqno = 1;
+
+    if ((c.ml = mrp_mainloop_create()) == NULL) {
+        mrp_log_error("Failed to create mainloop.");
+        exit(1);
+    }
+
+    mrp_add_sighandler(c.ml, SIGINT, signal_handler, &c);
+
+    if (next >= argc) {
+        if (!input_setup(&c))
+            goto fail;
+        c.cmds = NULL;
+        c.ncmd = 0;
+        c.ccmd = 0;
+    }
+    else {
+        c.cmds = argv + next;
+        c.ncmd = argc - next;
+        c.ccmd = 0;
+    }
+
+    if (!client_setup(&c))
+        goto fail;
+
+    mrp_mainloop_run(c.ml);
+
+    client_cleanup(&c);
+
+    if (next >= argc)
+        input_cleanup(&c);
+
+    return 0;
+
+ fail:
+    client_cleanup(&c);
+    input_cleanup(&c);
+    exit(1);
+}
+
diff --git a/src/core.h b/src/core.h
new file mode 100644 (file)
index 0000000..d178d17
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CORE_H__
+#define __MURPHY_CORE_H__
+
+#include <murphy/core/context.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/console.h>
+
+#endif
diff --git a/src/core/Makefile b/src/core/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/core/auth-deny.c b/src/core/auth-deny.c
new file mode 100644 (file)
index 0000000..ecba7f1
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/core/context.h>
+#include <murphy/core/auth.h>
+
+
+static int deny_auth(const char *target, mrp_auth_mode_t mode, const char *id,
+                     const char *token, void *auth_data)
+{
+    MRP_UNUSED(target);
+    MRP_UNUSED(mode);
+    MRP_UNUSED(id);
+    MRP_UNUSED(token);
+    MRP_UNUSED(auth_data);
+
+    return MRP_AUTH_RESULT_DENY;
+}
+
+
+MRP_REGISTER_AUTHENTICATOR("deny", NULL, deny_auth);
diff --git a/src/core/auth-smack.c b/src/core/auth-smack.c
new file mode 100644 (file)
index 0000000..d549073
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/smack.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/core/context.h>
+#include <murphy/core/auth.h>
+
+
+static int smack_auth(const char *target, mrp_auth_mode_t mode, const char *id,
+                      const char *token, void *auth_data)
+{
+    char access[4];
+    int  status;
+
+    MRP_UNUSED(token);
+    MRP_UNUSED(auth_data);
+
+    if (target == NULL || id == NULL)
+        goto error;
+
+    access[0] = (mode & MRP_AUTH_MODE_READ)  ? 'r' : '-';
+    access[1] = (mode & MRP_AUTH_MODE_WRITE) ? 'w' : '-';
+    access[2] = (mode & MRP_AUTH_MODE_EXEC)  ? 'x' : '-';
+    access[3] = '\0';
+
+    status = smack_have_access(target, id, access);
+
+    mrp_debug("SMACK '%s' access of %s to %s: %d", access, id, target, status);
+
+    switch (status) {
+    case 1:
+        return MRP_AUTH_RESULT_GRANT;
+    case 0:
+        return MRP_AUTH_RESULT_DENY;
+    default:
+    error:
+        return MRP_AUTH_RESULT_ERROR;
+    }
+}
+
+
+MRP_REGISTER_AUTHENTICATOR("smack", NULL, smack_auth);
diff --git a/src/core/auth.c b/src/core/auth.c
new file mode 100644 (file)
index 0000000..5e310cc
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include <murphy/core/context.h>
+#include <murphy/core/auth.h>
+
+
+typedef struct {
+    char            *name;               /* backend name */
+    mrp_auth_cb_t    cb;                 /* backend method */
+    void            *auth_data;          /* backend data */
+    mrp_list_hook_t  hook;               /* to list of backends */
+} auth_backend_t;
+
+
+static MRP_LIST_HOOK(pending);
+
+
+static auth_backend_t *find_auth(mrp_list_hook_t *backends, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    auth_backend_t  *auth;
+
+    mrp_list_foreach(backends, p, n) {
+        auth = mrp_list_entry(p, typeof(*auth), hook);
+
+        if (!strcmp(auth->name, name))
+            return auth;
+    }
+
+    return NULL;
+}
+
+
+static int register_auth(mrp_list_hook_t *backends, const char *name,
+                         mrp_auth_cb_t cb, void *auth_data)
+{
+    auth_backend_t *auth;
+
+    if (find_auth(backends, name) != NULL)
+        return FALSE;
+
+    auth = mrp_allocz(sizeof(*auth));
+
+    if (auth != NULL) {
+        mrp_list_init(&auth->hook);
+
+        auth->name      = mrp_strdup(name);
+        auth->cb        = cb;
+        auth->auth_data = auth_data;
+
+        if (auth->name != NULL) {
+            /*
+             * Notes:
+             *     Prepending here is a crude hack to make sure the first
+             *     registered backend, which is 'deny', ends up being the
+             *     last in the list of authenticators. Maybe we should add
+             *     a priority to the backend registration interface and
+             *     use it to make this more explicit...
+             */
+
+            mrp_list_prepend(backends, &auth->hook);
+
+            mrp_debug("registered authentication backend %s", auth->name);
+
+            return TRUE;
+        }
+
+        mrp_free(auth);
+    }
+
+    return FALSE;
+}
+
+
+static void unregister_auth(mrp_list_hook_t *backends, const char *name)
+{
+    auth_backend_t *auth;
+
+    auth = find_auth(backends, name);
+
+    if (auth != NULL) {
+        mrp_list_delete(&auth->hook);
+        mrp_free(auth->name);
+        mrp_free(auth);
+    }
+}
+
+
+int mrp_register_authenticator(mrp_context_t *ctx, const char *name,
+                               mrp_auth_cb_t cb, void *auth_data)
+{
+    mrp_list_hook_t *backends;
+
+    if (ctx != NULL) {
+        if (MRP_UNLIKELY(!mrp_list_empty(&pending)))
+            mrp_list_move(&ctx->auth, &pending);
+
+        backends = &ctx->auth;
+    }
+    else
+        backends = &pending;
+
+    if (register_auth(backends, name, cb, auth_data) == 0)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+void mrp_unregister_authenticator(mrp_context_t *ctx, const char *name)
+{
+    mrp_list_hook_t *backends = ctx ? &ctx->auth : &pending;
+
+    unregister_auth(backends, name);
+}
+
+
+int mrp_authenticate(mrp_context_t *ctx, const char *backend,
+                     const char *target, mrp_auth_mode_t mode,
+                     const char *id, const char *token)
+{
+    auth_backend_t  *auth;
+    mrp_list_hook_t *p, *n;
+    int              status, result;
+
+    if (MRP_UNLIKELY(!mrp_list_empty(&pending)))
+        mrp_list_move(&ctx->auth, &pending);
+
+    /*
+     * Notes:
+     *
+     * Currently we let the caller request authentication by any available
+     * backend by using MRP_AUTH_ANY. If requested so, access is granted
+     * if any of the backends grants access.
+     *
+     * We might want to change this in the future, probably by either
+     * requiring the caller to always specify a valid authentication backend,
+     * or by having one of the backends be marked as default which then would
+     * be used for authentication in these cases. Either of those would make
+     * it more difficult to grant unwanted access accidentially in the case
+     * of multiple available backends.
+     */
+
+    result = MRP_AUTH_RESULT_ERROR;
+
+    mrp_list_foreach(&ctx->auth, p, n) {
+        auth = mrp_list_entry(p, typeof(*auth), hook);
+
+        if (backend == MRP_AUTH_ANY || !strcmp(backend, auth->name)) {
+            status = auth->cb(target, mode, id, token, auth->auth_data);
+
+            mrp_debug("backend %s, access 0x%x of %s/%s to %s: %d", auth->name,
+                      mode, id, token ? token : "<none>", target, status);
+
+            if (backend != MRP_AUTH_ANY)
+                return status;
+
+            switch (status) {
+            case MRP_AUTH_RESULT_GRANT:
+                return MRP_AUTH_RESULT_GRANT;
+            case MRP_AUTH_RESULT_DENY:
+                result = MRP_AUTH_RESULT_DENY;
+                break;
+            default:
+                break;
+            }
+        }
+    }
+
+    return result;
+}
diff --git a/src/core/auth.h b/src/core/auth.h
new file mode 100644 (file)
index 0000000..4eb712d
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_AUTH_H__
+#define __MURPHY_AUTH_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/core/context.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_AUTH_ANY NULL                /* any authenticator */
+
+/*
+ * authentication access modes
+ */
+typedef enum {
+    MRP_AUTH_MODE_UNKNOWN = 0x0,         /* mode unknown / not applicabe */
+    MRP_AUTH_MODE_NA      = 0x0,         /* alias for unknown */
+    MRP_AUTH_MODE_READ    = 0x1,         /* 'read' access */
+    MRP_AUTH_MODE_WRITE   = 0x2,         /* 'write' access */
+    MRP_AUTH_MODE_EXEC    = 0x4,         /* 'execution' access */
+} mrp_auth_mode_t;
+
+
+/*
+ * authentication results
+ */
+typedef enum {
+    MRP_AUTH_RESULT_ERROR = -1,          /* authentiation failed with error */
+    MRP_AUTH_RESULT_DENY  =  0,          /* requested access denied */
+    MRP_AUTH_RESULT_GRANT =  1,          /* requested access granted */
+} mrp_auth_result_t;
+
+
+/** Type for authenticator backend callback. */
+typedef int (*mrp_auth_cb_t)(const char *target, mrp_auth_mode_t mode,
+                             const char *id, const char *token,
+                             void *user_data);
+
+/** Register an authentication backend. */
+int mrp_register_authenticator(mrp_context_t *ctx, const char *name,
+                               mrp_auth_cb_t cb, void *user_data);
+
+/** Unregister an authentication backend. */
+void mrp_unregister_authenticator(mrp_context_t *ctx, const char *name);
+
+/** Check if the given id has the reqested access to the given target. */
+int mrp_authenticate(mrp_context_t *ctx, const char *backend,
+                     const char *target, mrp_auth_mode_t mode,
+                     const char *id, const char *token);
+
+/** Convenience macro for autoregistering an authentication backend. */
+#define MRP_REGISTER_AUTHENTICATOR(name, init_cb, auth_cb)              \
+    MRP_INIT static void register_authenticator(void)                   \
+    {                                                                   \
+        int  (*initfn)(void **) = init_cb;                              \
+        void  *user_data        = NULL;                                 \
+                                                                        \
+        if (initfn == NULL || initfn(&user_data))                       \
+            mrp_register_authenticator(NULL, name, auth_cb, user_data); \
+        else                                                            \
+            mrp_log_error("Failed to initialize user data for "         \
+                          "authenticator '%s'.", name);                 \
+    }                                                                   \
+    struct __mrp_allow_trailing_semicolon
+
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_AUTH_H__ */
diff --git a/src/core/console-builtin.c b/src/core/console-builtin.c
new file mode 100644 (file)
index 0000000..5dfdfef
--- /dev/null
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#define DOTS "........................................................" \
+             "......................."
+
+#define NPRINT(mc, fmt, args...) fprintf(mc->stdout, fmt , ## args)
+#define EPRINT(mc, fmt, args...) fprintf(mc->stderr, fmt , ## args)
+
+
+
+/*
+ * top-level console commands
+ */
+
+
+static void get_string_lengthes(mrp_console_t *mc,
+                                size_t *nmaxp, size_t *smaxp, size_t *tmaxp)
+{
+    mrp_console_group_t *grp;
+    mrp_console_cmd_t   *cmd;
+    mrp_list_hook_t     *p, *n;
+    int                  i;
+    size_t               nlen, slen, tmax, nmax, smax;
+
+    tmax = nmax = smax = 0;
+
+    mrp_list_foreach(&mc->ctx->cmd_groups, p, n) {
+        grp = mrp_list_entry(p, typeof(*grp), hook);
+
+        for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+            nlen = strlen(cmd->name);
+            slen = strlen(cmd->summary);
+            nmax = MRP_MAX(nmax, nlen);
+            smax = MRP_MAX(smax, slen);
+            tmax = MRP_MAX(tmax, nlen + slen);
+        }
+    }
+
+    mrp_list_foreach(&core_groups, p, n) {
+        grp = mrp_list_entry(p, typeof(*grp), hook);
+
+        for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+            nlen = strlen(cmd->name);
+            slen = strlen(cmd->summary);
+            nmax = MRP_MAX(nmax, nlen);
+            smax = MRP_MAX(smax, slen);
+            tmax = MRP_MAX(tmax, nlen + slen);
+        }
+    }
+
+    *nmaxp = nmax;
+    *smaxp = smax;
+    *tmaxp = tmax;
+}
+
+
+static void help_overview(mrp_console_t *mc)
+{
+    mrp_console_group_t *grp;
+    mrp_console_cmd_t   *cmd;
+    mrp_list_hook_t     *p, *n;
+    int                  i, l, dend;
+    size_t               tmax, nmax, smax;
+
+    get_string_lengthes(mc, &nmax, &smax, &tmax);
+
+    if (4 + 2 + 2 + tmax < 79) {
+        dend = 79 - smax - 2;
+    }
+    else
+        dend = tmax + 20;
+
+    NPRINT(mc, "The following commands are available:\n\n");
+
+    mrp_list_foreach(&mc->ctx->cmd_groups, p, n) {
+        grp = mrp_list_entry(p, typeof(*grp), hook);
+
+        if (*grp->name)
+            NPRINT(mc, "  commands in group '%s':\n", grp->name);
+        else
+            NPRINT(mc, "  general commands:\n");
+
+        for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+            NPRINT(mc, "    %s  %n", cmd->name, &l);
+            NPRINT(mc, "%*.*s %s\n", dend - l, dend - l, DOTS, cmd->summary);
+        }
+
+        NPRINT(mc, "\n");
+    }
+
+    mrp_list_foreach(&core_groups, p, n) {
+        grp = mrp_list_entry(p, typeof(*grp), hook);
+
+        if (*grp->name)
+            NPRINT(mc, "  commands in group '%s':\n", grp->name);
+        else
+            NPRINT(mc, "  general commands:\n");
+
+        for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+            NPRINT(mc, "    %s  %n", cmd->name, &l);
+            NPRINT(mc, "%*.*s %s\n", dend - l, dend - l, DOTS, cmd->summary);
+        }
+
+        NPRINT(mc, "\n");
+    }
+}
+
+
+static void help_group(mrp_console_t *mc, const char *name)
+{
+    mrp_console_group_t *grp;
+    mrp_console_cmd_t   *cmd;
+    mrp_list_hook_t     *p, *n;
+    const char          *t;
+    int                  i;
+
+    grp = find_group(mc->ctx, name);
+
+    if (grp != NULL) {
+        if (grp->descr != NULL)
+            NPRINT(mc, "%s\n", grp->descr);
+
+        NPRINT(mc, "The following commands are available:\n");
+        for (i = 0, cmd = grp->commands; i < grp->ncommand; i++, cmd++) {
+            NPRINT(mc, "- %s (syntax: %s%s%s)\n\n", cmd->name,
+                   grp->name ? grp->name : "", grp->name ? " " : "",
+                   cmd->syntax);
+            NPRINT(mc, "%s\n", cmd->description);
+        }
+    }
+    else {
+        EPRINT(mc, "Command group '%s' does not exist.\n", name);
+        EPRINT(mc, "The existing groups are: ");
+        t = "";
+        mrp_list_foreach(&mc->ctx->cmd_groups, p, n) {
+            grp = mrp_list_entry(p, typeof(*grp), hook);
+            if (*grp->name) {
+                EPRINT(mc, "%s'%s'", t, grp->name);
+                t = ", ";
+            }
+        }
+        EPRINT(mc, ".\n");
+    }
+
+
+}
+
+
+#define HELP_SYNTAX      "help [group|command]"
+#define HELP_SUMMARY     "print help on a command group or a command"
+#define HELP_DESCRIPTION                                                  \
+    "Give general help or help on a specific command group or a\n"        \
+    "single command.\n"
+
+static void cmd_help(mrp_console_t *mc, void *user_data, int argc, char **argv)
+{
+    console_t *c = (console_t *)mc;
+    char      *ha[2];
+
+    MRP_UNUSED(c);
+
+    switch (argc) {
+    case 2:
+        help_overview(mc);
+        break;
+
+    case 3:
+        help_group(mc, argv[2]);
+        break;
+
+    case 4:
+        fprintf(mc->stdout, "Help for command '%s/%s'.\n", argv[2], argv[3]);
+        break;
+
+    default:
+        ha[0] = "help";
+        ha[1] = "help";
+        fprintf(mc->stderr, "help: invalid arguments (%d).\n", argc);
+        fflush(mc->stderr);
+        cmd_help(mc, user_data, 2, ha);
+    }
+}
+
+
+#define EXIT_SYNTAX      "exit"
+#define EXIT_SUMMARY     "exit from a command group or the console"
+#define EXIT_DESCRIPTION                                                  \
+    "Exit current console mode, or close the console.\n"
+
+
+static void cmd_exit(mrp_console_t *mc, void *user_data, int argc, char **argv)
+{
+    console_t *c = (console_t *)mc;
+    char      *ha[2];
+
+    switch (argc) {
+    case 2:
+        if (c->grp != NULL) {
+            if (c->cmd != NULL)
+                c->cmd = NULL;
+            else
+                c->grp = NULL;
+        }
+        else {
+        close_console:
+            fprintf(mc->stdout, "Bye.\n");
+            mrp_destroy_console(mc);
+        }
+        break;
+
+    case 3:
+        if (!strcmp(argv[2], "console"))
+            goto close_console;
+        /* intentional fall-through */
+
+    default:
+        ha[0] = "help";
+        ha[1] = "exit";
+        fprintf(mc->stderr, "exit: invalid arguments\n");
+        cmd_help(mc, user_data, 2, ha);
+    }
+}
+
+
+MRP_CONSOLE_GROUP(builtin_cmd_group, "", NULL, NULL, {
+        MRP_TOKENIZED_CMD("help", cmd_help, FALSE,
+                          HELP_SYNTAX, HELP_SUMMARY, HELP_DESCRIPTION),
+        MRP_TOKENIZED_CMD("exit", cmd_exit, FALSE,
+                          EXIT_SYNTAX, EXIT_SUMMARY, EXIT_DESCRIPTION),
+});
diff --git a/src/core/console-command.c b/src/core/console-command.c
new file mode 100644 (file)
index 0000000..28317eb
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "console-builtin.c"
+#include "console-debug.c"
+#include "console-db.c"
+#include "console-log.c"
diff --git a/src/core/console-command.h b/src/core/console-command.h
new file mode 100644 (file)
index 0000000..9ef1732
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CONSOLE_COMMAND_H__
+#define __MURPHY_CONSOLE_COMMAND_H__
+
+
+/** Macro to declare an array of console commands. */
+#define MRP_CONSOLE_COMMANDS(_var, ...)           \
+    static mrp_console_cmd_t _var[] = __VA_ARGS__
+
+/** Macro to declare a console command group. */
+#define MRP_CONSOLE_GROUP(_var, _name, _descr, _data, ...)                \
+    MRP_CONSOLE_COMMANDS(_var##_cmds, __VA_ARGS__);                       \
+    static mrp_console_group_t _var = {                                   \
+        .name      = (char *)_name,                                       \
+        .descr     = _descr,                                              \
+        .user_data = _data,                                               \
+        .commands  = _var##_cmds,                                         \
+        .ncommand  = MRP_ARRAY_SIZE(_var##_cmds),                         \
+        .hook      = MRP_LIST_INIT(_var.hook),                            \
+    };
+
+/** Macro to declare a console command that wants tokenized input. */
+#define MRP_TOKENIZED_CMD(_name, _cb, _flags, _syntax, _summ, _descr) {     \
+        .name        = _name,                                               \
+        .syntax      = _syntax,                                             \
+        .summary     = _summ,                                               \
+        .description = _descr,                                              \
+        .flags       = ((_flags) == 0x1 ? MRP_CONSOLE_SELECTABLE : _flags), \
+      { .tok         = _cb, }                                               \
+    }
+
+/** Macro to declare a console command that wants a raw input. */
+#define MRP_RAWINPUT_CMD(_name, _cb, _flags, _syntax, _summ, _descr) {      \
+        .name        = _name,                                               \
+        .syntax      = _syntax,                                             \
+        .summary     = _summ,                                               \
+        .description = _descr,                                              \
+        .flags       = MRP_CONSOLE_RAWINPUT |                               \
+                       ((_flags) == 0x1 ? MRP_CONSOLE_SELECTABLE : _flags), \
+      { .raw         = _cb, }                                               \
+    }
+
+typedef struct mrp_console_s mrp_console_t;
+
+
+/*
+ * console command flags
+ */
+
+typedef enum {
+    MRP_CONSOLE_TOKENIZE   = 0x0,        /* wants tokenized input */
+    MRP_CONSOLE_RAWINPUT   = 0x2,        /* wants raw input */
+    MRP_CONSOLE_SELECTABLE = 0x4,        /* selectable as command mode */
+    MRP_CONSOLE_CATCHALL   = 0x8,        /* catch-all command handler */
+} mrp_console_flag_t;
+
+
+/*
+ * a console command
+ */
+
+typedef struct {
+    const char         *name;            /* command name */
+    const char         *syntax;          /* command syntax */
+    const char         *summary;         /* short help */
+    const char         *description;     /* long command description */
+    mrp_console_flag_t  flags;           /* command flags */
+    union {                              /* tokenized or raw input cb */
+        void   (*tok)(mrp_console_t *c, void *user_data, int argc, char **argv);
+        void   (*raw)(mrp_console_t *c, void *user_data, const char *grp,
+                      const char *cmd, char *args);
+    };
+} mrp_console_cmd_t;
+
+
+/*
+ * a group of console commands
+ */
+
+typedef struct {
+    char              *name;             /* command group name/prefix */
+    char              *descr;            /* group description */
+    void              *user_data;        /* opaque callback data */
+    mrp_console_cmd_t *commands;         /* commands in this group */
+    int                ncommand;         /* number of commands */
+    mrp_list_hook_t    hook;             /* to list of command groups */
+} mrp_console_group_t;
+
+
+/** Register a console command group. */
+int mrp_console_add_group(mrp_context_t *ctx, mrp_console_group_t *group);
+
+/** Unregister a console command group. */
+int mrp_console_del_group(mrp_context_t *ctx, mrp_console_group_t *group);
+
+/** Convenience macro to register a group of core commands. */
+#define MRP_CORE_CONSOLE_GROUP(_var, _name, _descr, _data, ...)           \
+    MRP_CONSOLE_GROUP(_var, _name, _descr, _data, __VA_ARGS__);           \
+                                                                          \
+    static void _var##_register_core_group(void)                          \
+        __attribute__((constructor));                                     \
+                                                                          \
+    static void __attribute__((constructor))                              \
+    _var##_register_core_group(void) {                                    \
+        mrp_console_add_core_group(&_var);                                \
+    }                                                                     \
+                                                                          \
+    static void __attribute__((destructor))                               \
+    _var##_unregister_core_group(void) {                                  \
+        mrp_console_del_core_group(&_var);                                \
+    }                                                                     \
+    struct mrp_allow_trailing_semicolon
+
+/** Pre-register a group of core commands to register later to any context. */
+int mrp_console_add_core_group(mrp_console_group_t *group);
+
+/** Unregister a pre-registered group of core commands. */
+int mrp_console_del_core_group(mrp_console_group_t *group);
+
+#endif /* __MURPHY_CONSOLE_COMMAND_H__ */
diff --git a/src/core/console-db.c b/src/core/console-db.c
new file mode 100644 (file)
index 0000000..384a13c
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/*
+ * DB commands
+ */
+
+#include <stdarg.h>
+
+#include <murphy-db/mql.h>
+#include <murphy-db/mqi.h>
+
+static void db_cmd(char *fmt, ...)
+{
+    mql_result_t *r;
+    char          buf[1024];
+    va_list       ap;
+    int           n, error;
+    const char   *msg;
+
+    va_start(ap, fmt);
+    n = vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+
+    if (n < (int)sizeof(buf) && n > 0) {
+        r = mql_exec_string(mql_result_string, buf);
+
+        if (mql_result_is_success(r))
+            printf("%s\n", mql_result_string_get(r));
+        else {
+            error = mql_result_error_get_code(r);
+            msg   = mql_result_error_get_message(r);
+
+            printf("DB error %d: %s\n", error, msg ? msg : "unknown error");
+        }
+
+        mql_result_free(r);
+    }
+}
+
+
+static void db_exec(mrp_console_t *c, void *user_data, const char *grp,
+                    const char *cmd, char *args)
+{
+    mqi_handle_t tx;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(grp);
+    MRP_UNUSED(cmd);
+
+    tx = mqi_begin_transaction();
+    db_cmd(args);
+    mqi_commit_transaction(tx);
+}
+
+
+void db_source(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    mqi_handle_t tx;
+    int          i, success;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    success = TRUE;
+    tx      = mqi_begin_transaction();
+
+    for (i = 2; i < argc && success; i++) {
+        if (mql_exec_file(argv[i]) == 0)
+            printf("DB script '%s' OK\n", argv[i]);
+        else {
+            printf("DB script error %d: %s\n", errno, strerror(errno));
+            success = FALSE;
+        }
+    }
+
+    if (success)
+        mqi_commit_transaction(tx);
+    else {
+        mqi_rollback_transaction(tx);
+        printf("DB rolled back.\n");
+    }
+}
+
+
+#define DB_GROUP_DESCRIPTION                                                \
+    "Database commands provide means to manipulate the Murphy database\n"   \
+    "from the console. Commands are provided for listing, describing,\n"    \
+    "and removing tables as well as for issuing arbitrary high-level\n"     \
+    "MQL commands. Note that these commands are intended for debugging\n" \
+    "and debugging purposes. Extra care should to be taken when directly\n" \
+    "manipulating the database."
+
+#define DBEXEC_SYNTAX      "<DB command>"
+#define DBEXEC_SUMMARY     "execute the given database MQL command"
+#define DBEXEC_DESCRIPTION "Executes the given MQL command and prints the\n" \
+    "result.\n"
+
+#define DBSRC_SYNTAX      "source <file>"
+#define DBSRC_SUMMARY     "evaluate the MQL script in the given <file>"
+#define DBSRC_DESCRIPTION "Read and evaluate the contents of <file>.\n"
+
+
+MRP_CORE_CONSOLE_GROUP(db_group, "db", DB_GROUP_DESCRIPTION, NULL, {
+        MRP_TOKENIZED_CMD("source", db_source, FALSE,
+                          DBSRC_SYNTAX, DBSRC_SUMMARY, DBSRC_DESCRIPTION),
+        MRP_RAWINPUT_CMD("eval", db_exec,
+                         MRP_CONSOLE_CATCHALL | MRP_CONSOLE_SELECTABLE,
+                         DBEXEC_SYNTAX, DBEXEC_SUMMARY, DBEXEC_DESCRIPTION),
+});
diff --git a/src/core/console-debug.c b/src/core/console-debug.c
new file mode 100644 (file)
index 0000000..0ec9040
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/core/console.h>
+#include <errno.h>
+
+/*
+ * debug commands
+ */
+
+static void debug_enable(mrp_console_t *c, void *user_data,
+                         int argc, char **argv)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+    MRP_UNUSED(user_data);
+
+    mrp_debug_enable(TRUE);
+
+    printf("Debugging is now enabled.\n");
+}
+
+
+static void debug_disable(mrp_console_t *c, void *user_data,
+                          int argc, char **argv)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+    MRP_UNUSED(user_data);
+
+    mrp_debug_enable(FALSE);
+
+    printf("Debugging is now disabled.\n");
+}
+
+
+static void debug_show(mrp_console_t *c, void *user_data,
+                       int argc, char **argv)
+{
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    mrp_debug_dump_config(c->stdout);
+}
+
+
+static void debug_set(mrp_console_t *c, void *user_data,
+                      int argc, char **argv)
+{
+    int i;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    for (i = 2; i < argc; i++)
+        mrp_debug_set_config(argv[i]);
+}
+
+
+static void debug_reset(mrp_console_t *c, void *user_data,
+                        int argc, char **argv)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    mrp_debug_reset();
+
+    printf("Debugging configuration has been reset to default.");
+}
+
+
+static void debug_mm(mrp_console_t *c, void *user_data,
+                     int argc, char **argv)
+{
+    MRP_UNUSED(user_data);
+
+    if (argc == 3 && !strcmp(argv[2], "dump"))
+        mrp_mm_dump(c->stdout);
+    else if (argc == 4 && !strcmp(argv[2], "dump")) {
+        char *path = argv[3];
+        FILE *fp   = fopen(path, "w");
+
+        if (fp != NULL) {
+            printf("Producing mm-dump to '%s'...\n", path);
+            mrp_mm_dump(fp);
+            fclose(fp);
+        }
+        else
+            printf("Failed to open '%s' (%d: %s).", path,
+                   errno, strerror(errno));
+    }
+    else
+        printf("Unknown command...\n");
+}
+
+
+#define DEBUG_GROUP_DESCRIPTION                                           \
+    "Debugging commands provide fine-grained control over runtime\n"      \
+    "debugging messages produced by the murphy daemon or any of the\n"    \
+    "murphy plugins loaded. Each debug message that is generated by\n"    \
+    "the standard murphy debug macro declares a debug site that can\n"    \
+    "be turned on or off using debug rules. Debug rules come in two\n"    \
+    "flavours, enabling and inhibiting. Enabling rules turn matching\n"   \
+    "debug messages on, while inhibiting rules turn matching debug\n"     \
+    "messages off. Debug rules are in one of the following formats:\n"    \
+    "\n"                                                                  \
+    "    func[=on|off]:       all messages from <func>\n"                 \
+    "    @file[=on|off]:      all messages in <file>\n"                   \
+    "    @file:line=[on|off]: messages at <file>:<line>\n"                \
+    "    *[=on|off]:          all messages\n"                             \
+    "\n"                                                                  \
+    "Filenames without a directory can match filenames with one.\n"       \
+    "Enabling rules are evaluated before inhibiting rules. All debug\n"   \
+    "messages are suppressed if debugging is disabled.\n"
+
+#define ENABLE_SYNTAX       "enable"
+#define ENABLE_SUMMARY      "enable debugging"
+#define ENABLE_DESCRIPTION                                                \
+    "Enable debugging globally. Unless debugging is enabled, all debug\n" \
+    "messages are suppressed, even those for which matching enabling\n"   \
+    "rules exist.\n"
+
+#define DISABLE_SYNTAX      "disable"
+#define DISABLE_SUMMARY     "disable debugging"
+#define DISABLE_DESCRIPTION                                               \
+    "Disable debugging globally. Unless debugging is enabled all debug\n" \
+    "messages are suppressed, even those for which matching enabling\n"   \
+    "rules exist.\n"
+
+#define SHOW_SYNTAX         "show"
+#define SHOW_SUMMARY        "show debugging configuration"
+#define SHOW_DESCRIPTION                                                  \
+    "Show the current debugging configuration, and debug rules.\n"
+
+#define SET_SYNTAX          "set [+|-]rule"
+#define SET_SUMMARY         "change debugging rules"
+#define SET_DESCRIPTION                                                   \
+    "Install a new or remove an existing debugging rule. Debug rules\n"   \
+    "are in one of the following formats:\n"                              \
+    "\n"                                                                  \
+    "    func[=on|off]:       all messages from <func>\n"                 \
+    "    @file[=on|off]:      all messages in <file>\n"                   \
+    "    @file:line[=on|off]: messages at <file>:<line>\n"                \
+    "    *[=on|off]:          all messages\n"                             \
+
+#define RESET_SYNTAX        "reset"
+#define RESET_SUMMARY       "reset debugging configuration"
+#define RESET_DESCRIPTION                                                 \
+    "Reset the debugging configuration to the defaults. This will turn"   \
+    "disable debugging globally and flush all debugging rules.\n"
+
+#define MM_SYNTAX         "mm dump [file]"
+#define MM_SUMMARY        "produce an mm-dump"
+#define MM_DESCRIPTION                                                  \
+    "Produce an mm-dump of all currently allocated objects to the given\n" \
+    "or to the console.\n"
+
+MRP_CORE_CONSOLE_GROUP(debug_group, "debug", DEBUG_GROUP_DESCRIPTION, NULL, {
+        MRP_TOKENIZED_CMD("enable", debug_enable, FALSE,
+                          ENABLE_SYNTAX, ENABLE_SUMMARY, ENABLE_DESCRIPTION),
+        MRP_TOKENIZED_CMD("disable", debug_disable, FALSE,
+                          DISABLE_SYNTAX, DISABLE_SUMMARY, DISABLE_DESCRIPTION),
+        MRP_TOKENIZED_CMD("show", debug_show, FALSE,
+                          SHOW_SYNTAX, SHOW_SUMMARY, SHOW_DESCRIPTION),
+        MRP_TOKENIZED_CMD("set", debug_set, FALSE,
+                          SET_SYNTAX, SET_SUMMARY, SET_DESCRIPTION),
+        MRP_TOKENIZED_CMD("reset", debug_reset, FALSE,
+                          RESET_SYNTAX, RESET_SUMMARY, RESET_DESCRIPTION),
+        MRP_TOKENIZED_CMD("mm", debug_mm, FALSE,
+                          MM_SYNTAX, MM_SUMMARY, MM_DESCRIPTION),
+});
diff --git a/src/core/console-log.c b/src/core/console-log.c
new file mode 100644 (file)
index 0000000..9fd5294
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+static void log_level(mrp_console_t *c, void *user_data,
+                      int argc, char **argv)
+{
+    mrp_log_mask_t mask;
+    char           buf[256];
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    if (argc == 2)
+        mask = mrp_log_get_mask();
+    else {
+        mask = mrp_log_parse_levels(argv[2]);
+        mrp_log_set_mask(mask);
+    }
+
+    printf("current logging mask: %s\n",
+           mrp_log_dump_mask(mask, buf, sizeof(buf)));
+}
+
+
+static void log_target(mrp_console_t *c, void *user_data,
+                       int argc, char **argv)
+{
+    const char *target;
+    const char *targets[32];
+    int         i, n;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    if (argc == 2) {
+        target = mrp_log_get_target();
+        n      = mrp_log_get_targets(targets, MRP_ARRAY_SIZE(targets));
+
+        printf("available log targets:\n");
+        for (i = 0; i < n; i++)
+            printf("    %s%s\n", targets[i],
+                   !strcmp(targets[i], target) ? " (active)" : "");
+    }
+    else if (argc == 3) {
+        target = argv[2];
+
+        if (!mrp_log_set_target(target))
+            printf("failed to change logging target to %s\n", target);
+        else {
+            printf("changed log target to %s\n", target);
+            mrp_log_info("changed log target to %s", target);
+        }
+    }
+    else {
+        printf("%s/%s invoked with wrong number of arguments\n",
+               argv[0], argv[1]);
+    }
+}
+
+
+
+
+#define LOG_GROUP_DESCRIPTION                                               \
+    "Log commands provide means to configure the active logging settings\n" \
+    "of Murphy. Commands are provided for changing the logging level,\n"    \
+    "listing log targets, and settting the active target.\n"
+
+#define LEVEL_SYNTAX      "[[info[,warning[,error]]]]"
+#define LEVEL_SUMMARY     "change or show the active logging level"
+#define LEVEL_DESCRIPTION \
+    "Changes the logging level to the given one. Without arguments it\n" \
+    "prints out the current logging level.\n"
+
+#define TARGET_SYNTAX      "[stdout|stderr|syslog|<other targets>]"
+#define TARGET_SUMMARY     "change or show the active logging target"
+#define TARGET_DESCRIPTION \
+    "Changes the active logging target to the given one. Without arguments\n" \
+    "it lists the available targets and the currently active one."
+
+MRP_CORE_CONSOLE_GROUP(log_group, "log", LOG_GROUP_DESCRIPTION, NULL, {
+        MRP_TOKENIZED_CMD("level" , log_level , FALSE,
+                          LEVEL_SYNTAX , LEVEL_SUMMARY , LEVEL_DESCRIPTION),
+        MRP_TOKENIZED_CMD("target", log_target, FALSE,
+                          TARGET_SYNTAX, TARGET_SUMMARY, TARGET_DESCRIPTION)
+});
diff --git a/src/core/console-priv.h b/src/core/console-priv.h
new file mode 100644 (file)
index 0000000..ac20de5
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CONSOLE_PRIV_H__
+#define __MURPHY_CONSOLE_PRIV_H__
+
+#include <murphy/core/console.h>
+
+int console_setup(mrp_context_t *ctx);
+void console_cleanup(mrp_context_t *ctx);
+
+#endif /* __MURPHY_CONSOLE_PRIV_H__ */
diff --git a/src/core/console.c b/src/core/console.c
new file mode 100644 (file)
index 0000000..a9591b1
--- /dev/null
@@ -0,0 +1,1051 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _GNU_SOURCE                      /* we want fopencookie */
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+
+#include <murphy/core/console.h>
+
+#define MAX_PROMPT 64                    /* ie. way too long */
+#define CMD_EXIT   "exit"
+#define CMD_HELP   "help"
+
+#define COLOR  "\E"
+#define YELLOW "33m"
+#define WHITE  "37m"
+#define RED    "31m"
+
+#define CNORM  COLOR""WHITE
+#define CWARN  COLOR""YELLOW
+#define CERR   COLOR""RED
+
+#define RFD 0
+#define WFD 1
+
+
+#define MRP_CFG_MAXLINE 4096             /* input line length limit */
+#define MRP_CFG_MAXARGS   64             /* command argument limit */
+
+typedef struct {
+    char  buf[MRP_CFG_MAXLINE];          /* input buffer */
+    char  raw[MRP_CFG_MAXLINE];          /* raw input */
+    char *token;                         /* current token */
+    char *in;                            /* filling pointer */
+    char *out;                           /* consuming pointer */
+    char *next;                          /* next token buffer position */
+    int   error;                         /* whether has encounted and error */
+    char *file;                          /* file being processed */
+    int   line;                          /* line number */
+    int   next_newline;
+    int   was_newline;
+} input_t;
+
+static int get_next_line(input_t *in, char **args, size_t size);
+
+static MRP_LIST_HOOK(core_groups);
+
+/*
+ * an active console
+ */
+
+typedef struct {
+    MRP_CONSOLE_PUBLIC_FIELDS;               /* publicly visible fields */
+    mrp_console_group_t *grp;                /* active group if any */
+    mrp_console_cmd_t   *cmd;                /* active command if any */
+    char                 prompt[MAX_PROMPT]; /* current prompt */
+    input_t              in;                 /* input buffer */
+    mrp_list_hook_t      hook;               /* to list of active consoles */
+    int                  pout[2];            /* pipe for output proxying */
+    mrp_io_watch_t      *wout;               /* output watch */
+    int                  ofd;                /* saved fileno(stdout) */
+    int                  oblk;               /* saved O_NONBLOCK for ofd */
+    int                  efd;                /* saved fileno(stderr) */
+    int                  eblk;               /* saved O_NONBLOCK for efd */
+} console_t;
+
+
+static int check_destroy(mrp_console_t *mc);
+static int purge_destroyed(mrp_console_t *mc);
+static FILE *console_fopen(mrp_console_t *mc);
+static int console_read_output(console_t *c, void *buf, size_t size);
+static void console_flush_output(console_t *c, int copy_orig);
+static void console_release_output(console_t *c);
+
+static ssize_t input_evt(mrp_console_t *mc, void *buf, size_t size);
+static void disconnected_evt(mrp_console_t *c, int error);
+static ssize_t complete_evt(mrp_console_t *c, void *input, size_t isize,
+                            char **completions, size_t csize);
+
+static void register_commands(mrp_context_t *ctx);
+static void unregister_commands(mrp_context_t *ctx);
+
+void console_setup(mrp_context_t *ctx)
+{
+    mrp_list_init(&ctx->cmd_groups);
+    mrp_list_init(&ctx->consoles);
+
+    register_commands(ctx);
+}
+
+
+void console_cleanup(mrp_context_t *ctx)
+{
+    mrp_list_hook_t *p, *n;
+    console_t       *c;
+
+    mrp_list_foreach(&ctx->consoles, p, n) {
+        c = mrp_list_entry(p, typeof(*c), hook);
+        mrp_destroy_console((mrp_console_t *)c);
+    }
+
+    mrp_list_init(&ctx->cmd_groups);
+
+    unregister_commands(ctx);
+}
+
+
+static void output_cb(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                      void *user_data)
+{
+    mrp_console_t *mc = (mrp_console_t *)user_data;
+    console_t     *c  = (console_t *)mc;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(fd);
+
+    if (events & MRP_IO_EVENT_IN)
+        console_flush_output(c, TRUE);
+}
+
+
+mrp_console_t *mrp_create_console(mrp_context_t *ctx, mrp_console_req_t *req,
+                                  void *backend_data)
+{
+    static mrp_console_evt_t evt = {
+        .input        = input_evt,
+        .disconnected = disconnected_evt,
+        .complete     = complete_evt
+    };
+
+    console_t *c;
+
+    if (ctx->disable_console) {
+        mrp_log_error("Usage of debug console has been explicitly disabled.");
+        errno = EPERM;
+        return NULL;
+    }
+
+    if (req->write == NULL || req->close      == NULL ||
+        req->free  == NULL || req->set_prompt == NULL)
+        return NULL;
+
+    if ((c = mrp_allocz(sizeof(*c))) != NULL) {
+        mrp_list_init(&c->hook);
+        c->ctx = ctx;
+        c->req = *req;
+        c->evt = evt;
+
+        c->stdout = console_fopen((mrp_console_t *)c);
+        c->stderr = console_fopen((mrp_console_t *)c);
+
+        if (c->stdout == NULL || c->stderr == NULL)
+            goto fail;
+
+        c->backend_data  = backend_data;
+        c->check_destroy = check_destroy;
+
+        c->in.file = "<console input>";
+        c->in.line = 0;
+
+        if (pipe(c->pout) < 0)
+            mrp_log_warning("Failed to create console redirection pipe.");
+        else {
+            fcntl(c->pout[WFD], F_SETPIPE_SZ, 32 * 1024);
+            c->wout = mrp_add_io_watch(ctx->ml, c->pout[RFD],
+                                       MRP_IO_EVENT_IN, output_cb, c);
+        }
+        c->ofd = c->efd = -1;
+
+        mrp_list_append(&ctx->consoles, &c->hook);
+        mrp_set_console_prompt((mrp_console_t *)c);
+    }
+    else {
+    fail:
+        if (c != NULL) {
+            if (c->stdout != NULL)
+                fclose(c->stdout);
+            if (c->stderr != NULL)
+                fclose(c->stderr);
+            mrp_free(c);
+            c = NULL;
+        }
+    }
+
+    return (mrp_console_t *)c;
+}
+
+
+static int purge_destroyed(mrp_console_t *mc)
+{
+    console_t *c = (console_t *)mc;
+
+    if (c->destroyed && !c->busy) {
+        mrp_debug("Purging destroyed console %p...", c);
+
+        mrp_list_delete(&c->hook);
+
+        fclose(c->stdout);
+        fclose(c->stderr);
+
+        mrp_del_io_watch(c->wout);
+        c->wout = NULL;
+        console_release_output(c);
+        close(c->pout[0]);
+        close(c->pout[1]);
+
+        c->req.free(c->backend_data);
+        mrp_free(c);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+void mrp_destroy_console(mrp_console_t *mc)
+{
+    if (mc != NULL && !mc->destroyed) {
+        if (mc->stdout != NULL)
+            fflush(mc->stdout);
+        if (mc->stderr != NULL)
+            fflush(mc->stderr);
+
+        if (!mc->preserve)            /* the Kludge of Death... */
+            mc->destroyed = TRUE;
+
+        if (mc->backend_data != NULL) {
+            MRP_CONSOLE_BUSY(mc, {
+                    mc->req.close(mc);
+                });
+        }
+
+        purge_destroyed(mc);
+    }
+}
+
+
+static int check_destroy(mrp_console_t *c)
+{
+    return purge_destroyed(c);
+}
+
+
+void mrp_console_printf(mrp_console_t *mc, const char *fmt, ...)
+{
+    console_t *c = (console_t *)mc;
+    va_list    ap;
+
+    va_start(ap, fmt);
+    vfprintf(c->stdout, fmt, ap);
+    va_end(ap);
+
+    fflush(c->stdout);
+}
+
+
+void mrp_console_vprintf(mrp_console_t *mc, const char *fmt, va_list ap)
+{
+    console_t *c = (console_t *)mc;
+    va_list    cp;
+
+    va_copy(cp, ap);
+    vfprintf(c->stdout, fmt, cp);
+    va_end(cp);
+
+    fflush(c->stdout);
+}
+
+
+void mrp_set_console_prompt(mrp_console_t *mc)
+{
+    console_t *c = (console_t *)mc;
+    char      *prompt, buf[MAX_PROMPT];
+
+    if (c->destroyed)
+        return;
+
+    if (c->grp != NULL) {
+        prompt = buf;
+
+        if (c->cmd != NULL)
+            snprintf(buf, sizeof(buf), "murphy %s/%s",
+                     c->grp->name, c->cmd->name);
+        else
+            snprintf(buf, sizeof(buf), "murphy %s", c->grp->name);
+    }
+    else
+        prompt = "murphy";
+
+    if (strcmp(prompt, c->prompt)) {
+        strcpy(c->prompt, prompt);
+        c->req.set_prompt(mc, prompt);
+    }
+}
+
+
+static mrp_console_group_t *find_group(mrp_context_t *ctx, const char *name)
+{
+    mrp_list_hook_t     *p, *n;
+    mrp_console_group_t *grp;
+
+    if (*name == '/') {
+        name++;
+
+        if (!*name)
+            return NULL;
+    }
+
+    if (ctx != NULL) {
+        mrp_list_foreach(&ctx->cmd_groups, p, n) {
+            grp = mrp_list_entry(p, typeof(*grp), hook);
+            if (!strcmp(grp->name, name))
+                return grp;
+        }
+    }
+
+    mrp_list_foreach(&core_groups, p, n) {
+        grp = mrp_list_entry(p, typeof(*grp), hook);
+        if (!strcmp(grp->name, name))
+            return grp;
+    }
+
+    return NULL;
+}
+
+
+static mrp_console_cmd_t *find_command(mrp_console_group_t *group,
+                                       const char *command, int *fallback)
+{
+    mrp_console_cmd_t *any = NULL;
+    mrp_console_cmd_t *cmd;
+    int                i;
+
+    if (fallback != NULL)
+        *fallback = FALSE;
+
+    if (group != NULL) {
+        for (i = 0, cmd = group->commands; i < group->ncommand; i++, cmd++) {
+            if (!strcmp(cmd->name, command))
+                return cmd;
+            if (cmd->flags & MRP_CONSOLE_CATCHALL) {
+                any = cmd;
+                if (fallback != NULL)
+                    *fallback = TRUE;
+            }
+        }
+    }
+
+    return any;
+}
+
+
+int mrp_console_add_group(mrp_context_t *ctx, mrp_console_group_t *group)
+{
+    mrp_console_cmd_t *cmd, *catchall;
+    int                i;
+
+    if (group != NULL && find_group(ctx, group->name) == NULL) {
+        mrp_list_append(&ctx->cmd_groups, &group->hook);
+
+        catchall = NULL;
+        for (i = 0, cmd = group->commands; i < group->ncommand; i++, cmd++) {
+            if (cmd->flags & MRP_CONSOLE_CATCHALL) {
+                if (catchall == NULL)
+                    catchall = cmd;
+                else
+                    mrp_log_warning("Console group '%s' has multiple "
+                                    "catch-all commands: (%s, %s).",
+                                    group->name, catchall->name, cmd->name);
+            }
+        }
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_console_del_group(mrp_context_t *ctx, mrp_console_group_t *group)
+{
+    if (group != NULL && find_group(ctx, group->name) == group) {
+        mrp_list_delete(&group->hook);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_console_add_core_group(mrp_console_group_t *group)
+{
+    mrp_console_cmd_t *cmd, *catchall;
+    int                i;
+
+    if (group != NULL && find_group(NULL, group->name) == NULL) {
+        mrp_list_append(&core_groups, &group->hook);
+
+        catchall = NULL;
+        for (i = 0, cmd = group->commands; i < group->ncommand; i++, cmd++) {
+            if (cmd->flags & MRP_CONSOLE_CATCHALL) {
+                if (catchall == NULL)
+                    catchall = cmd;
+                else
+                    mrp_log_warning("Console group '%s' has multiple "
+                                    "catch-all commands: (%s, %s).",
+                                    group->name, catchall->name, cmd->name);
+            }
+        }
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_console_del_core_group(mrp_console_group_t *group)
+{
+    if (group != NULL && find_group(NULL, group->name) == group) {
+        mrp_list_delete(&group->hook);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static void console_grab_output(console_t *c)
+{
+    int ofd = fileno(stdout);
+    int efd = fileno(stderr);
+    int blk;
+
+    if (c->ofd == -1 && c->pout[RFD] != -1) {
+        blk = fcntl(ofd, F_GETFL, 0);
+        c->oblk = (blk > 0 && (blk & O_NONBLOCK));
+        blk = fcntl(efd, F_GETFL, 0);
+        c->eblk = (blk > 0 && (blk & O_NONBLOCK));
+
+        c->ofd = dup(ofd);
+        dup2(c->pout[WFD], ofd);
+        fcntl(c->pout[RFD], F_SETFL, O_NONBLOCK);
+
+        c->efd = dup(efd);
+        dup2(c->pout[WFD], efd);
+        fcntl(c->pout[WFD], F_SETFL, O_NONBLOCK);
+    }
+}
+
+
+static void console_release_output(console_t *c)
+{
+    int ofd = fileno(stdout);
+    int efd = fileno(stderr);
+
+    if (c->ofd >= 0) {
+        dup2(c->ofd, ofd);
+        c->ofd = -1;
+        fcntl(ofd, F_SETFL, c->oblk);
+    }
+
+    if (c->efd >= 0) {
+        dup2(c->efd, efd);
+        c->efd = -1;
+        fcntl(efd, F_SETFL, c->eblk);
+    }
+}
+
+
+static int console_read_output(console_t *c, void *buf, size_t size)
+{
+    return read(c->pout[RFD], buf, size);
+}
+
+
+static void console_flush_output(console_t *c, int copy_orig)
+{
+    char data[1024];
+    int  size;
+
+    fflush(stdout);
+    fflush(stderr);
+
+    while ((size = console_read_output(c, data, sizeof(data))) > 0) {
+        if (copy_orig && c->ofd >= 0)
+            dprintf(c->ofd, "%*.*s", size, size, data);
+        mrp_console_printf((mrp_console_t *)c, "%*.*s", size, size, data);
+    }
+}
+
+
+static char *raw_argument(char *raw, const char *grp, const char *cmd)
+{
+#define SKIP_WHITESPACE(_p)            \
+    while (*_p == ' ' || *_p == '\t')  \
+        _p++
+
+#define SKIP_PREFIX(_p, _prfx) do {                                       \
+        int _l = strlen(_prfx);                                           \
+                                                                          \
+        if (!strncmp(_p, _prfx, _l) && (_p[_l] == ' ' || _p[_l] == '\t')) \
+            _p += _l;                                                     \
+    } while (0)
+
+    while (*raw == '/')
+        raw++;
+
+    SKIP_WHITESPACE(raw);
+    SKIP_PREFIX(raw, grp);
+    SKIP_WHITESPACE(raw);
+    SKIP_PREFIX(raw, cmd);
+
+    return raw;
+
+#undef SKIP_WHITESPACE
+#undef SKIP_PREFIX
+}
+
+
+static ssize_t input_evt(mrp_console_t *mc, void *buf, size_t size)
+{
+    console_t           *c = (console_t *)mc;
+    mrp_console_group_t *grp;
+    mrp_console_cmd_t   *cmd;
+    char                *args[MRP_CFG_MAXARGS];
+    int                  argc;
+    char               **argv, *raw;
+    int                  len, fallback;
+
+    /*
+     * parse the given command to tokens
+     */
+
+    len = size;
+    strncpy(c->in.buf, buf, len);
+    c->in.buf[len++] = '\n';
+    c->in.buf[len]   = '\0';
+
+    c->in.token = c->in.buf;
+    c->in.out   = c->in.buf;
+    c->in.next  = c->in.buf;
+    c->in.in    = c->in.buf + len;
+    c->in.line  = 1;
+    c->in.error = 0;
+    *c->in.in   = '\0';
+
+    argv = args + 2;
+    argc = get_next_line(&c->in, argv, MRP_ARRAY_SIZE(args) - 2);
+    grp  = c->grp;
+    cmd  = NULL;
+
+    /*
+     * Notes: Uhmmkay... so this will need to get replaced eventually with
+     *        decent input processing.
+     */
+
+    if (argc < 0) {
+        fprintf(c->stderr, "failed to parse command: '%.*s'\n",
+                (int)size, (char *)buf);
+        return -1;
+    }
+    else if (argc == 0)
+        goto prompt;
+
+
+    /*
+     * take care of common top-level commands (exit, help)
+     */
+
+    grp = find_group(c->ctx, "");
+    cmd = find_command(grp, argv[0], NULL);
+
+    if (cmd != NULL) {
+        argv[-1] = "";
+        argv--;
+        argc++;
+        goto execute;
+    }
+
+
+    /*
+     * take care of group and command mode selection
+     */
+
+    if (argc == 1) {
+        if (c->grp == NULL) {
+            c->grp = find_group(c->ctx, argv[0]);
+
+            if (c->grp != NULL)
+                goto prompt;
+        }
+        else {
+            if (argv[0][0] == '/') {
+                grp = find_group(c->ctx, argv[0]);
+
+                if (grp != NULL) {
+                    c->grp = grp;
+                    goto prompt;
+                }
+                else if (argv[0][1] == '\0') {
+                    c->grp = NULL;
+                    c->cmd = NULL;
+                    goto prompt;
+                }
+                else
+                    goto unknown_command;
+            }
+
+            if (c->cmd == NULL) {
+                cmd = find_command(c->grp, argv[0], &fallback);
+
+                if (cmd != NULL &&
+                    (cmd->flags & MRP_CONSOLE_SELECTABLE) && !fallback) {
+                    c->cmd = cmd;
+                    goto prompt;
+                }
+            }
+        }
+    }
+
+
+    /*
+     * take care of commands while in group or command mode
+     */
+
+    if (c->grp != NULL && *argv[0] != '/') {
+        if (c->cmd != NULL) {
+            grp = c->grp;
+            cmd = c->cmd;
+            argv[-2] = grp->name;
+            argv[-1] = (char *)cmd->name;
+            argv -= 2;
+            argc += 2;
+        }
+        else {
+            grp = c->grp;
+            cmd = find_command(grp, argv[0], NULL);
+
+            if (cmd == NULL)
+                goto unknown_command;
+
+            argv[-1] = grp->name;
+            argv--;
+            argc++;
+        }
+
+        goto execute;
+    }
+
+    /*
+     * take care of commands while at the top-level
+     */
+
+    if (argc > 1) {
+        grp = find_group(c->ctx, argv[0]);
+        cmd = find_command(grp, argv[1], NULL);
+    }
+
+ execute:
+    if (cmd != NULL) {
+        console_grab_output(c);
+
+        clearerr(stdout);
+        clearerr(stderr);
+
+        MRP_CONSOLE_BUSY(mc, {
+                if (cmd->flags & MRP_CONSOLE_RAWINPUT) {
+                    raw = raw_argument(buf, grp->name, cmd->name);
+                    cmd->raw(mc, grp->user_data, grp->name, cmd->name, raw);
+                }
+                else
+                    cmd->tok(mc, grp->user_data, argc, argv);
+            });
+
+        /*
+         * Although our watch for c->pout[RFD]/output_cb should take
+         * care of flushing any output over to the console, since we
+         * know there is very probably pending output we might as well
+         * take care of proxying it right away...
+         */
+        console_flush_output(c, TRUE);
+
+        console_release_output(c);
+    }
+    else {
+    unknown_command:
+        fprintf(mc->stderr, "invalid command '%.*s'\n", (int)size, (char *)buf);
+    }
+
+ prompt:
+    if (mc->check_destroy(mc))
+        return size;
+
+    fflush(mc->stdout);
+    fflush(mc->stderr);
+
+    mrp_set_console_prompt(mc);
+
+    return size;
+}
+
+
+static void disconnected_evt(mrp_console_t *c, int error)
+{
+    mrp_log_info("Console %p has been disconnected (error: %d).", c, error);
+}
+
+
+static ssize_t complete_evt(mrp_console_t *c, void *input, size_t isize,
+                             char **completions, size_t csize)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(input);
+    MRP_UNUSED(isize);
+    MRP_UNUSED(completions);
+    MRP_UNUSED(csize);
+
+    return 0;
+}
+
+
+/*
+ * stream-based console I/O
+ */
+
+static ssize_t cookie_write(void *cptr, const char *buf, size_t size)
+{
+    console_t *c = (console_t *)cptr;
+    ssize_t    ssize;
+
+    if (c->destroyed)
+        return size;
+
+    MRP_CONSOLE_BUSY(c, {
+            ssize = c->req.write((mrp_console_t *)c, (char *)buf, size);
+        });
+
+    return ssize;
+}
+
+
+static int cookie_close(void *cptr)
+{
+    MRP_UNUSED(cptr);
+
+    return 0;
+}
+
+
+static FILE *console_fopen(mrp_console_t *mc)
+{
+    static cookie_io_functions_t io_func = {
+        .read  = NULL,
+        .write = cookie_write,
+        .seek  = NULL,
+        .close = cookie_close
+    };
+
+    return fopencookie((void *)mc, "w", io_func);
+}
+
+
+/*
+ * builtin console commands
+ */
+
+#include "console-command.c"
+
+static void register_commands(mrp_context_t *ctx)
+{
+    mrp_console_add_group(ctx, &builtin_cmd_group);
+}
+
+
+static void unregister_commands(mrp_context_t *ctx)
+{
+    mrp_console_del_group(ctx, &builtin_cmd_group);
+}
+
+
+/*
+ * XXX TODO Verbatim copy of config.c tokenizer. Separate this out
+ *          to common (maybe common/text-utils.c), generalize and
+ *          clean it up.
+ */
+
+#define MRP_START_COMMENT   '#'
+
+static char *get_next_token(input_t *in);
+
+static int get_next_line(input_t *in, char **args, size_t size)
+{
+    char *token;
+    int   narg;
+
+    narg = 0;
+    while ((token = get_next_token(in)) != NULL && narg < (int)size) {
+        if (in->error)
+            return -1;
+
+        if (token[0] != '\n')
+            args[narg++] = token;
+        else {
+            if (*args[0] != MRP_START_COMMENT && narg && *args[0] != '\n')
+                return narg;
+            else
+                narg = 0;
+        }
+    }
+
+    if (in->error)
+        return -1;
+
+    if (narg >= (int)size) {
+        mrp_log_error("Too many tokens on line %d of %s.",
+                      in->line - 1, in->file);
+        return -1;
+    }
+    else {
+        if (*args[0] != MRP_START_COMMENT && *args[0] != '\n')
+            return narg;
+        else
+            return 0;
+    }
+}
+
+
+static inline void skip_whitespace(input_t *in)
+{
+    while ((*in->out == ' ' || *in->out == '\t') && in->out < in->in)
+        in->out++;
+}
+
+
+static char *get_next_token(input_t *in)
+{
+    int   diff, size;
+    int   quote, quote_line;
+    char *p, *q;
+
+    /*
+     * Newline:
+     *
+     *     If the previous token was terminated by a newline,
+     *     take care of properly returning and administering
+     *     the newline token here.
+     */
+
+    if (in->next_newline) {
+        in->next_newline = FALSE;
+        in->was_newline  = TRUE;
+        in->line++;
+
+        return "\n";
+    }
+
+
+    /*
+     * if we just finished a line, discard all old data/tokens
+     */
+
+    if (*in->token == '\n' || in->was_newline) {
+        diff = in->out - in->buf;
+        size = in->in - in->out;
+        memmove(in->buf, in->out, size);
+        in->out  -= diff;
+        in->in   -= diff;
+        in->next  = in->buf;
+        *in->in   = '\0';
+    }
+
+    if (in->out >= in->in)
+        return NULL;
+
+    skip_whitespace(in);
+
+    quote = FALSE;
+    quote_line = 0;
+
+    p = in->out;
+    q = in->next;
+    in->token = q;
+
+    while (p < in->in) {
+        /*printf("[%c]\n", *p == '\n' ? '.' : *p);*/
+        switch (*p) {
+            /*
+             * Quoting:
+             *
+             *     If we're not within a quote, mark a quote started.
+             *     Otherwise if quote matches, close quoting. Otherwise
+             *     copy the quoted quote verbatim.
+             */
+        case '\'':
+        case '\"':
+            if (!quote) {
+                quote      = *p++;
+                quote_line = in->line;
+            }
+            else {
+                if (*p == quote) {
+                    quote      = FALSE;
+                    quote_line = 0;
+                    p++;
+                }
+                else {
+                    *q++ = *p++;
+                }
+            }
+            in->was_newline = FALSE;
+            break;
+
+            /*
+             * Whitespace:
+             *
+             *     If we're quoting, copy verbatim. Otherwise mark the end
+             *     of the token.
+             */
+        case ' ':
+        case '\t':
+            if (quote)
+                *q++ = *p++;
+            else {
+                p++;
+                *q++ = '\0';
+
+                in->out  = p;
+                in->next = q;
+
+                return in->token;
+            }
+            in->was_newline = FALSE;
+            break;
+
+            /*
+             * Escaping:
+             *
+             *     If the last character in the input, copy verbatim.
+             *     Otherwise if it escapes a '\n', skip both. Otherwise
+             *     copy the escaped character verbatim.
+             */
+        case '\\':
+            if (p < in->in - 1) {
+                p++;
+                if (*p != '\n')
+                    *q++ = *p++;
+                else {
+                    p++;
+                    in->line++;
+                    in->out = p;
+                    skip_whitespace(in);
+                    p = in->out;
+                }
+            }
+            else
+                *q++ = *p++;
+            in->was_newline = FALSE;
+            break;
+
+            /*
+             * Newline:
+             *
+             *     We don't allow newlines to be quoted. Otherwise
+             *     if the token is not the newline itself, we mark
+             *     the next token to be newline and return the token
+             *     it terminated.
+             */
+        case '\n':
+            if (quote) {
+                mrp_log_error("%s:%d: Unterminated quote (%c) started "
+                              "on line %d.", in->file, in->line, quote,
+                              quote_line);
+                in->error = TRUE;
+
+                return NULL;
+            }
+            else {
+                *q = '\0';
+                p++;
+
+                in->out  = p;
+                in->next = q;
+
+                if (in->token == q) {
+                    in->line++;
+                    in->was_newline = TRUE;
+                    return "\n";
+                }
+                else {
+                    in->next_newline = TRUE;
+                    return in->token;
+                }
+            }
+            break;
+
+            /*
+             * CR: just ignore it
+             */
+        case '\r':
+            p++;
+            break;
+
+        default:
+            *q++ = *p++;
+            in->was_newline = FALSE;
+        }
+    }
+
+    *q = '\0';
+    in->out = p;
+    in->in = q;
+
+    return in->token;
+}
diff --git a/src/core/console.h b/src/core/console.h
new file mode 100644 (file)
index 0000000..8cf9243
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CONSOLE_H__
+#define __MURPHY_CONSOLE_H__
+
+#include <murphy/common/list.h>
+#include <murphy/common/msg.h>
+#include <murphy/core/context.h>
+
+#include <murphy/core/console-command.h>
+
+
+
+/*
+ * console requests
+ *
+ * Console request correspond to top-down event propagation in the console
+ * communication stack. These requests are made by the core console to the
+ * underlying actual console implementation, typically either as a result
+ * of calls to the console abstraction layer, or in reponse to requests
+ * (ie. input) coming from the actual console implementation.
+ */
+
+typedef struct {
+    /** Deliver a buffer of data to the given console. */
+    ssize_t (*write)(mrp_console_t *c, void *buf, size_t size);
+    /** Console being closed, close the backend (do not release memory yet). */
+    void (*close)(mrp_console_t *c);
+    /** Console has been destroyed, release resources allocated by backend. */
+    void (*free)(void *data);
+    /** Set the prompt shown to the user at the console. */
+    void (*set_prompt)(mrp_console_t *c, const char *prompt);
+} mrp_console_req_t;
+
+
+/*
+ * console events
+ *
+ * Console events correspond to bottom-up event propagation in the console
+ * communication stack. These callbacks are made by the console backend to
+ * the core console to inform about relevant console events, such as new
+ * console input or disconnect by the peer.
+ */
+
+typedef struct {
+    /** New input available from console. */
+    ssize_t (*input)(mrp_console_t *c, void *buf, size_t size);
+    /** Peer has disconnected from the console. */
+    void (*disconnected)(mrp_console_t *c, int error);
+    /** Generate possible completions for the given input. */
+    ssize_t (*complete)(mrp_console_t *c, void *input, size_t insize,
+                        char **completions, size_t csize);
+} mrp_console_evt_t;
+
+
+#define MRP_CONSOLE_PUBLIC_FIELDS                                         \
+    mrp_context_t         *ctx;                                           \
+    mrp_console_req_t      req;                                           \
+    mrp_console_evt_t      evt;                                           \
+    int                  (*check_destroy)(mrp_console_t *c);              \
+    FILE                  *stdout;                                        \
+    FILE                  *stderr;                                        \
+    void                  *backend_data;                                  \
+    int                    busy;                                          \
+    int                    destroyed : 1;                                 \
+    int                    preserve : 1 /* the Kludge of Death, Sir Robin... */
+
+struct mrp_console_s {
+    MRP_CONSOLE_PUBLIC_FIELDS;
+};
+
+
+/**
+ * Macro to mark a console busy while running a block of code.
+ *
+ * The backend needs to make sure the console is not freed while any console
+ * request or event callback function is active. Similarly, the backend needs
+ * to check if the console has been marked for destruction whenever an event
+ * callback returns and trigger destruction if it is necessary and possible
+ * (ie. the above criterium of not being active is fullfilled).
+ *
+ * These are the easiest to accomplish using the provided MRP_CONSOLE_BUSY
+ * macro and the check_destroy callback member provided by mrp_console_t.
+ *
+ *     1) Use the provided MRP_CONSOLE_BUSY macro to enclose al blocks of
+ *        code that invoke event callbacks. Do not do a return directly
+ *        from within the enclosed call blocks, rather just set a flag
+ *        within the block, check it after the block and do the return
+ *        there if necessary.
+ *
+ *     2) Call mrp_console_t->check_destroy after any call to an console
+ *        event callback. check_destroy will check for any pending destroy
+ *        request and perform the actual destruction if it is both necessary
+ *        and possible. If the console has been left intact, check_destroy
+ *        returns FALSE. However, if the console has been destroyed and freed
+ *        it returns TRUE, in which case the caller must not attempt to use
+ *        or dereference the console any more.
+ */
+
+#ifndef __MRP_CONSOLE_DISABLE_CODE_CHECK__
+#  define W mrp_log_error
+#  define __CONSOLE_CHK_BLOCK(...) do {                                   \
+        static int __checked = FALSE, __warned = FALSE;                   \
+                                                                          \
+        if (MRP_UNLIKELY(!__checked)) {                                   \
+            __checked = TRUE;                                             \
+            if (MRP_UNLIKELY(!__warned &&                                 \
+                             strstr(#__VA_ARGS__, "return") != NULL)) {   \
+                W("********************* WARNING *********************"); \
+                W("* You seem to directly do a return from a block   *"); \
+                W("* of code protected by MRP_CONSOLE_BUSY. Are      *"); \
+                W("* you absolutely sure you know what you are doing *"); \
+                W("* and that you are also doing it correctly ?      *"); \
+                W("***************************************************"); \
+                W("The suspicious code block is located at: ");           \
+                W("  %s@%s:%d", __FUNCTION__, __FILE__, __LINE__);        \
+                W("and it looks like this:");                             \
+                W("---------------------------------------------");       \
+                W("%s", #__VA_ARGS__);                                    \
+                W("---------------------------------------------");       \
+                W("If you understand what MRP_CONSOLE_BUSY does");        \
+                W("and how, and you are sure about the correctness of");  \
+                W("your code you can disable this error message by");     \
+                W("#defining __MRP_CONSOLE_DISABLE_CODE_CHECK__");        \
+                W("when compiling %s.", __FILE__);                        \
+                __warned = TRUE;                                          \
+            }                                                             \
+        }                                                                 \
+    } while (0)
+#else
+#  define __CONSOLE_CHK_BLOCK(...) do { } while (0)
+#endif
+
+#define MRP_CONSOLE_BUSY(c, ...) do {           \
+       __CONSOLE_CHK_BLOCK(__VA_ARGS__);        \
+        (c)->busy++;                            \
+        __VA_ARGS__                             \
+        (c)->busy--;                            \
+    } while (0)
+
+
+/** Create a new console instance. */
+mrp_console_t *mrp_create_console(mrp_context_t *ctx, mrp_console_req_t *req,
+                                  void *backend_data);
+
+/** Close and mark a console for destruction. */
+void mrp_destroy_console(mrp_console_t *mc);
+
+/** Send (printf-compatible) formatted output to a console. */
+void mrp_console_printf(mrp_console_t *mc, const char *fmt, ...);
+
+/** Send (vprintf-compatible) formatted output to a console. */
+void mrp_console_vprintf(mrp_console_t *mc, const char *fmt, va_list ap);
+
+/** Set the prompt of a console. */
+void mrp_set_console_prompt(mrp_console_t *mc);
+
+#endif /* __MURPHY_CONSOLE_H__ */
diff --git a/src/core/context.c b/src/core/context.c
new file mode 100644 (file)
index 0000000..fd01cce
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/core/context.h>
+#include <murphy/core/console-priv.h>
+#include <murphy/core/domain.h>
+
+mrp_context_t *mrp_context_create(void)
+{
+    mrp_context_t *c;
+
+    if ((c = mrp_allocz(sizeof(*c))) != NULL) {
+        mrp_list_init(&c->plugins);
+        console_setup(c);
+        domain_setup(c);
+
+        mrp_list_init(&c->auth);
+
+
+        if ((c->ml = mrp_mainloop_create()) == NULL) {
+            mrp_log_error("Failed to create mainloop.");
+            mrp_free(c);
+            c = NULL;
+        }
+    }
+
+    return c;
+}
+
+
+void mrp_context_destroy(mrp_context_t *c)
+{
+    if (c != NULL) {
+        console_cleanup(c);
+        mrp_mainloop_destroy(c->ml);
+        mrp_free(c);
+    }
+
+
+}
+
+
+void mrp_context_setstate(mrp_context_t *c, mrp_context_state_t state)
+{
+    c->state = state;
+}
diff --git a/src/core/context.h b/src/core/context.h
new file mode 100644 (file)
index 0000000..97725f0
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CONTEXT_H__
+#define __MURPHY_CONTEXT_H__
+
+#include <stdbool.h>
+
+typedef struct mrp_context_s mrp_context_t;
+
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/resolver/resolver.h>
+
+
+typedef enum {
+    MRP_STATE_INITIAL = 0,
+    MRP_STATE_LOADING,
+    MRP_STATE_STARTING,
+    MRP_STATE_RUNNING,
+    MRP_STATE_STOPPING
+} mrp_context_state_t;
+
+
+struct mrp_context_s {
+    /* logging settings, path configuration, etc. */
+    int         log_mask;                  /* what to log */
+    const char *log_target;                /* and where to log to */
+
+    const char *config_file;               /* configuration file */
+    const char *config_dir;                /* plugin configuration directory */
+    const char *plugin_dir;                /* plugin directory */
+    bool        foreground;                /* whether to stay in foreground*/
+
+    char       *resolver_ruleset;          /* resolver ruleset file */
+
+    const char *blacklist_plugins;         /* blacklisted plugins */
+    const char *blacklist_builtin;         /* blacklisted builtin plugins */
+    const char *blacklist_dynamic;         /* blacklisted dynamic plugins */
+    const char *whitelist_plugins;         /* whitelisted plugins */
+    const char *whitelist_builtin;         /* whitelisted builtin plugins */
+    const char *whitelist_dynamic;         /* whitelisted dynamic plugins */
+    bool        disable_runtime_load;      /* disallow post-startup loading */
+    bool        disable_console;           /* disable murphy console */
+
+    /* actual runtime context data */
+    int              state;                /* context/daemon state */
+    mrp_mainloop_t  *ml;                   /* mainloop */
+    mrp_list_hook_t  plugins;              /* list of loaded plugins */
+    mrp_event_bus_t *plugin_bus;           /* bus for plugin events */
+    mrp_event_bus_t *daemon_bus;           /* bus for daemon events */
+    mrp_list_hook_t  cmd_groups;           /* console command groups */
+    mrp_list_hook_t  consoles;             /* active consoles */
+    mrp_resolver_t  *r;                    /* resolver context */
+    void            *lua_state;            /* state for Lua bindings */
+    mrp_list_hook_t  auth;                 /* authenticator backends */
+
+    /*
+     * Hmm, this is not very nice.  Most of the domain handling code (in
+     * practice all) used to live in the domain-control plugin. To avoid
+     * loading order dependencies on plugin-domain-control we now started
+     * collecting registered handlers of proxied functions here. Calls by
+     * the core to proxied functions of domain controllers and by domain-
+     * controllers to the core are still handled in the domain-control
+     * plugin (and in the domain-controller client library).
+     *
+     * It would be perhaps the cleanest not to have a domain-controller
+     * specific function export mechanism at all. Instead the various
+     * import/export mechanisms (at least plugins, resolver, and this) should
+     * be replaced by / built on a single core implementation that is flexible
+     * enough to handle all the needs of all these.
+     */
+
+    mrp_list_hook_t  domain_methods;       /* functions for domain controllers */
+    void            *domain_invoke;        /* domain invoke handler */
+    void            *domain_data;          /* domain invoke handler data */
+};
+
+/** Create a new murphy context. */
+mrp_context_t *mrp_context_create(void);
+
+/** Destroy an existing murphy context. */
+void mrp_context_destroy(mrp_context_t *c);
+
+/** Set the context state to the given state. */
+void mrp_context_setstate(mrp_context_t *c, mrp_context_state_t state);
+
+#endif /* __MURPHY_CONTEXT_H__ */
diff --git a/src/core/domain-types.h b/src/core/domain-types.h
new file mode 100644 (file)
index 0000000..5064078
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CORE_DOMAIN_TYPES_H__
+#define __MURPHY_CORE_DOMAIN_TYPES_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/msg.h>
+
+MRP_CDECL_BEGIN
+
+
+/*
+ * passable data types to/from domain controllers
+ */
+typedef enum {
+    MRP_DOMCTL_END      = MRP_MSG_FIELD_INVALID,
+    MRP_DOMCTL_STRING   = MRP_MSG_FIELD_STRING,
+    MRP_DOMCTL_INTEGER  = MRP_MSG_FIELD_INT32,
+    MRP_DOMCTL_UNSIGNED = MRP_MSG_FIELD_UINT32,
+    MRP_DOMCTL_DOUBLE   = MRP_MSG_FIELD_DOUBLE,
+    MRP_DOMCTL_BOOL     = MRP_MSG_FIELD_BOOL,
+    MRP_DOMCTL_UINT8    = MRP_MSG_FIELD_UINT8,
+    MRP_DOMCTL_INT8     = MRP_MSG_FIELD_INT8,
+    MRP_DOMCTL_UINT16   = MRP_MSG_FIELD_UINT16,
+    MRP_DOMCTL_INT16    = MRP_MSG_FIELD_INT16,
+    MRP_DOMCTL_UINT32   = MRP_MSG_FIELD_UINT32,
+    MRP_DOMCTL_INT32    = MRP_MSG_FIELD_INT32,
+    MRP_DOMCTL_UINT64   = MRP_MSG_FIELD_UINT64,
+    MRP_DOMCTL_INT64    = MRP_MSG_FIELD_INT64,
+
+#define MRP_DOMCTL_ARRAY(_type)      MRP_MSG_FIELD_ARRAY_OF(_type)
+#define MRP_DOMCTL_IS_ARRAY(_type)   MRP_MSG_FIELD_IS_ARRAY(_type)
+#define MRP_DOMCTL_ARRAY_TYPE(_type) MRP_MSG_FIELD_ARRAY_TYPE(_type)
+} mrp_domctl_type_t;
+
+
+/*
+ * a single data value passed to/from a domain controller
+ */
+
+typedef struct {
+    mrp_domctl_type_t  type;             /* data type */
+    union {
+        /* these are usable both in DB operations and proxied invocations */
+        const char *str;                 /* MRP_DOMCTL_STRING */
+        uint32_t    u32;                 /* MRP_DOMCTL_{UNSIGNED,UINT32} */
+        int32_t     s32;                 /* MRP_DOMCTL_{INTEGER,INT32} */
+        double      dbl;                 /* MRP_DOMCTL_DOUBLE */
+        /* these are only usable in proxied invocations */
+        int         bln;                 /* MRP_DOMCTL_BOOL */
+        uint8_t     u8;                  /* MRP_DOMCTL_UINT8 */
+        int8_t      s8;                  /* MRP_DOMCTL_INT8 */
+        uint16_t    u16;                 /* MRP_DOMCTL_UINT16 */
+        int16_t     s16;                 /* MRP_DOMCTL_INT16 */
+        uint64_t    u64;                 /* MRP_DOMCTL_UINT64 */
+        int64_t     s64;                 /* MRP_DOMCTL_INT64 */
+        void       *arr;                 /* MRP_DOMCTL_ARRAY(*) */
+    };
+    uint32_t           size;             /* size for arrays */
+} mrp_domctl_value_t;
+
+
+/*
+ * proxied invokation errors
+ */
+
+typedef enum {
+    MRP_DOMAIN_OK = 0,                   /* no errors */
+    MRP_DOMAIN_NOTFOUND,                 /* domain not found */
+    MRP_DOMAIN_NOMETHOD,                 /* call domain method not found */
+    MRP_DOMAIN_FAILED,                   /* called method remotely failed */
+} mrp_domain_error_t;
+
+/* Type for a proxied invocation argument. */
+typedef mrp_domctl_value_t mrp_domctl_arg_t;
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_CORE_DOMAIN_TYPES_H__ */
diff --git a/src/core/domain.c b/src/core/domain.c
new file mode 100644 (file)
index 0000000..75df352
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+
+#include <murphy/core/context.h>
+#include <murphy/core/domain.h>
+
+typedef struct {
+    mrp_list_hook_t         hook;        /* to list of registered methods */
+    char                   *name;        /* method name */
+    int                     max_out;     /* max returned arguments */
+    mrp_domain_invoke_cb_t  cb;          /* actual callback */
+    void                   *user_data;   /* callback user data */
+} method_t;
+
+
+void domain_setup(mrp_context_t *ctx)
+{
+    mrp_list_init(&ctx->domain_methods);
+}
+
+
+int mrp_set_domain_invoke_handler(mrp_context_t *ctx,
+                                  mrp_domain_invoke_handler_t handler,
+                                  void *handler_data)
+{
+    if (ctx->domain_invoke != NULL)
+        return FALSE;
+
+    ctx->domain_invoke = handler;
+    ctx->domain_data   = handler_data;
+
+    return TRUE;
+}
+
+
+int mrp_register_domain_methods(mrp_context_t *ctx,
+                                mrp_domain_method_def_t *defs, size_t ndef)
+{
+    mrp_domain_method_def_t *def;
+    method_t                *m;
+    size_t                   i;
+
+    for (i = 0, def = defs; i < ndef; i++, def++) {
+        m = mrp_allocz(sizeof(*m));
+
+        if (m == NULL)
+            return FALSE;
+
+        mrp_list_init(&m->hook);
+
+        m->name      = mrp_strdup(def->name);
+        m->max_out   = def->max_out;
+        m->cb        = def->cb;
+        m->user_data = def->user_data;
+
+        if (m->name == NULL) {
+            mrp_free(m);
+            return FALSE;
+        }
+
+        mrp_list_append(&ctx->domain_methods, &m->hook);
+    }
+
+    return TRUE;
+}
+
+
+static method_t *find_method(mrp_context_t *ctx, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    method_t        *m;
+
+    mrp_list_foreach(&ctx->domain_methods, p, n) {
+        m = mrp_list_entry(p, typeof(*m), hook);
+
+        if (!strcmp(m->name, name))
+            return m;
+    }
+
+    return NULL;
+}
+
+
+int mrp_lookup_domain_method(mrp_context_t *ctx, const char *name,
+                             mrp_domain_invoke_cb_t *cb, int *max_out,
+                             void **user_data)
+{
+    method_t *m;
+
+    m = find_method(ctx, name);
+
+    if (m == NULL)
+        return FALSE;
+
+    *cb        = m->cb;
+    *max_out   = m->max_out;
+    *user_data = m->user_data;
+
+    return TRUE;
+}
+
+
+int mrp_invoke_domain(mrp_context_t *ctx, const char *domain,
+                      const char *method, int narg, mrp_domctl_arg_t *args,
+                      mrp_domain_return_cb_t return_cb, void *user_data)
+{
+    mrp_domain_invoke_handler_t  handler      = ctx->domain_invoke;
+    void                        *handler_data = ctx->domain_data;
+
+    if (handler == NULL)
+        return FALSE;
+
+    return handler(handler_data, domain,
+                   method, narg, args, return_cb, user_data);
+}
diff --git a/src/core/domain.h b/src/core/domain.h
new file mode 100644 (file)
index 0000000..18358f6
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CORE_DOMAIN__
+#define __MURPHY_CORE_DOMAIN__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/msg.h>
+
+#include <murphy/core/context.h>
+#include <murphy/core/domain-types.h>
+
+MRP_CDECL_BEGIN
+
+/* Type for a proxied invocation handler. */
+typedef int (*mrp_domain_invoke_cb_t)(uint32_t narg, mrp_domctl_arg_t *args,
+                                      uint32_t *nout, mrp_domctl_arg_t *outs,
+                                      void *user_data);
+
+/* Type for a proxied invocation return/reply handler. */
+typedef void (*mrp_domain_return_cb_t)(int error, int retval, int narg,
+                                       mrp_domctl_arg_t *args, void *user_data);
+
+typedef struct {
+    char                   *name;        /* method name */
+    int                     max_out;     /* max. number of return arguments */
+    mrp_domain_invoke_cb_t  cb;          /* handler callback */
+    void                   *user_data;   /* opaque callback user data */
+} mrp_domain_method_def_t;
+
+
+
+/* Type for handling proxied invocation to domain controllers. */
+typedef int (*mrp_domain_invoke_handler_t)(void *handler_data, const char *id,
+                                           const char *method, int narg,
+                                           mrp_domctl_arg_t *args,
+                                           mrp_domain_return_cb_t return_cb,
+                                           void *user_data);
+
+/* Initialize domain-specific context parts. */
+void domain_setup(mrp_context_t *ctx);
+
+/* Register a domain method. */
+int mrp_register_domain_methods(mrp_context_t *ctx,
+                                mrp_domain_method_def_t *defs, size_t ndef);
+
+/* Find a registered domain method. */
+int mrp_lookup_domain_method(mrp_context_t *ctx, const char *method,
+                             mrp_domain_invoke_cb_t *cb, int *max_out,
+                             void **user_data);
+
+/* Invoke the named method of the specified domain. */
+int mrp_invoke_domain(mrp_context_t *ctx, const char *domain, const char *method,
+                      int narg, mrp_domctl_arg_t *args,
+                      mrp_domain_return_cb_t return_cb, void *user_data);
+
+/* Set the domain invoke handler. */
+int mrp_set_domain_invoke_handler(mrp_context_t *ctx,
+                                  mrp_domain_invoke_handler_t handler,
+                                  void *handler_data);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_CORE_DOMAIN__ */
diff --git a/src/core/event.c b/src/core/event.c
new file mode 100644 (file)
index 0000000..2e148b1
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdarg.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/msg.h>
+
+#include <murphy/core/event.h>
+
+
+/*
+ * an event definition
+ */
+
+typedef struct {
+    char            *name;                    /* event name */
+    int              id;                      /* associated event id */
+    mrp_list_hook_t  watches;                 /* single-event watches */
+} event_def_t;
+
+
+/*
+ * an event watch
+ */
+
+struct mrp_event_watch_s {
+    mrp_list_hook_t   hook;                   /* hook to event watch list */
+    mrp_list_hook_t   purge;                  /* hook to deleted event list */
+    mrp_event_cb_t    cb;                     /* notification callback */
+    mrp_event_mask_t  events;                 /* events to watch */
+    void             *user_data;              /* opaque callback data */
+};
+
+
+static event_def_t     events[MRP_EVENT_MAX]; /* event table */
+static int             nevent;                /* number of events */
+static int             nemit;                 /* events being emitted */
+static mrp_list_hook_t watches;               /* multi-event watches */
+static mrp_list_hook_t deleted;               /* events deleted during emit */
+
+
+static int single_event(mrp_event_mask_t *mask);
+
+
+MRP_INIT static void init_watch_lists(void)
+{
+    int i;
+
+    for (i = 0; i < MRP_EVENT_MAX; i++)
+        mrp_list_init(&events[i].watches);
+
+    mrp_list_init(&watches);
+    mrp_list_init(&deleted);
+}
+
+
+mrp_event_watch_t *mrp_add_event_watch(mrp_event_mask_t *mask,
+                                       mrp_event_cb_t cb, void *user_data)
+{
+    mrp_event_watch_t *w;
+    event_def_t       *def;
+    int                id;
+
+    if (cb != NULL) {
+        w = mrp_allocz(sizeof(*w));
+
+        if (w != NULL) {
+            mrp_list_init(&w->hook);
+            mrp_list_init(&w->purge);
+            w->cb        = cb;
+            w->user_data = user_data;
+            w->events    = *mask;
+
+            id = single_event(mask);
+
+            if (id != MRP_EVENT_UNKNOWN) {
+                if (MRP_EVENT_UNKNOWN < id && id <= nevent) {
+                    def = events + id;
+
+                    if (def->name != NULL)
+                        mrp_list_append(&def->watches, &w->hook);
+                    else {
+                        mrp_free(w);
+                        w = NULL;
+                    }
+                }
+                else {
+                    mrp_free(w);
+                    w = NULL;
+                }
+            }
+            else
+                mrp_list_append(&watches, &w->hook);
+        }
+    }
+    else
+        w = NULL;
+
+    return w;
+
+#if 0
+    if (MRP_EVENT_UNKNOWN < id && id <= nevent) {
+        def = events + id;
+
+        if (def->name != NULL && cb != NULL) {
+            w = mrp_allocz(sizeof(*w));
+
+            if (w != NULL) {
+                mrp_list_init(&w->hook);
+                mrp_list_init(&w->purge);
+                w->cb        = cb;
+                w->user_data = user_data;
+
+                if (single_event(mask)) {
+                    id = single_event(mask);
+
+                mrp_list_append(&def->watches, &w->hook);
+
+                return w;
+            }
+        }
+    }
+
+    return NULL;
+#endif
+}
+
+
+static void delete_watch(mrp_event_watch_t *w)
+{
+    mrp_list_delete(&w->hook);
+    mrp_list_delete(&w->purge);
+    mrp_free(w);
+}
+
+
+static void purge_deleted(void)
+{
+    mrp_event_watch_t *w;
+    mrp_list_hook_t   *p, *n;
+
+    mrp_list_foreach(&deleted, p, n) {
+        w = mrp_list_entry(p, typeof(*w), purge);
+        delete_watch(w);
+    }
+}
+
+
+void mrp_del_event_watch(mrp_event_watch_t *w)
+{
+    if (w != NULL) {
+        if (nemit > 0)
+            mrp_list_append(&deleted, &w->purge);
+        else
+            delete_watch(w);
+    }
+}
+
+
+int mrp_get_event_id(const char *name, int create)
+{
+    event_def_t *def;
+    int          i;
+
+    for (i = MRP_EVENT_UNKNOWN + 1, def = events + i; i <= nevent; i++, def++) {
+        if (!strcmp(name, def->name))
+            return i;
+    }
+
+    if (create) {
+        if (nevent < MRP_EVENT_MAX - 1) {
+            def       = events + 1 + nevent;
+            def->name = mrp_strdup(name);
+
+            if (def->name != NULL) {
+                mrp_list_init(&def->watches);
+                def->id = 1 + nevent++;
+
+                return def->id;
+            }
+        }
+    }
+
+    return MRP_EVENT_UNKNOWN;
+}
+
+
+const char *mrp_get_event_name(int id)
+{
+    event_def_t *def;
+
+    if (MRP_EVENT_UNKNOWN < id && id <= nevent) {
+        def = events + id;
+
+        return def->name;
+    }
+
+    return "<unknown event>";
+}
+
+
+int mrp_emit_event_msg(int id, mrp_msg_t *event_data)
+{
+    event_def_t       *def;
+    mrp_list_hook_t   *p, *n;
+    mrp_event_watch_t *w;
+
+    if (MRP_EVENT_UNKNOWN < id && id <= nevent) {
+        def = events + id;
+
+        if (def->name != NULL) {
+            nemit++;
+            mrp_msg_ref(event_data);
+
+            mrp_debug("emitting event 0x%x (%s)", def->id, def->name);
+#if 0
+            mrp_msg_dump(event_data, stdout);
+#endif
+
+            mrp_list_foreach(&def->watches, p, n) {
+                w = mrp_list_entry(p, typeof(*w), hook);
+                w->cb(w, def->id, event_data, w->user_data);
+            }
+
+            mrp_list_foreach(&watches, p, n) {
+                w = mrp_list_entry(p, typeof(*w), hook);
+                if (mrp_test_event(&w->events, id))
+                    w->cb(w, def->id, event_data, w->user_data);
+            }
+
+            nemit--;
+            mrp_msg_unref(event_data);
+        }
+
+        if (nemit <= 0)
+            purge_deleted();
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+int mrp_emit_event(int id, ...)
+{
+    mrp_msg_t *msg;
+    uint16_t   tag;
+    va_list    ap;
+    int        success;
+
+    va_start(ap, id);
+    tag = va_arg(ap, unsigned int);
+    if (tag != MRP_MSG_FIELD_INVALID)
+        msg = mrp_msg_createv(tag, ap);
+    else
+        msg = NULL;
+    va_end(ap);
+
+    success = mrp_emit_event_msg(id, msg);
+    mrp_msg_unref(msg);
+
+    return success;
+}
+
+
+mrp_event_mask_t *mrp_set_events(mrp_event_mask_t *mask, ...)
+{
+    va_list ap;
+    int     id;
+
+    mrp_reset_event_mask(mask);
+
+    va_start(ap, mask);
+    while ((id = va_arg(ap, int)) != MRP_EVENT_UNKNOWN) {
+        mrp_add_event(mask, id);
+    }
+    va_end(ap);
+
+    return mask;
+}
+
+
+mrp_event_mask_t *mrp_set_named_events(mrp_event_mask_t *mask, ...)
+{
+    va_list     ap;
+    const char *name;
+    int         id;
+
+    mrp_reset_event_mask(mask);
+
+    va_start(ap, mask);
+    while ((name = va_arg(ap, const char *)) != NULL) {
+        id = mrp_lookup_event(name);
+
+        if (id != MRP_EVENT_UNKNOWN)
+            mrp_add_event(mask, id);
+    }
+    va_end(ap);
+
+    return mask;
+}
+
+
+static int event_count(mrp_event_mask_t *mask)
+{
+    uint64_t bits = *mask;
+
+    return __builtin_popcountll(bits);
+}
+
+
+static int lowest_bit(mrp_event_mask_t *mask)
+{
+    uint64_t bits = *mask;
+
+    return __builtin_ffs(bits);
+}
+
+
+static int single_event(mrp_event_mask_t *mask)
+{
+    if (event_count(mask) == 1)
+        return lowest_bit(mask);
+    else
+        return MRP_EVENT_UNKNOWN;
+}
diff --git a/src/core/event.h b/src/core/event.h
new file mode 100644 (file)
index 0000000..389c890
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_EVENT_H__
+#define __MURPHY_EVENT_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/msg.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_EVENT_UNKNOWN   0
+#define MRP_EVENT_MAX      64
+
+
+/*
+ * event declarations
+ */
+
+typedef struct {
+    const char *name;                    /* event name */
+    int         id;                      /* associated event id */
+} mrp_event_t;
+
+
+/*
+ * Convenience macro for autoregistering a table of events on startup.
+ */
+
+#define MRP_REGISTER_EVENTS(_event_table, ...)                          \
+    static mrp_event_t _event_table[] = {                               \
+        __VA_ARGS__,                                                    \
+        { .name = NULL }                                                \
+    };                                                                  \
+    MRP_INIT static void register_events(void)                          \
+    {                                                                   \
+        mrp_event_t *e;                                                 \
+        int          i;                                                 \
+                                                                        \
+        for (i = 0, e = _event_table; e->name != NULL; i++, e++) {      \
+            if (e->id != i) {                                           \
+                mrp_log_error("%s:%d: misinitialized event table %s.",  \
+                              __FILE__, __LINE__, #_event_table);       \
+                mrp_log_error("This can result from passing a mis"      \
+                              "initialized event table to the macro");  \
+                mrp_log_error("MRP_REGISTER_EVENT, ie. a table where "  \
+                              "id != index for some element).");        \
+                return;                                                 \
+            }                                                           \
+                                                                        \
+            e->id = mrp_register_event(e->name);                        \
+                                                                        \
+            if (e->id != MRP_EVENT_UNKNOWN)                             \
+                mrp_log_info("Event '%s' registered as 0x%x.",          \
+                             e->name, e->id);                           \
+            else                                                        \
+                mrp_log_error("Failed to register event '%s'.",         \
+                              e->name);                                 \
+        }                                                               \
+    }                                                                   \
+    struct __mrp_allow_trailing_semicolon
+
+
+typedef uint64_t mrp_event_mask_t;
+
+
+typedef struct mrp_event_watch_s mrp_event_watch_t;
+
+
+/** Event watch notification callback type. */
+typedef void (*mrp_event_cb_t)(mrp_event_watch_t *w, int id,
+                               mrp_msg_t *event_data, void *user_data);
+
+/** Subscribe for an event. */
+mrp_event_watch_t *mrp_add_event_watch(mrp_event_mask_t *events,
+                                       mrp_event_cb_t cb,
+                                       void *user_data);
+
+/** Unsubscribe from an event. */
+void mrp_del_event_watch(mrp_event_watch_t *w);
+
+/** Get the id of the given event, create missing events is asked for. */
+int mrp_get_event_id(const char *name, int create);
+
+/** Get the name of the event corresponding to the given id. */
+const char *mrp_get_event_name(int id);
+
+/** Register a new event. */
+#define mrp_register_event(name) mrp_get_event_id(name, TRUE)
+
+/** Look up the id of an existing event. */
+#define mrp_lookup_event(name) mrp_get_event_id(name, FALSE)
+
+/** Emit an event with the given message. */
+int mrp_emit_event_msg(int id, mrp_msg_t *event_data);
+
+/** Emit an event with a message constructed from the given parameters. */
+int mrp_emit_event(int id, ...) MRP_NULLTERM;
+
+/** Initialize an event mask to be empty. */
+static inline void mrp_reset_event_mask(mrp_event_mask_t *mask)
+{
+    *mask = 0;
+}
+
+/** Turn on the bit corresponding to id in mask. */
+static inline void mrp_add_event(mrp_event_mask_t *mask, int id)
+{
+    *mask |= (1 << (id - 1));
+}
+
+/** Turn off the bit corresponding to id in mask. */
+static inline void mrp_del_event(mrp_event_mask_t *mask, int id)
+{
+    *mask &= ~(1 << (id - 1));
+}
+
+/** Test if the bit corresponding to id in mask is on. */
+static inline int mrp_test_event(mrp_event_mask_t *mask, int id)
+{
+    return *mask & (1 << (id - 1));
+}
+
+/** Turn on the bit corresponding to the named event in mask. */
+static inline int mrp_add_named_event(mrp_event_mask_t *mask, const char *name)
+{
+    int id = mrp_lookup_event(name);
+
+    if (id != 0) {
+        mrp_add_event(mask, id);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+/** Turn off the bit corresponding to the named event in mask. */
+static inline int mrp_del_named_event(mrp_event_mask_t *mask, const char *name)
+{
+    int id = mrp_lookup_event(name);
+
+    if (id != 0) {
+        mrp_del_event(mask, id);
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+/** Test the bit corresponding to the named event in mask. */
+static inline int mrp_test_named_event(mrp_event_mask_t *mask, const char *name)
+{
+    int id = mrp_lookup_event(name);
+
+    if (id != 0)
+        return mrp_test_event(mask, id);
+    else
+        return FALSE;
+}
+
+/** Set up the given mask with the given event ids (terminated by
+    MRP_EVENT_UNKNOWN). */
+mrp_event_mask_t *mrp_set_events(mrp_event_mask_t *mask, ...);
+
+/** Set up the given mask with the given named events (terminated by
+    NULL). */
+mrp_event_mask_t *mrp_set_named_events(mrp_event_mask_t *mask, ...);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_EVENT_H__ */
diff --git a/src/core/lua-bindings/Makefile b/src/core/lua-bindings/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/core/lua-bindings/lua-bitwise.c b/src/core/lua-bindings/lua-bitwise.c
new file mode 100644 (file)
index 0000000..0f40705
--- /dev/null
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+/*
+ * Lua bitwise operations
+ */
+
+static int bitwise_lua_and(lua_State *L);
+static int bitwise_lua_or(lua_State *L);
+static int bitwise_lua_xor(lua_State *L);
+static int bitwise_lua_neg(lua_State *L);
+
+static int bitwise_lua_and(lua_State *L)
+{
+    int narg = lua_gettop(L);
+    int offs, i, v;
+
+    switch (lua_type(L, 1)) {
+    case LUA_TUSERDATA:
+    case LUA_TLIGHTUSERDATA:
+        offs = 2;
+        break;
+    default:
+        offs = 1;
+        break;
+    }
+
+    v = lua_tointeger(L, offs);
+    for (i = offs + 1; i <= narg; i++)
+        v &= lua_tointeger(L, i);
+
+    lua_pushinteger(L, v);
+    return 1;
+}
+
+
+static int bitwise_lua_or(lua_State *L)
+{
+    int narg = lua_gettop(L);
+    int offs, i, v;
+
+    switch (lua_type(L, 1)) {
+    case LUA_TUSERDATA:
+    case LUA_TLIGHTUSERDATA:
+        offs = 2;
+        break;
+    default:
+        offs = 1;
+        break;
+    }
+
+    v = lua_tointeger(L, offs);
+    for (i = offs + 1; i <= narg; i++)
+        v |= lua_tointeger(L, i);
+
+    lua_pushinteger(L, v);
+    return 1;
+}
+
+
+static int bitwise_lua_xor(lua_State *L)
+{
+    int narg = lua_gettop(L);
+    int offs, i, v;
+
+    switch (lua_type(L, 1)) {
+    case LUA_TUSERDATA:
+    case LUA_TLIGHTUSERDATA:
+        offs = 2;
+        break;
+    default:
+        offs = 1;
+        break;
+    }
+
+    v = lua_tointeger(L, offs);
+    for (i = offs + 1; i <= narg; i++)
+        v ^= lua_tointeger(L, i);
+
+    lua_pushinteger(L, v);
+    return 1;
+}
+
+
+static int bitwise_lua_neg(lua_State *L)
+{
+    int narg = lua_gettop(L);
+    int arg, offs;
+
+    switch (lua_type(L, 1)) {
+    case LUA_TUSERDATA:
+    case LUA_TLIGHTUSERDATA:
+        offs = 2;
+        break;
+    default:
+        offs = 1;
+        break;
+    }
+
+    if (narg != offs)
+        return luaL_error(L, "bitwise NEG takes a single argument");
+
+    arg = lua_tointeger(L, offs);
+    lua_pushinteger(L, ~arg);
+
+    return 1;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, NULL,
+                             { "AND"   , bitwise_lua_and },
+                             { "OR"    , bitwise_lua_or  },
+                             { "XOR"   , bitwise_lua_xor },
+                             { "NEG"   , bitwise_lua_neg },
+                             { "NEGATE", bitwise_lua_neg });
diff --git a/src/core/lua-bindings/lua-console.c b/src/core/lua-bindings/lua-console.c
new file mode 100644 (file)
index 0000000..ef7e5e0
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <alloca.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/core/console.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+static void eval_cb(mrp_console_t *c, void *user_data, const char *grp,
+                    const char *cmd, char *code)
+{
+    lua_State *L;
+    int        len, top;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(grp);
+    MRP_UNUSED(cmd);
+
+    L = mrp_lua_get_lua_state();
+
+    if (L == NULL) {
+        printf("Lua runtime not available or initialized.");
+        return;
+    }
+
+    top = lua_gettop(L);
+
+    len = strlen(code);
+    if (luaL_loadbuffer(L, code, len, "<console>") || lua_pcall(L, 0, 0, 0))
+        printf("Lua error: %s\n", lua_tostring(L, -1));
+
+    lua_settop(L, top);
+}
+
+
+static void source_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    lua_State   *L;
+    struct stat  st;
+    char        *path, *code;
+    size_t       size;
+    ssize_t      len;
+    int          fd;
+    int          top;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    if (argc != 3) {
+        printf("Invalid arguments, expecting a single path.");
+        return;
+    }
+    else
+        path = argv[2];
+
+    L = mrp_lua_get_lua_state();
+
+    if (L == NULL) {
+        printf("Lua runtime not available or initialized.");
+        return;
+    }
+
+    if (path && *path) {
+        top = lua_gettop(L);
+
+        if (stat(path, &st) == 0) {
+            fd = open(path, O_RDONLY);
+
+            if (fd >= 0) {
+                size = st.st_size;
+                code = alloca(size);
+                len  = read(fd, code, size);
+                close(fd);
+
+                if (len > 0) {
+                    if (luaL_loadbuffer(L, code, len, path) != 0 ||
+                        lua_pcall(L, 0, 0, 0) != 0)
+                        printf("Lua error: %s\n", lua_tostring(L, -1));
+                }
+            }
+            else
+                printf("Failed to open %s (%d: %s).\n", path,
+                       errno, strerror(errno));
+        }
+        else
+            printf("Failed to open %s (%d: %s).\n", path,
+                   errno, strerror(errno));
+
+        lua_settop(L, top);
+    }
+}
+
+
+static void debug_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    mrp_lua_debug_t level;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    if (argc == 3) {
+        if      (!strcmp(argv[2], "disable"))  level = MRP_LUA_DEBUG_DISABLED;
+        else if (!strcmp(argv[2], "enable"))   level = MRP_LUA_DEBUG_ENABLED;
+        else if (!strcmp(argv[2], "detailed")) level = MRP_LUA_DEBUG_DETAILED;
+        else {
+            printf("Invalid Lua debug level '%s'.\n", argv[2]);
+            printf("The valid levels are: disable, enable, detailed.\n");
+            return;
+        }
+
+        if (mrp_lua_set_debug(level))
+            printf("Lua debugging level set to '%s'.\n", argv[2]);
+        else
+            printf("Failed to set Lua debugging level to '%s'.\n", argv[2]);
+    }
+    else {
+        printf("Invalid usage.\n");
+        printf("Argument must be disable, enable, or detailed.\n");
+    }
+}
+
+
+static void dump_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    lua_State            *L = mrp_lua_get_lua_state();
+    mrp_lua_tostr_mode_t  mode;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    switch (argc) {
+    case 2:
+        mode = MRP_LUA_TOSTR_CHECKDUMP;
+        break;
+
+    case 3:
+        if      (!strcmp(argv[2], "default"))   mode = MRP_LUA_TOSTR_DEFAULT;
+        else if (!strcmp(argv[2], "stackdump")) mode = MRP_LUA_TOSTR_STACKDUMP;
+        else if (!strcmp(argv[2], "errordump")) mode = MRP_LUA_TOSTR_ERRORDUMP;
+        else if (!strcmp(argv[2], "checkdump")) mode = MRP_LUA_TOSTR_CHECKDUMP;
+        else {
+            printf("Unknown dump mode '%s', using default.\n", argv[2]);
+            mode = MRP_LUA_TOSTR_DEFAULT;
+        }
+        break;
+
+    case 4:
+#define MAP(var, name, value) if (!strcmp(var, name)) mode |= value
+        mode = 0;
+        MAP(argv[2], "lua"    , MRP_LUA_TOSTR_LUA    );
+        MAP(argv[2], "minimal", MRP_LUA_TOSTR_MINIMAL);
+        MAP(argv[2], "compact", MRP_LUA_TOSTR_COMPACT);
+        MAP(argv[2], "oneline", MRP_LUA_TOSTR_ONELINE);
+        MAP(argv[2], "short"  , MRP_LUA_TOSTR_SHORT  );
+        MAP(argv[2], "medium" , MRP_LUA_TOSTR_MEDIUM );
+        MAP(argv[2], "full"   , MRP_LUA_TOSTR_FULL   );
+        MAP(argv[2], "verbose", MRP_LUA_TOSTR_VERBOSE);
+        MAP(argv[2], "meta"   , MRP_LUA_TOSTR_META   );
+        MAP(argv[2], "data"   , MRP_LUA_TOSTR_DATA   );
+        MAP(argv[2], "both"   , MRP_LUA_TOSTR_BOTH   );
+
+        MAP(argv[3], "lua"    , MRP_LUA_TOSTR_LUA    );
+        MAP(argv[3], "minimal", MRP_LUA_TOSTR_MINIMAL);
+        MAP(argv[3], "compact", MRP_LUA_TOSTR_COMPACT);
+        MAP(argv[3], "oneline", MRP_LUA_TOSTR_ONELINE);
+        MAP(argv[3], "short"  , MRP_LUA_TOSTR_SHORT  );
+        MAP(argv[3], "medium" , MRP_LUA_TOSTR_MEDIUM );
+        MAP(argv[3], "full"   , MRP_LUA_TOSTR_FULL   );
+        MAP(argv[3], "verbose", MRP_LUA_TOSTR_VERBOSE);
+        MAP(argv[3], "meta"   , MRP_LUA_TOSTR_META   );
+        MAP(argv[3], "data"   , MRP_LUA_TOSTR_DATA   );
+        MAP(argv[3], "both"   , MRP_LUA_TOSTR_BOTH   );
+#undef MAP
+        break;
+
+    default:
+        printf("Invalid dump command.\n");
+        return;
+    }
+
+    if (L != NULL)
+        mrp_lua_dump_objects(MRP_LUA_TOSTR_CHECKDUMP, L, stdout);
+}
+
+
+static void gc_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    int        pause, step;
+    char      *e;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+    MRP_UNUSED(user_data);
+
+    if (L == NULL)
+        return;
+
+    switch (argc) {
+    case 2:
+        goto full;
+
+    case 3:
+        if (!strcmp(argv[2], "full")) {
+        full:
+            printf("Performing a full Lua garbage collection cycle...\n");
+            lua_gc(L, LUA_GCCOLLECT, 0);
+        }
+        else if (!strcmp(argv[2], "stop")) {
+            lua_gc(L, LUA_GCSTOP, 0);
+            printf("Lua garbage collector stopped...\n");
+        }
+        else if (!strcmp(argv[2], "start")) {
+            lua_gc(L, LUA_GCRESTART, 0);
+            printf("Lua garbage collector restarted...\n");
+        }
+        else
+        invalid:
+            printf("Invalid Lua garbage collector command.\n");
+        break;
+
+    case 5:
+        if (strcmp(argv[2], "set"))
+            goto invalid;
+
+        pause = (int)strtoul(argv[3], &e, 10);
+        if (*e) {
+            printf("Invalid Lua garbage collector pause '%s'.\n", argv[3]);
+            return;
+        }
+
+        step = strtoul(argv[4], &e, 10);
+        if (*e) {
+            printf("Invalid Lua garbage collector step '%s'.\n", argv[4]);
+            return;
+        }
+
+        printf("Setting Lua garbage collector pause=%d, step=%d...\n",
+               pause, step);
+
+        lua_gc(L, LUA_GCSETPAUSE, pause);
+        lua_gc(L, LUA_GCSETSTEPMUL, step);
+        break;
+
+    default:
+        goto invalid;
+    }
+}
+
+#define LUA_GROUP_DESCRIPTION                                    \
+    "Lua commands allows one to evaluate Lua code either from\n" \
+    "the console command line itself, or from sourced files.\n"
+
+#define EVAL_SYNTAX      "<lua-code>"
+#define EVAL_SUMMARY     "evaluate the given snippet of Lua code"
+#define EVAL_DESCRIPTION                                               \
+    "Evaluate the given snippet of Lua code. Currently you have to\n"  \
+    "fully quote the Lua code you are trying to evaluate to protect\n" \
+    "it from the tokenizer of the console input parser. This is the\n" \
+    "easiest to accomplish by surrounding your Lua code snippet in\n"  \
+    "single or double quotes unconditionally.\n"
+
+#define SOURCE_SYNTAX      "source <lua-file>"
+#define SOURCE_SUMMARY     "evaluate the Lua script from the given <lua-file>"
+#define SOURCE_DESCRIPTION "Read and evaluate the contents of <lua-file>.\n"
+
+#define DEBUG_SYNTAX       "debug {disable, enable, detailed}"
+#define DEBUG_SUMMARY      "configure Murphy Lua debugging"
+#define DEBUG_DESCRIPTION  "Configure Murphy Lua debugging."
+
+#define DUMP_SYNTAX        "dump [dump-flags]"
+#define DUMP_SUMMARY       "dump active Murphy Lua objects"
+#define DUMP_DESCRIPTION                                                     \
+    "Dump unfreed Murphy Lua objects per object class. You need to enable\n" \
+    "object tracking for this to work. The easiest way to do this is to\n"   \
+    "set the environment variable__MURPHY_MM_CONFIG=\"lua:true\" before\n"   \
+    "starting the daemon. dump-flags control how much information gets\n"    \
+    "printed about a single object. If you use a single dump-flag, it can\n" \
+    "be one of default, stackdump, errordump, or checkdump. If omitted,\n"   \
+    "default is used. You can also give a pair of dump flags, the first\n"   \
+    "of lua, minimal, compact, oneline, short, medium, full, or verbose\n"   \
+    "and the second one of meta, data, or both. These correspond directly\n" \
+    "to the object to string conversion mode flags of the Murphy Lua\n"      \
+    "object infrastructure. At the moment these flags have very little\n"    \
+    "practical effect on the actual dump as most of the dump modes have\n"   \
+    "not been implemented yet so now they are just aliased to the default.\n"
+
+#define GC_SYNTAX        "gc [full|stop|start|set <pause> <step>"
+#define GC_SUMMARY       "trigger or configure the Lua garbage collector"
+#define GC_DESCRIPTION   "Trigger or configure the Lua garbage collector."
+
+MRP_CORE_CONSOLE_GROUP(lua_group, "lua", LUA_GROUP_DESCRIPTION, NULL, {
+        MRP_TOKENIZED_CMD("source", source_cb, FALSE,
+                          SOURCE_SYNTAX, SOURCE_SUMMARY, SOURCE_DESCRIPTION),
+        MRP_RAWINPUT_CMD("eval", eval_cb,
+                         MRP_CONSOLE_CATCHALL | MRP_CONSOLE_SELECTABLE,
+                         EVAL_SYNTAX, EVAL_SUMMARY, EVAL_DESCRIPTION),
+        MRP_TOKENIZED_CMD("debug", debug_cb, FALSE,
+                          DEBUG_SYNTAX, DEBUG_SUMMARY, DEBUG_DESCRIPTION),
+        MRP_TOKENIZED_CMD("dump", dump_cb, FALSE,
+                          DUMP_SYNTAX, DUMP_SUMMARY, DUMP_DESCRIPTION),
+        MRP_TOKENIZED_CMD("gc", gc_cb, FALSE,
+                          GC_SYNTAX, GC_SUMMARY, GC_DESCRIPTION),
+    });
diff --git a/src/core/lua-bindings/lua-deferred.c b/src/core/lua-bindings/lua-deferred.c
new file mode 100644 (file)
index 0000000..9e0f3f1
--- /dev/null
@@ -0,0 +1,290 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/lua-utils/error.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#define DEFERRED_LUA_CLASS MRP_LUA_CLASS(deferred, lua)
+
+/*
+ * Lua deferred object
+ */
+
+typedef struct {
+    lua_State      *L;                   /* Lua execution context */
+    mrp_mainloop_t *ml;                  /* murphy mainloop */
+    mrp_deferred_t *d;                   /* associated murphy deferred */
+    int             callback;            /* reference to callback */
+    bool            disabled;            /* true if disabled */
+    bool            oneshot;             /* true for one-shot deferreds */
+} deferred_lua_t;
+
+
+static int deferred_lua_create(lua_State *L);
+static void deferred_lua_destroy(void *data);
+static void deferred_lua_changed(void *data, lua_State *L, int member);
+static ssize_t deferred_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                     size_t size, lua_State *L, void *data);
+static int deferred_lua_enable(lua_State *L);
+static int deferred_lua_disable(lua_State *L);
+
+/*
+ * Lua deferred class
+ */
+
+#define OFFS(m)  MRP_OFFSET(deferred_lua_t, m)
+#define RDONLY   MRP_LUA_CLASS_READONLY
+#define NOTIFY   MRP_LUA_CLASS_NOTIFY
+#define NOFLAGS  MRP_LUA_CLASS_NOFLAGS
+#define USESTACK MRP_LUA_CLASS_USESTACK
+
+MRP_LUA_METHOD_LIST_TABLE(deferred_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(deferred_lua_create)
+                          MRP_LUA_METHOD(disable, deferred_lua_disable)
+                          MRP_LUA_METHOD(enable , deferred_lua_enable));
+
+MRP_LUA_METHOD_LIST_TABLE(deferred_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (deferred_lua_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(deferred_lua_members,
+    MRP_LUA_CLASS_LFUNC  ("callback" , OFFS(callback) , NULL, NULL, NOTIFY )
+    MRP_LUA_CLASS_BOOLEAN("disabled" , OFFS(disabled) , NULL, NULL, NOTIFY )
+    MRP_LUA_CLASS_BOOLEAN("oneshot"  , OFFS(oneshot)  , NULL, NULL, NOTIFY ));
+
+typedef enum {
+    DEFERRED_MEMBER_CALLBACK,
+    DEFERRED_MEMBER_DISABLED,
+    DEFERRED_MEMBER_ONESHOT,
+} deferred_member_t;
+
+MRP_LUA_DEFINE_CLASS(deferred, lua, deferred_lua_t, deferred_lua_destroy,
+                     deferred_lua_methods, deferred_lua_overrides,
+                     deferred_lua_members, NULL, deferred_lua_changed,
+                     deferred_lua_tostring, NULL,
+                     MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_DYNAMIC);
+
+
+static void deferred_lua_cb(mrp_deferred_t *deferred, void *user_data)
+{
+    deferred_lua_t *d   = (deferred_lua_t *)user_data;
+    int             one = d->oneshot;
+    int             top;
+
+    MRP_UNUSED(deferred);
+
+    top = lua_gettop(d->L);
+
+    if (mrp_lua_object_deref_value(d, d->L, d->callback, false)) {
+        mrp_lua_push_object(d->L, d);
+
+        if (lua_pcall(d->L, 1, 0, 0) != 0) {
+            mrp_log_error("failed to invoke Lua deferred callback, disabling");
+            mrp_disable_deferred(d->d);
+            d->disabled = true;
+        }
+    }
+
+    if (one) {
+        mrp_disable_deferred(d->d);
+        d->disabled = true;
+    }
+
+    lua_settop(d->L, top);
+}
+
+
+static void deferred_lua_changed(void *data, lua_State *L, int member)
+{
+    deferred_lua_t *d = (deferred_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("deferred member #%d (%s) changed", member,
+              deferred_lua_members[member].name);
+
+    switch (member) {
+    case DEFERRED_MEMBER_DISABLED:
+        if (d->disabled)
+            mrp_disable_deferred(d->d);
+        else
+            mrp_enable_deferred(d->d);
+        mrp_debug("deferred %p(%p) is now %sabled", d, d->d,
+                  d->disabled ? "dis" : "en");
+        break;
+
+    case DEFERRED_MEMBER_CALLBACK:
+        if (!d->disabled) {
+            if (d->callback == LUA_NOREF)
+                mrp_disable_deferred(d->d);
+            else
+                mrp_enable_deferred(d->d);
+            mrp_debug("deferred %p(%p) is now %sabled", d, d->d,
+                      d->disabled ? "dis" : "en");
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static int deferred_lua_create(lua_State *L)
+{
+
+    mrp_context_t  *ctx    = mrp_lua_get_murphy_context();
+    char            e[128] = "";
+    deferred_lua_t *d;
+    int             narg;
+
+    if (ctx == NULL)
+        luaL_error(L, "failed to get murphy context");
+
+    narg = lua_gettop(L);
+
+    d = (deferred_lua_t *)mrp_lua_create_object(L, DEFERRED_LUA_CLASS, NULL, 0);
+
+    d->L        = L;
+    d->ml       = ctx->ml;
+    d->d        = mrp_add_deferred(d->ml, deferred_lua_cb, d);
+    d->callback = LUA_NOREF;
+
+    if (d->d == NULL)
+        return luaL_error(L, "failed to create Lua Murphy deferred");
+
+    switch (narg) {
+    case 1:
+        break;
+    case 2:
+        if (mrp_lua_init_members(d, L, -2, e, sizeof(e)) != 1)
+            return luaL_error(L, "failed to initialize deferred (%s)", e);
+        break;
+    default:
+        return luaL_error(L, "expecting 0 or 1 arguments, got %d", narg);
+    }
+
+    if (d->disabled || d->callback == LUA_NOREF || d->callback == LUA_REFNIL)
+        mrp_disable_deferred(d->d);
+
+    return 1;
+}
+
+
+static void deferred_lua_destroy(void *data)
+{
+    deferred_lua_t *d = (deferred_lua_t *)data;
+
+    mrp_debug("destroying Lua deferred %p", data);
+
+    mrp_del_deferred(d->d);
+    d->d = NULL;
+
+    mrp_lua_object_unref_value(d, d->L, d->callback);
+
+    d->callback = LUA_NOREF;
+}
+
+
+static deferred_lua_t *deferred_lua_check(lua_State *L, int idx)
+{
+    return (deferred_lua_t *)mrp_lua_check_object(L, DEFERRED_LUA_CLASS, idx);
+}
+
+
+static ssize_t deferred_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                     size_t size, lua_State *L, void *data)
+{
+    deferred_lua_t *d = (deferred_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{%s %s deferred %p}",
+                        d->disabled ? "disabled" : "enabled",
+                        d->oneshot  ? "oneshot"  : "recurring",
+                        d->d);
+    }
+}
+
+
+static int deferred_lua_enable(lua_State *L)
+{
+    deferred_lua_t *d = deferred_lua_check(L, -1);
+
+    if (d == NULL) {
+        lua_pushboolean(L, false);
+        return 1;
+    }
+
+    if (d->d != NULL && d->callback != LUA_NOREF && d->callback != LUA_REFNIL) {
+        mrp_enable_deferred(d->d);
+        d->disabled = false;
+    }
+
+    lua_pushboolean(L, !d->disabled);
+
+    return 1;
+}
+
+
+static int deferred_lua_disable(lua_State *L)
+{
+    deferred_lua_t *d = deferred_lua_check(L, -1);
+
+    if (d == NULL) {
+        lua_pushboolean(L, false);
+        return 1;
+    }
+
+    if (d->d != NULL) {
+        mrp_disable_deferred(d->d);
+        d->disabled = true;
+    }
+
+    lua_pushboolean(L, true);
+
+    return 1;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, DEFERRED_LUA_CLASS,
+                             { "Deferred", deferred_lua_create });
diff --git a/src/core/lua-bindings/lua-env.c b/src/core/lua-bindings/lua-env.c
new file mode 100644 (file)
index 0000000..5190186
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <sys/types.h>
+
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+static int env_lua_getenv(lua_State *L)
+{
+    int         narg = lua_gettop(L);
+    int         offs, i;
+    const char *v;
+
+    switch (lua_type(L, 1)) {
+    case LUA_TUSERDATA:
+    case LUA_TLIGHTUSERDATA:
+        offs = 2;
+        break;
+    default:
+        offs = 1;
+        break;
+    }
+
+    for (i = offs; i <= narg; i++) {
+        if (lua_type(L, offs) == LUA_TSTRING)
+            v = getenv(lua_tostring(L, offs));
+        else
+            v = NULL;
+
+        lua_remove(L, offs);
+
+        if (v)
+            lua_pushstring(L, v);
+        else
+            lua_pushnil(L);
+    }
+
+    return narg + 1 - offs;
+}
+
+
+static int env_lua_getpid(lua_State *L)
+{
+    lua_pushinteger(L, getpid());
+
+    return 1;
+}
+
+
+static int env_lua_getuid(lua_State *L)
+{
+    lua_pushinteger(L, getuid());
+
+    return 1;
+}
+
+
+static int env_lua_geteuid(lua_State *L)
+{
+    lua_pushinteger(L, geteuid());
+
+    return 1;
+}
+
+
+static int env_lua_getgid(lua_State *L)
+{
+    lua_pushinteger(L, getuid());
+
+    return 1;
+}
+
+
+static int env_lua_getuser(lua_State *L)
+{
+    struct passwd pwd, *r;
+    char          buf[1024];
+
+    getpwuid_r(getuid(), &pwd, buf, sizeof(buf), &r);
+
+    if (r != NULL)
+        lua_pushstring(L, pwd.pw_name);
+    else
+        lua_pushnil(L);
+
+    return 1;
+}
+
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, NULL,
+                             { "getenv" , env_lua_getenv  },
+                             { "getpid" , env_lua_getpid  },
+                             { "getuid" , env_lua_getuid  },
+                             { "geteuid", env_lua_geteuid },
+                             { "getgid" , env_lua_getgid  },
+                             { "getuser", env_lua_getuser });
diff --git a/src/core/lua-bindings/lua-event.c b/src/core/lua-bindings/lua-event.c
new file mode 100644 (file)
index 0000000..0a9902f
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2014 Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+/* This is a placeholder for proper Murphy event system. The facilities for
+ * sending/receiving messages along with the events have not been implemented
+ * yet, and neither is sending/receiving multiple events at once (as part of
+ * the event mask). Only the basic functionality for sending/receiving
+ * argumentless messages exists. */
+
+/*
+ * Lua EventWatch object
+ */
+
+#define EVTWATCH_LUA_CLASS MRP_LUA_CLASS(evtwatch, lua)
+
+typedef struct {
+    lua_State         *L;          /* Lua execution context */
+    mrp_context_t     *ctx;        /* murphy context */
+    mrp_event_bus_t   *bus;        /* event bus to watch on */
+    mrp_event_mask_t   mask;       /* mask of events to watch */
+    mrp_event_watch_t *w;          /* associated murphy event watch */
+    bool               init;       /* being initialized */
+
+    /* Lua members */
+    char              *bus_name;   /* name of the event bus to use */
+    char             **events;     /* name of the events to watch */
+    int                nevent;     /* number of event names */
+    int                callback;   /* reference to callback */
+    bool               oneshot;    /* disable after first matching event */
+} evtwatch_lua_t;
+
+
+static int evtwatch_lua_create(lua_State *L);
+static void evtwatch_lua_destroy(void *data);
+static void evtwatch_lua_changed(void *data, lua_State *L, int member);
+static ssize_t evtwatch_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                     size_t size, lua_State *L, void *data);
+
+static int evtwatch_lua_start(lua_State *L);
+static int evtwatch_lua_stop(lua_State *L);
+
+static void evtwatch_lua_cb(mrp_event_watch_t *ew, uint32_t id,
+                            int format, void *data, void *user_data);
+
+
+
+/*
+ * Lua EventWatch class
+ */
+
+#define OFFS(m) MRP_OFFSET(evtwatch_lua_t, m)
+#define RDONLY  MRP_LUA_CLASS_READONLY
+#define NOTIFY  MRP_LUA_CLASS_NOTIFY
+#define NOFLAGS MRP_LUA_CLASS_NOFLAGS
+
+MRP_LUA_METHOD_LIST_TABLE(evtwatch_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(evtwatch_lua_create)
+                          MRP_LUA_METHOD(stop , evtwatch_lua_stop)
+                          MRP_LUA_METHOD(start, evtwatch_lua_start));
+
+MRP_LUA_METHOD_LIST_TABLE(evtwatch_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL(evtwatch_lua_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(evtwatch_lua_members,
+    MRP_LUA_CLASS_STRING ("bus"     , OFFS(bus_name), NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_ARRAY  ("events"  , STRING, evtwatch_lua_t, events, nevent,
+                                                      NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_LFUNC  ("callback", OFFS(callback), NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_BOOLEAN("oneshot" , OFFS(oneshot) , NULL, NULL, NOTIFY));
+
+
+typedef enum {
+    EVENT_MEMBER_BUS,
+    EVENT_MEMBER_EVENTS,
+    EVENT_MEMBER_CALLBACK,
+    EVENT_MEMBER_ONESHOT
+} event_member_t;
+
+MRP_LUA_DEFINE_CLASS(evtwatch, lua, evtwatch_lua_t, evtwatch_lua_destroy,
+        evtwatch_lua_methods, evtwatch_lua_overrides,
+        evtwatch_lua_members, NULL, evtwatch_lua_changed,
+        evtwatch_lua_tostring, NULL,
+        MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_DYNAMIC);
+
+
+static void evtwatch_stop(evtwatch_lua_t *w)
+{
+    mrp_event_del_watch(w->w);
+    w->w = NULL;
+}
+
+
+static bool evtwatch_start(evtwatch_lua_t *w)
+{
+    if (w->w == NULL) {
+        w->w = mrp_event_add_watch_mask(w->bus, &w->mask, evtwatch_lua_cb, w);
+        mrp_debug("started event watch %p (%p)", w, w->w);
+    }
+
+    return w->w != NULL;
+}
+
+
+static void evtwatch_lua_cb(mrp_event_watch_t *watch, uint32_t id,
+                            int format, void *data, void *user_data)
+{
+    evtwatch_lua_t *w   = (evtwatch_lua_t *)user_data;
+    int             one = w->oneshot;
+    int             top;
+
+    MRP_UNUSED(watch);
+    MRP_UNUSED(format);
+    MRP_UNUSED(data);
+
+    mrp_debug("got event 0x%x (%s)", id, mrp_event_name(id));
+
+    top = lua_gettop(w->L);
+
+    if (mrp_lua_object_deref_value(w, w->L, w->callback, false)) {
+        mrp_lua_push_object(w->L, w);
+        lua_pushinteger(w->L, id);
+
+        if (lua_pcall(w->L, 2, 0, 0) != 0) {
+            mrp_log_error("failed to invoke Lua event watch callback (%s), "
+                          "stopping", lua_tostring(w->L, -1));
+            evtwatch_stop(w);
+        }
+
+        if (one)
+            evtwatch_stop(w);
+    }
+
+    lua_settop(w->L, top);
+}
+
+
+static void evtwatch_lua_changed(void *data, lua_State *L, int member)
+{
+    evtwatch_lua_t *w = (evtwatch_lua_t *)data;
+    int             i;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("event watch member #%d (%s) changed", member,
+              evtwatch_lua_members[member].name);
+
+    switch (member) {
+    case EVENT_MEMBER_BUS:
+        if (!w->init)
+            evtwatch_stop(w);
+        if (!w->bus_name || !*w->bus_name || !strcmp(w->bus_name, "global"))
+            w->bus = NULL;
+        else
+            w->bus = mrp_event_bus_get(w->ctx->ml, w->bus_name);
+        if (!w->init)
+            evtwatch_start(w);
+        break;
+
+    case EVENT_MEMBER_EVENTS:
+        if (!w->init)
+            evtwatch_stop(w);
+        mrp_mask_reset(&w->mask);
+        for (i = 0; i < w->nevent; i++) {
+            mrp_debug("setting event %s in mask", w->events[i]);
+            mrp_mask_set(&w->mask, mrp_event_id(w->events[i]));
+        }
+        if (!w->init)
+            evtwatch_start(w);
+        break;
+
+    case EVENT_MEMBER_CALLBACK:
+        mrp_debug("callback set to (ref) %u", w->callback);
+        if (w->callback == LUA_NOREF || w->callback == LUA_REFNIL) {
+            if (!w->init)
+                evtwatch_stop(w);
+        }
+        else
+            if (!w->init)
+                evtwatch_start(w);
+        break;
+
+    case EVENT_MEMBER_ONESHOT:
+        break;
+
+    default:
+        break;
+    }
+
+
+
+}
+
+
+static int evtwatch_lua_create(lua_State *L)
+{
+    evtwatch_lua_t *w;
+    int             narg;
+    char           e[128], events[512];
+
+    narg = lua_gettop(L);
+
+    if (narg < 1 || narg > 2)
+        return luaL_error(L, "expected 0, or 1 arguments, got %d", narg - 1);
+
+    w = (evtwatch_lua_t *)mrp_lua_create_object(L, EVTWATCH_LUA_CLASS, NULL, 0);
+    w->L        = L;
+    w->ctx      = mrp_lua_get_murphy_context();
+    w->init     = true;
+    w->callback = LUA_NOREF;
+
+    if (mrp_lua_init_members(w, L, -2, e, sizeof(e)) != 1)
+        return luaL_error(L, "failed to initialize event watch (%s)", e);
+
+    w->init = false;
+
+    evtwatch_start(w);
+
+    mrp_debug("created event watch %p (%p) for events %s", w, w->w,
+              mrp_event_dump_mask(&w->mask, events, sizeof(events)));
+
+    return 1;
+}
+
+
+static void evtwatch_lua_destroy(void *data)
+{
+    evtwatch_lua_t *w = (evtwatch_lua_t *) data;
+
+    mrp_debug("destroying Lua event watch %p", w);
+
+    evtwatch_stop(w);
+
+    mrp_lua_object_unref_value(w, w->L, w->callback);
+
+    w->callback = LUA_NOREF;
+}
+
+
+static evtwatch_lua_t *evtwatch_lua_check(lua_State *L, int idx)
+{
+    return (evtwatch_lua_t *)mrp_lua_check_object(L, EVTWATCH_LUA_CLASS, idx);
+}
+
+
+static ssize_t evtwatch_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                     size_t size, lua_State *L, void *data)
+{
+    evtwatch_lua_t *w = (evtwatch_lua_t *) data;
+    char            events[512];
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "event watch <%s>",
+                        mrp_event_dump_mask(&w->mask, events, sizeof(events)));
+    }
+}
+
+
+static int evtwatch_lua_start(lua_State *L)
+{
+    evtwatch_lua_t *w = evtwatch_lua_check(L, -1);
+
+    lua_pushboolean(L, evtwatch_start(w));
+
+    return 1;
+}
+
+
+static int evtwatch_lua_stop(lua_State *L)
+{
+    evtwatch_lua_t *w = evtwatch_lua_check(L, -1);
+
+    evtwatch_stop(w);
+
+    return 0;
+}
+
+
+static int evtwatch_emit_event(lua_State *L)
+{
+    mrp_context_t   *ctx  = mrp_lua_get_murphy_context();
+    int              narg = lua_gettop(L);
+    mrp_event_bus_t *bus;
+    const char      *bus_name, *event;
+    uint32_t         id;
+    int              flags, r;
+
+    if (narg != 3 && narg != 4)
+        return luaL_error(L, "expected 2 or 3 arguments, got %d", narg - 1);
+
+    if (lua_type(L, 2) == LUA_TSTRING)
+        bus_name = lua_tostring(L, 2);
+    else if (lua_type(L, 2) == LUA_TNIL)
+        bus_name = NULL;
+    else
+        return luaL_error(L, "expected nil or bus name as 1st argument");
+
+    if (lua_type(L, 3) == LUA_TSTRING)
+        event = lua_tostring(L, 3);
+    else
+        return luaL_error(L, "expected event name string as 2nd argument");
+
+    flags = MRP_EVENT_SYNCHRONOUS;
+
+    if (narg == 4) {
+        if (lua_type(L, 4) != LUA_TBOOLEAN)
+            return luaL_error(L, "expected asynchronous bool as 3rd argument");
+        if (lua_toboolean(L, 4))
+            flags = MRP_EVENT_ASYNCHRONOUS;
+    }
+
+    bus = mrp_event_bus_get(ctx->ml, bus_name);
+    id  = mrp_event_id(event);
+
+    mrp_debug("emitting event 0x%x (<%s>) on bus <%s>", id, event,
+              bus_name ? bus_name : "global");
+
+    r = mrp_event_emit_msg(bus, id, MRP_EVENT_SYNCHRONOUS, flags, MRP_MSG_END);
+
+    lua_pushboolean(L, r == 0);
+
+    return 1;
+}
+
+
+static int evtwatch_event_id(lua_State *L)
+{
+    int narg = lua_gettop(L);
+
+    if (narg != 2)
+        return luaL_error(L, "expected 1 event name argument, got %d", narg);
+
+    if (lua_type(L, 2) != LUA_TSTRING)
+        return luaL_error(L, "expected event name string argument");
+
+    lua_pushinteger(L, mrp_event_id(lua_tostring(L, 2)));
+
+    return 1;
+}
+
+
+static int evtwatch_event_name(lua_State *L)
+{
+    int narg = lua_gettop(L);
+
+    if (narg != 2)
+        return luaL_error(L, "expected 1 event id argument, got %d", narg);
+
+    if (lua_type(L, 2) != LUA_TNUMBER)
+        return luaL_error(L, "expected event id integer argument");
+
+    lua_pushstring(L, mrp_event_name(lua_tointeger(L, 2)));
+
+    return 1;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, EVTWATCH_LUA_CLASS,
+                             { "EventWatch"   , evtwatch_lua_create },
+                             { "emit_event"   , evtwatch_emit_event },
+                             { "EventListener", evtwatch_lua_create },
+                             { "send_event"   , evtwatch_emit_event },
+                             { "event_id"     , evtwatch_event_id   },
+                             { "event_name"   , evtwatch_event_name });
diff --git a/src/core/lua-bindings/lua-json.c b/src/core/lua-bindings/lua-json.c
new file mode 100644 (file)
index 0000000..2dd8f17
--- /dev/null
@@ -0,0 +1,456 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/json.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-bindings/lua-json.h>
+
+#define JSON_LUA_CLASS MRP_LUA_CLASS(json, lua)
+
+/*
+ * Lua JSON object
+ */
+
+typedef struct {
+    mrp_json_t *json;
+} json_lua_t;
+
+
+/*
+ * Lua JSON object methods
+ */
+
+static void json_lua_destroy(void *data);
+static int  json_lua_getfield(lua_State *L);
+static int  json_lua_setfield(lua_State *L);
+static int  json_lua_stringify(lua_State *L);
+static json_lua_t *json_lua_get(lua_State *L, int idx);
+static mrp_json_t *json_lua_table_to_object(lua_State *L, int t);
+
+
+/*
+ * JSON Lua class
+ */
+
+MRP_LUA_METHOD_LIST_TABLE(json_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(mrp_json_lua_create));
+
+MRP_LUA_METHOD_LIST_TABLE(json_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (mrp_json_lua_create)
+                          MRP_LUA_OVERRIDE_GETFIELD (json_lua_getfield)
+                          MRP_LUA_OVERRIDE_SETFIELD (json_lua_setfield)
+                          MRP_LUA_OVERRIDE_STRINGIFY(json_lua_stringify));
+
+MRP_LUA_CLASS_DEF_FLAGS(json, lua, json_lua_t,
+                        json_lua_destroy, json_lua_methods, json_lua_overrides,
+                        MRP_LUA_CLASS_DYNAMIC);
+
+
+int mrp_json_lua_create(lua_State *L)
+{
+    json_lua_t *lson;
+    mrp_json_t *json;
+    int         t;
+
+    switch (lua_gettop(L)) {
+    noargs:
+    case 0:
+        lson = (json_lua_t *)mrp_lua_create_object(L, JSON_LUA_CLASS, NULL, 0);
+        lson->json = mrp_json_create(MRP_JSON_OBJECT);
+        break;
+
+        /*
+         * assume to be called as m:JSON(<initializer table>)
+         *
+         * Notes: We should check the argument to see if it happens
+         *     to be of type murphy to catch calls of the form m.JSON()
+         *     and redirect them to case 0.
+         */
+    case 1:
+        if (lua_type(L, 1) == LUA_TUSERDATA)
+            goto noargs;
+        t = 1;
+    init:
+        if (lua_type(L, t) != LUA_TTABLE)
+            return luaL_error(L, "invalid argument to JSON constructor");
+        json = json_lua_table_to_object(L, t);
+        lson = (json_lua_t *)mrp_lua_create_object(L, JSON_LUA_CLASS, NULL, 0);
+        lson->json = json;
+        break;
+
+        /*
+         * assume to be called as m:JSON(<initializer table>), ie.
+         * m.JSON(m, <initializer table>)
+         *
+         * Notes: We should check the first argument and make sure it
+         *     is of type murphy.
+         */
+    case 2:
+        t = 2;
+        goto init;
+
+    default:
+        return luaL_error(L, "invalid arguments to JSON constructor (%d)",
+                          lua_gettop(L));
+    }
+
+    return 1;
+}
+
+
+void *mrp_json_lua_wrap(lua_State *L, mrp_json_t *json)
+{
+    json_lua_t *lson = mrp_lua_create_object(L, JSON_LUA_CLASS, NULL, 0);
+
+    lson->json = mrp_json_ref(json);
+
+    return lson;
+}
+
+
+int mrp_json_lua_push(lua_State *L, mrp_json_t *json)
+{
+    json_lua_t *lson;
+
+    if ((lson = mrp_json_lua_wrap(L, json)) == NULL)
+        lua_pushnil(L);
+
+    return 1;
+}
+
+
+mrp_json_t *mrp_json_lua_get(lua_State *L, int idx)
+{
+    json_lua_t *lson = json_lua_get(L, idx);
+
+    if (lson != NULL)
+        return mrp_json_ref(lson->json);
+    else
+        return NULL;
+}
+
+
+mrp_json_t *mrp_json_lua_unwrap(void *lson)
+{
+    if (mrp_lua_pointer_of_type(lson, MRP_LUA_TYPE_ID(JSON_LUA_CLASS)))
+        return mrp_json_ref(((json_lua_t *)lson)->json);
+    else
+        return NULL;
+}
+
+
+static void json_lua_destroy(void *data)
+{
+    json_lua_t *lson = (json_lua_t *)data;
+
+    mrp_debug("destroying Lua JSON object %p (%p)", lson, lson->json);
+
+    mrp_json_unref(lson->json);
+}
+
+
+static inline json_lua_t *json_lua_check(lua_State *L, int idx)
+{
+    return (json_lua_t *)mrp_lua_check_object(L, JSON_LUA_CLASS, idx);
+}
+
+
+static json_lua_t *json_lua_get(lua_State *L, int idx)
+{
+    json_lua_t *lson;
+    void       *userdata;
+
+    lua_pushvalue(L, idx);
+    lua_pushliteral(L, "userdata");
+
+    lua_rawget(L, -2);
+    userdata = lua_touserdata(L, -1);
+    lua_pop(L, 2);
+
+    if (userdata != NULL)
+        if ((lson = json_lua_check(L, idx)) != NULL)
+            return lson;
+
+    return NULL;
+}
+
+
+static int json_lua_getfield(lua_State *L)
+{
+    json_lua_t *lson = json_lua_check(L, 1);
+    const char *key;
+    int         idx;
+    mrp_json_t *val;
+
+    switch (lua_type(L, 2)) {
+    case LUA_TSTRING:
+        key = lua_tostring(L, 2);
+        val = mrp_json_get(lson->json, key);
+        break;
+
+    case LUA_TNUMBER:
+        if (mrp_json_get_type(lson->json) != MRP_JSON_ARRAY)
+            return luaL_error(L, "trying to index non-array JSON object");
+
+        idx = lua_tointeger(L, 2);
+        val = mrp_json_array_get(lson->json, idx - 1);
+        break;
+
+    default:
+        return luaL_error(L, "invalid JSON field/index type (%s).",
+                          lua_typename(L, lua_type(L, 2)));
+    }
+
+    lua_pop(L, 2);
+
+    switch (mrp_json_get_type(val)) {
+    case MRP_JSON_STRING:
+        lua_pushstring(L, mrp_json_string_value(val));
+        break;
+
+    case MRP_JSON_BOOLEAN:
+        lua_pushboolean(L, mrp_json_boolean_value(val));
+        break;
+
+    case MRP_JSON_INTEGER:
+        lua_pushinteger(L, mrp_json_integer_value(val));
+        break;
+
+    case MRP_JSON_DOUBLE:
+        lua_pushnumber(L, mrp_json_double_value(val));
+        break;
+
+    case MRP_JSON_OBJECT:
+    case MRP_JSON_ARRAY:
+        mrp_json_lua_push(L, val);
+        break;
+
+    default:
+        lua_pushnil(L);
+    }
+
+    return 1;
+}
+
+
+static mrp_json_t *json_lua_table_to_object(lua_State *L, int t)
+{
+    json_lua_t *lson;
+    mrp_json_t *json;
+    const char *key;
+    int         idx, i;
+    double      dbl;
+    mrp_json_t *val;
+
+    if ((lson = json_lua_get(L, t)) != NULL)
+        return mrp_json_clone(lson->json);
+
+    json = NULL;
+    val  = NULL;
+
+    lua_pushnil(L);
+    while (lua_next(L, t) != 0) {
+        switch (lua_type(L, -2)) {
+        case LUA_TSTRING:
+            if (json != NULL && mrp_json_get_type(json) != MRP_JSON_OBJECT)
+                luaL_error(L, "trying to set member on a JSON array");
+
+            key = lua_tostring(L, -2);
+            idx = 0;
+            break;
+
+        case LUA_TNUMBER:
+            if (json != NULL && mrp_json_get_type(json) != MRP_JSON_ARRAY)
+                luaL_error(L, "trying to set array element on a JSON object");
+
+            if ((idx = lua_tointeger(L, -2)) < 1)
+                luaL_error(L, "invalid index (%d) for JSON array", idx);
+
+            idx--;
+            key = NULL;
+            break;
+
+        default:
+            luaL_error(L, "invalid member (key) for JSON object");
+            idx = 0;
+            key = NULL;
+        }
+
+        switch (lua_type(L, -1)) {
+        case LUA_TSTRING:
+            val = mrp_json_create(MRP_JSON_STRING, lua_tostring(L, -1), -1);
+            break;
+
+        case LUA_TNUMBER:
+            if ((i = lua_tointeger(L, -1)) == (dbl = lua_tonumber(L, -1)))
+                val = mrp_json_create(MRP_JSON_INTEGER, i);
+            else
+                val = mrp_json_create(MRP_JSON_DOUBLE, dbl);
+            break;
+
+        case LUA_TBOOLEAN:
+            val = mrp_json_create(MRP_JSON_BOOLEAN, lua_toboolean(L, -1));
+            break;
+
+        case LUA_TTABLE:
+            val = json_lua_table_to_object(L, lua_gettop(L));
+            break;
+
+        case LUA_TNIL:
+            goto next;
+
+        default:
+            luaL_error(L, "invalid value for JSON member");
+        }
+
+        if (val == NULL) {
+            mrp_json_unref(json);
+            luaL_error(L, "failed convert Lua value to JSON object");
+        }
+
+        if (json == NULL) {
+            json = mrp_json_create(key ? MRP_JSON_OBJECT : MRP_JSON_ARRAY);
+
+            if (json == NULL)
+                luaL_error(L, "failed to create JSON object");
+        }
+
+        if (key != NULL)
+            mrp_json_add(json, key, val);
+        else
+            if (!mrp_json_array_append(json, val))
+                luaL_error(L, "failed to set JSON array element [%d]", idx);
+
+    next:
+        lua_pop(L, 1);
+    }
+
+    return json;
+}
+
+
+static int json_lua_setfield(lua_State *L)
+{
+    json_lua_t *lson = json_lua_check(L, 1);
+    mrp_json_t *json = lson->json;
+    const char *key;
+    int         idx, i;
+    double      dbl;
+    mrp_json_t *val;
+
+    switch (lua_type(L, 2)) {
+    case LUA_TSTRING:
+        if (mrp_json_get_type(json) != MRP_JSON_OBJECT)
+            return luaL_error(L, "trying to set member on a JSON array");
+
+        key = lua_tostring(L, 2);
+        idx = 0;
+        break;
+
+    case LUA_TNUMBER:
+        if (mrp_json_get_type(json) != MRP_JSON_ARRAY)
+            return luaL_error(L, "trying to set array element on JSON object");
+
+        if ((idx = lua_tointeger(L, 2)) < 1)
+            return luaL_error(L, "invalid index (%d) for JSON array", idx);
+
+        idx--;
+        key = NULL;
+        break;
+
+    default:
+        return luaL_error(L, "invalid member (key) for JSON object");
+    }
+
+    switch (lua_type(L, 3)) {
+    case LUA_TSTRING:
+        val = mrp_json_create(MRP_JSON_STRING, lua_tostring(L, 3), -1);
+        break;
+
+    case LUA_TNUMBER:
+        if ((i = lua_tointeger(L, 3)) == (dbl = lua_tonumber(L, 3)))
+            val = mrp_json_create(MRP_JSON_INTEGER, i);
+        else
+            val = mrp_json_create(MRP_JSON_DOUBLE, dbl);
+        break;
+
+    case LUA_TBOOLEAN:
+        val = mrp_json_create(MRP_JSON_BOOLEAN, lua_toboolean(L, 3));
+        break;
+
+    case LUA_TTABLE:
+        if ((val = json_lua_table_to_object(L, 3)) == json)
+            return luaL_error(L, "can't set JSON object as a member of itself");
+        break;
+
+    case LUA_TNIL:
+        if (key != NULL)
+            mrp_json_del_member(json, key);
+        else
+            return luaL_error(L, "can't delete JSON array element by "
+                              "setting to nil");
+        goto out;
+
+    default:
+        return luaL_error(L, "invalid value for JSON member");
+    }
+
+    if (val == NULL)
+        return luaL_error(L, "failed convert Lua value to JSON object");
+
+    if (key != NULL)
+        mrp_json_add(json, key, val);
+    else
+        if (!mrp_json_array_set(json, idx, val))
+            return luaL_error(L, "failed to set set JSON array element [%d]",
+                              idx);
+
+ out:
+    lua_pop(L, 3);
+
+    return 0;
+}
+
+
+static int json_lua_stringify(lua_State *L)
+{
+    json_lua_t *lson = json_lua_check(L, 1);
+
+    lua_pushstring(L, mrp_json_object_to_string(lson->json));
+
+    return 1;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, JSON_LUA_CLASS,
+                             { "JSON", mrp_json_lua_create });
diff --git a/src/core/lua-bindings/lua-json.h b/src/core/lua-bindings/lua-json.h
new file mode 100644 (file)
index 0000000..54363af
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_JSON_H__
+#define __MURPHY_LUA_JSON_H__
+
+#include <murphy/common/json.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+/** Create a Lua JSON object with an empty JSON object. */
+int  mrp_json_lua_create(lua_State *L);
+
+/** Create a Lua JSON object, wrap the given JSON object, increase refcount. */
+void *mrp_json_lua_wrap(lua_State *L, mrp_json_t *json);
+
+/** Get and add a reference to a wrapped JSON object. */
+mrp_json_t *mrp_json_lua_unwrap(void *lson);
+
+/** Wrap the given JSON object, increase refcount and push it on the stack. */
+int mrp_json_lua_push(lua_State *L, mrp_json_t *json);
+
+/** Get the JSON object at the given stack position, increase refcount. */
+mrp_json_t *mrp_json_lua_get(lua_State *L, int idx);
+
+#endif /* __MURPHY_LUA_JSON_H__ */
diff --git a/src/core/lua-bindings/lua-log.c b/src/core/lua-bindings/lua-log.c
new file mode 100644 (file)
index 0000000..89ddb50
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/log.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+static int call_function(lua_State *L, const char *table, const char *method)
+{
+    int n, type, status;
+
+    if (table != NULL)
+        lua_getglobal(L, table);
+
+    if (lua_istable(L, -1)) {
+        lua_getfield(L, -1, method);
+        lua_remove(L, -2);
+
+        if (lua_isfunction(L, -1)) {
+            n = lua_gettop(L);
+            lua_insert(L, 1);
+
+            status = lua_pcall(L, n - 1, 1, 0);
+        }
+        else {
+            type = lua_type(L, -1);
+            lua_pop(L, 1);
+
+            if (type == LUA_TNIL)
+                lua_pushfstring(L, "non-existent member %s", method);
+            else
+                lua_pushfstring(L, "member %s is not a function", method);
+
+            status = -1;
+        }
+    }
+    else {
+        if (table != NULL)
+            lua_pushfstring(L, "%s is not a table", table);
+        else
+            lua_pushfstring(L, "requested field %s of a non-table", method);
+
+        status = -1;
+    }
+
+
+    return status;
+}
+
+
+static int log_msg(lua_State *L, int level)
+{
+    static int  loaded = FALSE;
+    int         n      = lua_gettop(L);
+    lua_Debug   caller;
+    const char *file, *func;
+    int         line;
+    int         top;
+
+    top = lua_gettop(L);
+
+    if (!loaded) {
+        luaopen_string(L);
+        loaded = TRUE;
+    }
+
+    if (lua_isuserdata(L, 1)) {
+        lua_remove(L, 1);                /* remove self if any */
+        n--;
+    }
+
+    if (n > 1)
+        if (call_function(L, "string", "format") != 0)
+            goto out;
+
+    lua_getstack(L, 1, &caller);
+    if (lua_getinfo(L, "Snl", &caller)) {
+        func = caller.name   ? caller.name   : "<lua-function>";
+        file = caller.source ? caller.source : "<lua-source>";
+        line = caller.currentline;
+    }
+    else {
+        func = "<lua-function>";
+        line = 0;
+        file = "<lua-source>";
+    }
+
+    mrp_log_msg(level, file, line, func, "%s", lua_tostring(L, 1));
+
+ out:
+    lua_settop(L, top);
+
+    return 0;
+}
+
+
+static int log_info(lua_State *L)
+{
+    return log_msg(L, MRP_LOG_INFO);
+}
+
+
+static int log_warning(lua_State *L)
+{
+    return log_msg(L, MRP_LOG_WARNING);
+
+}
+
+
+static int log_error(lua_State *L)
+{
+    return log_msg(L, MRP_LOG_ERROR);
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, NULL,
+                             { "info"   , log_info    },
+                             { "warning", log_warning },
+                             { "error"  , log_error   });
diff --git a/src/core/lua-bindings/lua-lua.c b/src/core/lua-bindings/lua-lua.c
new file mode 100644 (file)
index 0000000..67720d0
--- /dev/null
@@ -0,0 +1,213 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/core/plugin.h>
+
+#include <murphy/core/lua-utils/include.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+static MRP_LIST_HOOK(included);
+static int include_disabled;
+
+static int include_lua(lua_State *L, const char *file, int try, int once)
+{
+    mrp_list_hook_t *files = once ? &included : NULL;
+    const char      *dirs[2];
+
+    dirs[0] = mrp_lua_get_murphy_lua_config_dir();
+    dirs[1] = NULL;
+
+    if (mrp_lua_include_file(L, file, &dirs[0], files) == 0)
+        return 0;
+
+    if (try) {
+        if (errno == EINVAL) {
+            if (lua_type(L, -1) == LUA_TSTRING) {
+                mrp_log_warning("inclusion of '%s' failed with error '%s'",
+                                file, lua_tostring(L, -1));
+            }
+        }
+
+        return 0;
+    }
+
+    return -1;
+
+}
+
+
+static int include_lua_file(lua_State *L, int try, int once)
+{
+    const char *file;
+    int         narg, status;
+
+    if (include_disabled)
+        return luaL_error(L, "Lua inclusion is disabled.");
+
+    narg = lua_gettop(L);
+
+    switch (narg) {
+    case 1:
+        if (lua_type(L, -1) != LUA_TSTRING)
+            return luaL_error(L, "expecting <string> for inclusion");
+        break;
+    case 2:
+        if (lua_type(L, -2) != LUA_TUSERDATA ||
+            lua_type(L, -1) != LUA_TSTRING)
+            return luaL_error(L, "expecting <murphy>, <string> for inclusion");
+        break;
+    default:
+        return luaL_error(L, "expecting <string> for inclusion");
+    }
+
+    file = lua_tostring(L, -1);
+
+    status = include_lua(L, file, try, once);
+
+    if (status == 0 || try) {
+        lua_settop(L, 0);
+        return 0;
+    }
+    else {
+        mrp_log_error("failed to include%s Lua file '%s'.",
+                      once ? "_once" : "", file);
+
+        return luaL_error(L, "failed to include file '%s' (%s)", file,
+                          lua_type(L, -1) == LUA_TSTRING ?
+                          lua_tostring(L, -1) : "<unknown error>");
+    }
+}
+
+
+static int try_luafile(lua_State *L)
+{
+    return include_lua_file(L, TRUE, FALSE);
+}
+
+
+static int try_once_luafile(lua_State *L)
+{
+    return include_lua_file(L, TRUE, TRUE);
+}
+
+
+static int include_luafile(lua_State *L)
+{
+    return include_lua_file(L, FALSE, FALSE);
+}
+
+
+static int include_once_luafile(lua_State *L)
+{
+    return include_lua_file(L, FALSE, TRUE);
+}
+
+
+static int disable_include(lua_State *L)
+{
+    MRP_UNUSED(L);
+
+    include_disabled = TRUE;
+
+    return 0;
+}
+
+
+static int open_lualib(lua_State *L)
+{
+    struct {
+        const char  *name;
+        int        (*loader)(lua_State *L);
+    } *lib, libs[] = {
+        { "math"   , luaopen_math    },
+        { "string" , luaopen_string  },
+        { "io"     , luaopen_io      },
+        { "os"     , luaopen_os      },
+        { "table"  , luaopen_table   },
+        { "debug"  , luaopen_debug   },
+        { "package", luaopen_package },
+        { "base"   , luaopen_base    },
+        { NULL     , NULL }
+    };
+    const char *name;
+    int         i, n;
+
+    n = lua_gettop(L);
+
+    if (lua_isuserdata(L, 1)) {
+        lua_remove(L, 1);                /* remove self if any */
+        n--;
+    }
+
+    if (n < 1)
+        return luaL_error(L, "%s called without any arguments", __FUNCTION__);
+
+    for (i = 1; i <= n; i++) {
+        luaL_checktype(L, 1, LUA_TSTRING);
+
+        name = lua_tostring(L, i);
+
+        for (lib = libs; lib->name != NULL; lib++)
+            if (!strcmp(lib->name, name))
+                break;
+
+        if (lib->loader != NULL) {
+            mrp_debug("loading Lua lib '%s' with %p...", name, lib->loader);
+            lib->loader(L);
+        }
+        else {
+            if (include_disabled)
+                return luaL_error(L, "Lua inclusion is disabled.");
+
+            if (include_lua(L, name, FALSE, TRUE) < 0)
+                return luaL_error(L, "failed to load unknown "
+                                  "Lua library '%s'", name);
+        }
+    }
+
+    lua_settop(L, 0);
+    return 0;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, NULL,
+                             { "open_lualib"     , open_lualib          },
+                             { "include"         , include_luafile      },
+                             { "include_once"    , include_once_luafile },
+                             { "try_include"     , try_luafile          },
+                             { "try_include_once", try_once_luafile     },
+                             { "disable_include" , disable_include      });
diff --git a/src/core/lua-bindings/lua-murphy.c b/src/core/lua-bindings/lua-murphy.c
new file mode 100644 (file)
index 0000000..5121b9f
--- /dev/null
@@ -0,0 +1,608 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-decision/mdb.h>
+#include <murphy/core/lua-decision/element.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+static mrp_context_t *context;
+static MRP_LIST_HOOK(bindings);
+static MRP_LIST_HOOK(pending);
+static int debug_level;
+static char *config_file;
+static char *config_dir;
+
+static lua_Alloc setup_allocator(void);
+
+
+static int create_murphy_object(lua_State *L)
+{
+    mrp_lua_murphy_t *m;
+
+    m = (mrp_lua_murphy_t *)lua_newuserdata(L, sizeof(*m));
+
+    m->ctxp = &context;
+
+    luaL_getmetatable(L, "murphy");
+    lua_setmetatable(L, -2);
+
+    return 1;
+}
+
+
+static int register_murphy(mrp_context_t *ctx)
+{
+    static luaL_reg functions[] = {
+        { "get", create_murphy_object },
+        { NULL , NULL                 }
+    };
+    lua_State *L = ctx->lua_state;
+
+    luaL_newmetatable(L, "murphy");
+    lua_pushliteral(L, "__index");       /* murphy.__index = murphy */
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);
+
+    luaL_openlib(L, "murphy", functions, 0);
+
+    return TRUE;
+}
+
+
+static int register_bindings(mrp_lua_bindings_t *b)
+{
+    lua_State *L = context->lua_state;
+    luaL_reg  *m;
+
+    luaL_getmetatable(L, b->meta);
+
+    for (m = b->methods; m->name != NULL; m++) {
+        lua_pushstring(L, m->name);
+        lua_pushcfunction(L, m->func);
+        lua_rawset(L, -3);
+    }
+
+    if (b->classdef != NULL) {
+        if (mrp_lua_create_object_class(L, b->classdef) < 0) {
+            mrp_log_error("Object class registration failed.");
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+int mrp_lua_register_murphy_bindings(mrp_lua_bindings_t *b)
+{
+    mrp_context_t   *ctx;
+    lua_State       *L;
+
+    mrp_list_init(&b->hook);
+    mrp_list_append(&bindings, &b->hook);
+
+    if ((ctx = context) != NULL && (L = ctx->lua_state) != NULL)
+        return register_bindings(b);
+    else
+        return TRUE;
+}
+
+
+static void init_lua_utils(lua_State *L)
+{
+    mrp_create_funcbridge_class(L);
+    mrp_create_funcarray_class(L);
+}
+
+
+static void init_lua_decision(lua_State *L)
+{
+    mrp_lua_create_mdb_class(L);
+    mrp_lua_create_element_class(L);
+}
+
+
+static lua_State *init_lua(void)
+{
+    lua_Alloc  A = setup_allocator();
+    lua_State *L;
+
+    if (A == NULL)
+        L = luaL_newstate();
+    else
+        L = lua_newstate(A, NULL);
+
+    if (L != NULL) {
+        luaopen_base(L);
+        init_lua_utils(L);
+        init_lua_decision(L);
+    }
+
+    return L;
+}
+
+
+lua_State *mrp_lua_set_murphy_context(mrp_context_t *ctx)
+{
+    lua_State          *L;
+    mrp_list_hook_t    *p, *n;
+    mrp_lua_bindings_t *b;
+    int                 success;
+
+    if (context == NULL) {
+        L = init_lua();
+
+        if (L != NULL) {
+            ctx->lua_state = L;
+            context        = ctx;
+
+            if (register_murphy(ctx)) {
+                success = TRUE;
+
+                init_lua_utils(L);
+                init_lua_decision(L);
+
+                mrp_list_foreach(&bindings, p, n) {
+                    b = mrp_list_entry(p, typeof(*b), hook);
+                    success &= register_bindings(b);
+                }
+
+                return L;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+void mrp_lua_set_murphy_lua_config_file(const char *path)
+{
+    if (config_file == NULL && path != NULL) {
+        config_file = mrp_strdup(path);
+        mrp_log_info("Lua config file is: '%s'.", config_file);
+    }
+}
+
+
+mrp_context_t *mrp_lua_check_murphy_context(lua_State *L, int index)
+{
+    mrp_lua_murphy_t *m;
+
+    m = (mrp_lua_murphy_t *)luaL_checkudata(L, index, "murphy");
+    luaL_argcheck(L, m, index, "murphy object expected");
+
+    if (*m->ctxp == NULL)
+        return (void *)(ptrdiff_t)luaL_error(L, "murphy context is not set");
+    else
+        return *m->ctxp;
+}
+
+
+mrp_context_t *mrp_lua_get_murphy_context(void)
+{
+    return context;
+}
+
+
+lua_State *mrp_lua_get_lua_state(void)
+{
+    if (context != NULL)
+        return context->lua_state;
+    else
+        return NULL;
+}
+
+const char *mrp_lua_get_murphy_lua_config_dir(void)
+{
+    char   *base, dir[PATH_MAX];
+    size_t  offs, len;
+
+    if (config_file == NULL)
+        return NULL;
+
+    if (config_dir == NULL) {
+        if (config_file[0] != '/') {
+            if (getcwd(dir, sizeof(dir)) == NULL)
+                return NULL;
+            else {
+                offs = strlen(dir);
+
+                if (offs >= sizeof(dir) - 1)
+                    return NULL;
+
+                dir[offs++] = '/';
+                dir[offs]   = '\0';
+            }
+        }
+        else
+            offs = 0;
+
+        base = strrchr(config_file, '/');
+
+        if (base != NULL)
+            while (base > config_file && base[-1] == '/')
+                base--;
+
+        if (base != NULL && base > config_file) {
+            len = base - config_file;
+
+            if (sizeof(dir) - offs - 1 <= len)
+                return NULL;
+
+            strncpy(dir + offs, config_file, len);
+            offs += len;
+            dir[offs] = '\0';
+
+            config_dir = mrp_strdup(dir);
+
+            mrp_log_info("Lua config directory is '%s'.", config_dir);
+        }
+    }
+
+    return config_dir;
+}
+
+
+/*
+ * runtime debugging
+ */
+
+void mrp_lua_dump_stack(lua_State *L, const char *prefix)
+{
+    char prebuf[256];
+    int  i, n;
+
+    n = lua_gettop(L);
+
+    if (prefix != NULL && *prefix) {
+        snprintf(prebuf, sizeof(prebuf), "%s: ", prefix);
+        prefix = prebuf;
+    }
+    else
+        prefix = "";
+
+    if (n > 0) {
+        mrp_debug("%sLua stack dump (%d items):", prefix, n);
+
+        for (i = 1; i <= n; i++)
+            mrp_debug("%s#%d(%d): %s", prefix, -i, (n - i) + 1,
+                      lua_typename(L, lua_type(L, -i)));
+    }
+    else
+        mrp_debug("%sLua stack is empty", prefix);
+}
+
+
+static void lua_debug(lua_State *L, lua_Debug *ar)
+{
+#define RUNNING(_ar, _what) ((_ar)->what != NULL && !strcmp((_ar)->what, _what))
+#define ALIGNFMT "%*.*s"
+#define ALIGNARG 4 * depth, 4 * depth, ""
+
+    static int depth = 0;
+
+    lua_Debug   f;
+    const char *type, *name;
+    char        loc[1024];
+
+    switch (ar->event) {
+    case LUA_HOOKRET:
+        depth--;
+        mrp_debug(ALIGNFMT"<= return", ALIGNARG);
+        break;
+
+#ifdef LUA_HOOKTAILRET
+    case LUA_HOOKTAILRET:
+        depth--;
+        mrp_debug(ALIGNFMT"<= tail return", ALIGNARG);
+        break;
+#endif
+
+    case LUA_HOOKCALL:
+#ifdef LUA_HOOKTAILCALL
+    case LUA_HOOKTAILCALL:
+#endif
+
+        mrp_clear(&f);
+        if (lua_getstack(L, 1, &f) && lua_getinfo(L, "Snl", &f)) {
+            if      (RUNNING(&f, "C"))    type = "Lua-C";
+            else if (RUNNING(&f, "Lua"))  type = "Lua";
+            else if (RUNNING(&f, "main")) type = "Lua-main";
+            else if (RUNNING(&f, "tail")) {
+                mrp_debug(ALIGNFMT"<=> tail-call", ALIGNARG);
+#ifndef LUA_HOOKTAILRET
+                depth++;
+#endif
+                return;
+            }
+            else
+                type = "???";
+
+            name = f.name ? f.name : NULL;
+
+            if (f.currentline != -1 && f.short_src[0])
+                snprintf(loc, sizeof(loc), "@ %s:%d", f.short_src,
+                         f.currentline);
+            else
+                loc[0] = '\0';
+
+            if (name)
+                mrp_debug(ALIGNFMT"=> %s %s %s", ALIGNARG, type, name, loc);
+            else
+                mrp_debug(ALIGNFMT"=> %s %s", ALIGNARG, type, loc);
+        }
+        else
+            mrp_debug(ALIGNFMT"=> Lua", ALIGNARG);
+
+        depth++;
+        break;
+
+    case LUA_HOOKLINE:
+        mrp_clear(&f);
+
+        if (lua_getstack(L, 1, &f) && lua_getinfo(L, "Snl", &f))
+            mrp_debug(ALIGNFMT" @ %s:%d", ALIGNARG, f.short_src, f.currentline);
+        else
+            mrp_debug(ALIGNFMT" @ line %d", ALIGNARG, ar->currentline);
+        break;
+
+    default:
+        break;
+    }
+
+#undef RUNNING
+#undef ALIGNFMT
+#undef ALIGNARG
+}
+
+
+static int setup_debug_hook(int mask)
+{
+    mrp_context_t *ctx     = mrp_lua_get_murphy_context();
+    lua_State     *L       = ctx ? ctx->lua_state : NULL;
+
+    return (L != NULL && lua_sethook(L, lua_debug, mask, 0));
+}
+
+
+static void clear_debug_hook(void)
+{
+    mrp_context_t *ctx = mrp_lua_get_murphy_context();
+    lua_State     *L   = ctx ? ctx->lua_state : NULL;
+
+    if (L != NULL)
+        lua_sethook(L, lua_debug, 0, 0);
+}
+
+
+int mrp_lua_set_debug(mrp_lua_debug_t level)
+{
+    const char *cfg;
+    int         ena, mask, success;
+
+    if (debug_level)
+        clear_debug_hook();
+
+    mask = 0;
+
+    switch (level) {
+    case MRP_LUA_DEBUG_DISABLED:
+        ena = FALSE;
+        cfg = "-lua_debug";
+        break;
+
+    case MRP_LUA_DEBUG_DETAILED:
+        mask |= LUA_MASKLINE;
+    case MRP_LUA_DEBUG_ENABLED:
+        mask |= LUA_MASKCALL | LUA_MASKRET;
+        ena   = TRUE;
+        cfg   = "+lua_debug";
+        break;
+
+    default:
+        return FALSE;
+    }
+
+    if (ena) {
+        success = setup_debug_hook(mask);
+        mrp_debug_enable(TRUE);
+    }
+    else
+        success = TRUE;
+
+    mrp_debug_set_config(cfg);
+
+    if (success)
+        debug_level = level;
+
+    return success;
+}
+
+
+/*
+ * Lua memory allocation tracking
+ *
+ * This is intended for debugging and diagnostic purposes. By default
+ * tracking Lua allocations is off. Lua allocation tracking can be
+ * enabled by including lua=true in the environment variable controlling
+ * Murphy memory management (__MURPHY_MM_CONFIG).
+ */
+
+/*
+ * a tracked block of memory allocated for Lua by us
+ */
+
+#define MEMBLK_SIZE(lsize) \
+    ((lsize) ? ((void *)&((memblk_t *)NULL)->mem[(lsize)] - NULL) : 0)
+
+typedef struct {
+    mrp_list_hook_t hook;                /* hook to hash bucket */
+    char            mem[0];              /* memory passed on to Lua */
+} memblk_t;
+
+static MRP_LIST_HOOK(memblks);           /* allocated blocks */
+
+
+static inline void *memblk_store(memblk_t *blk)
+{
+    mrp_list_init(&blk->hook);
+    mrp_list_append(&memblks, &blk->hook);
+
+    return &blk->mem[0];
+}
+
+
+static void memblk_clear(memblk_t *blk)
+{
+    mrp_list_delete(&blk->hook);
+}
+
+
+static inline memblk_t *ptr_to_memblk(void *ptr)
+{
+    memblk_t *blk;
+
+    if (ptr != NULL)
+        blk = (memblk_t *)(((char *)ptr) - MRP_OFFSET(typeof(*blk), mem[0]));
+    else
+        blk = NULL;
+
+    return blk;
+}
+
+
+static inline void *memblk_to_ptr(memblk_t *blk)
+{
+    if (blk != NULL)
+        return (void *)&blk->mem[0];
+    else
+        return NULL;
+}
+
+
+static inline void *memblk_alloc(size_t lsize)
+{
+    memblk_t *blk;
+
+    blk = mrp_alloc(MEMBLK_SIZE(lsize));
+
+    if (blk != NULL) {
+        return memblk_store(blk);
+    }
+    else
+        return NULL;
+}
+
+
+static inline void *memblk_resize(memblk_t *blk, size_t osize, size_t nsize)
+{
+    memblk_clear(blk);
+
+    if (mrp_reallocz(blk, osize, nsize)) {
+        return memblk_store(blk);
+    }
+    else {
+        mrp_free(blk);
+        return NULL;
+    }
+}
+
+
+static inline void memblk_free(memblk_t *blk)
+{
+    if (blk != NULL) {
+        memblk_clear(blk);
+        mrp_free(blk);
+    }
+}
+
+
+static void *lua_alloc(void *ud, void *optr, size_t olsize, size_t nlsize)
+{
+    memblk_t *oblk;
+    size_t    obsize, nbsize;
+    void     *nptr;
+
+    MRP_UNUSED(ud);
+
+    mrp_debug("Lua allocation request <%p, %zd, %zd>", optr, olsize, nlsize);
+
+    oblk = ptr_to_memblk(optr);
+
+    if (nlsize > 0) {
+        nbsize = MEMBLK_SIZE(nlsize);
+
+        if (oblk != NULL) {
+            obsize = MEMBLK_SIZE(olsize);
+            nptr   = memblk_resize(oblk, obsize, nbsize);
+        }
+        else
+            nptr = memblk_alloc(nbsize);
+    }
+    else {
+        memblk_free(oblk);
+        nptr = NULL;
+    }
+
+    mrp_debug("Lua allocation reply %p", nptr);
+
+    return nptr;
+}
+
+
+static lua_Alloc setup_allocator(void)
+{
+    int debug;
+
+    debug = mrp_mm_config_bool("lua", FALSE);
+
+    if (!debug) {
+        mrp_debug("%s not set to debug*, using native Lua allocator",
+                  MRP_MM_CONFIG_ENVVAR);
+        return NULL;
+    }
+    else {
+        mrp_debug("Lua memory tracking enabled, overriding native allocator");
+
+        mrp_list_init(&memblks);
+        mrp_lua_track_objects(true);
+
+        return lua_alloc;
+    }
+}
diff --git a/src/core/lua-bindings/lua-plugin.c b/src/core/lua-bindings/lua-plugin.c
new file mode 100644 (file)
index 0000000..6604d81
--- /dev/null
@@ -0,0 +1,428 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+static int stringify_table(lua_State *L, int index,
+                           char **bufp, int *sizep, int *offsp);
+
+static int plugin_exists(lua_State *L)
+{
+    mrp_context_t *ctx;
+    const char    *name;
+
+    ctx = mrp_lua_check_murphy_context(L, 1);
+    luaL_checktype(L, 2, LUA_TSTRING);
+
+    name = lua_tostring(L, 2);
+
+    mrp_debug("lua: check if plugin '%s' exists", name);
+
+    lua_pushboolean(L, mrp_plugin_exists(ctx, name));
+
+    return 1;
+}
+
+
+static int plugin_loaded(lua_State *L)
+{
+    mrp_context_t *ctx;
+    const char    *name;
+
+    ctx = mrp_lua_check_murphy_context(L, 1);
+    luaL_checktype(L, 2, LUA_TSTRING);
+
+    name = lua_tostring(L, 2);
+
+    mrp_debug("lua: check if plugin '%s' is loaded", name);
+
+    lua_pushboolean(L, mrp_plugin_running(ctx, name));
+
+    return 1;
+}
+
+
+static int ensure_buffer(char **bufp, int *sizep, int *offsp, int len)
+{
+    int spc, size, diff;
+
+    spc = *sizep - *offsp;
+    if (spc < len) {
+        diff = len - spc;
+
+        if (diff > *sizep)
+            size = *sizep + diff;
+        else
+            size = *sizep * 2;
+
+        if (!mrp_realloc(*bufp, size))
+            return -1;
+        else
+            *sizep = size;
+    }
+
+    return 0;
+}
+
+
+static int push_buffer(char **bufp, int *sizep, int *offsp,
+                       const char *str, int len)
+{
+    if (len <= 0)
+        len = strlen(str);
+
+    if (ensure_buffer(bufp, sizep, offsp, len + 1) == 0) {
+        strcpy(*bufp + *offsp, str);
+        *offsp += len;
+
+        return 0;
+    }
+    else
+        return -1;
+}
+
+
+static int stringify_string(lua_State *L, int index,
+                            char **bufp, int *sizep, int *offsp)
+{
+    const char *str;
+    size_t      size;
+    int         len;
+
+    str = lua_tolstring(L, index, &size);
+    len = (int)size;
+
+    if (ensure_buffer(bufp, sizep, offsp, len + 3) < 0)
+        return -1;
+
+    snprintf(*bufp + *offsp, len + 3, "'%s'", str);
+    *offsp += len + 2;
+
+    return 0;
+}
+
+
+static int stringify_number(lua_State *L, int index,
+                                char **bufp, int *sizep, int *offsp)
+{
+    char   num[64];
+    double d;
+    int    i, len;
+
+    if ((d = lua_tonumber(L, index)) == 1.0 * (i = lua_tointeger(L, index)))
+        len = snprintf(num, sizeof(num), "%d", i);
+    else
+        len = snprintf(num, sizeof(num), "%f", d);
+
+    return push_buffer(bufp, sizep, offsp, num, len);
+}
+
+
+static int stringify_boolean(lua_State *L, int index,
+                                 char **bufp, int *sizep, int *offsp)
+{
+    const char *bln;
+    int         len;
+
+    if (lua_toboolean(L, index)) {
+        bln = "true";
+        len = 4;
+    }
+    else {
+        bln = "false";
+        len = 5;
+    }
+
+    return push_buffer(bufp, sizep, offsp, bln, len);
+}
+
+
+static int stringify_object(lua_State *L, int index,
+                            char **bufp, int *sizep, int *offsp)
+{
+    switch (lua_type(L, index)) {
+    case LUA_TSTRING:  return stringify_string(L, index, bufp, sizep, offsp);
+    case LUA_TNUMBER:  return stringify_number(L, index, bufp, sizep, offsp);
+    case LUA_TBOOLEAN: return stringify_boolean(L, index, bufp, sizep, offsp);
+    case LUA_TTABLE:   return stringify_table(L, index, bufp, sizep, offsp);
+    default:
+        errno = EINVAL;
+    }
+
+    return -1;
+}
+
+
+static int stringify_table(lua_State *L, int index,
+                           char **bufp, int *sizep, int *offsp)
+{
+    const char *sep, *p;
+    char        key[256];
+    int         arr;
+    int         i, d, idx, len;
+
+    arr = TRUE;
+
+    lua_pushnil(L);
+
+    /*
+     * check what the table should be converted to (array or dictionary)
+     */
+    idx = -1;
+    while (arr && lua_next(L, index - 1)) {
+        switch (lua_type(L, -2)) {
+        case LUA_TBOOLEAN:
+        case LUA_TTABLE:
+        default:
+        invalid:
+            lua_pop(L, 3);
+            return -1;
+
+        case LUA_TNUMBER:
+            d = lua_tonumber(L, -2);
+            i = lua_tointeger(L, -2);
+
+            if (d != 1.0 * i || (idx >= 0 && i != idx + 1))
+                goto invalid;
+            else
+                idx = i;
+            break;
+
+        case LUA_TSTRING:
+            lua_pop(L, 1);
+            arr = FALSE;
+            break;
+        }
+
+        lua_pop(L, 1);
+    }
+
+
+    /*
+     * convert either to an array or a dictionary
+     */
+
+    if (push_buffer(bufp, sizep, offsp, arr ? "[" : "{", 1))
+        return -1;
+
+    sep = "";
+    lua_pushnil(L);
+    while (lua_next(L, index - 1)) {
+        if (!arr) {
+            len = snprintf(key, sizeof(key), "%s'%s':", sep,
+                           lua_tostring(L, -2));
+            p = key;
+        }
+        else {
+            p   = (char *)sep;
+            len = strlen(sep);
+        }
+
+        if (push_buffer(bufp, sizep, offsp, p, len) < 0)
+            return -1;
+
+        if (stringify_object(L, -1, bufp, sizep, offsp) < 0) {
+            lua_pop(L, 3);
+            return -1;
+        }
+
+        lua_pop(L, 1);
+        sep = ",";
+    }
+
+    if (push_buffer(bufp, sizep, offsp, arr ? "]" : "}", 1) < 0)
+        return -1;
+    else
+        return 0;
+}
+
+
+static int load(lua_State *L, int may_fail)
+{
+    mrp_context_t    *ctx;
+    mrp_plugin_t     *plugin;
+    char              name[256], instbuf[256];
+    const char       *instance, *argerr;
+    mrp_plugin_arg_t  args[256];
+    int               narg, n, type, t, success;
+    char             *json;
+    int               size, offs;
+
+    ctx = mrp_lua_check_murphy_context(L, 1);
+    n   = lua_gettop(L);
+
+    if (n < 2 || n > 4)
+        return luaL_error(L, "%s called with incorrect arguments",
+                          __FUNCTION__);
+
+    luaL_checktype(L, 2, LUA_TSTRING);
+    snprintf(name, sizeof(name), "%s", lua_tostring(L, 2));
+    instance = NULL;
+    narg     = 0;
+
+    mrp_debug("lua: %sload-plugin '%s'", may_fail ? "try-" : "", name);
+
+    switch (n) {
+    case 2:
+        break;
+
+    case 3:
+        type = lua_type(L, 3);
+
+        if (type == LUA_TTABLE) {
+            t = 3;
+            goto parse_arguments;
+        }
+        else if (type == LUA_TSTRING) {
+            snprintf(instbuf, sizeof(instbuf), "%s", lua_tostring(L, 3));
+            instance = instbuf;
+        }
+        else
+            return luaL_error(L, "%s expects string or table as 2nd argument",
+                              __FUNCTION__);
+        break;
+
+    case 4:
+    default:
+        luaL_checktype(L, 3, LUA_TSTRING);
+        luaL_checktype(L, 4, LUA_TTABLE);
+        snprintf(instbuf, sizeof(instbuf), "%s", lua_tostring(L, 3));
+        instance = instbuf;
+        t        = 4;
+    parse_arguments:
+        mrp_clear(&args);
+        lua_pushnil(L);
+        while (lua_next(L, t) != 0) {
+            if (narg >= (int)MRP_ARRAY_SIZE(args)) {
+                argerr = "too many plugin arguments";
+                goto arg_error;
+            }
+
+            if (lua_type(L, -2) != LUA_TSTRING) {
+                argerr = "non-string argument table key";
+                goto arg_error;
+            }
+
+            args[narg].type = MRP_PLUGIN_ARG_TYPE_STRING;
+            args[narg].key  = mrp_strdup(lua_tostring(L, -2));
+
+            switch (lua_type(L, -1)) {
+            case LUA_TSTRING:
+            case LUA_TNUMBER:
+                args[narg].str = mrp_strdup(lua_tostring(L, -1));
+                break;
+            case LUA_TBOOLEAN:
+                args[narg].str = mrp_strdup(lua_toboolean(L, -1) ?
+                                            "true" : "false");
+                break;
+            case LUA_TTABLE:
+                json = NULL;
+                size = 0;
+                offs = 0;
+                if (stringify_table(L, -1, &json, &size, &offs) == 0)
+                    args[narg].str = json;
+                else {
+                    argerr = "failed to json-stringify Lua table";
+                    goto arg_error;
+                }
+                break;
+            default:
+                argerr = "invalid argument table value";
+                goto arg_error;
+            }
+            mrp_debug("lua: argument #%d: '%s' = '%s'", narg,
+                      args[narg].key, args[narg].str);
+            narg++;
+
+            lua_pop(L, 1);
+        }
+        break;
+    }
+
+    plugin = mrp_load_plugin(ctx, name, instance, narg ? args : NULL, narg);
+
+    if (plugin != NULL) {
+        plugin->may_fail = may_fail;
+
+        success = TRUE;
+    }
+    else {
+        success = FALSE;
+
+        if (!may_fail)
+            return luaL_error(L, "failed to load plugin %s (as instance %s)",
+                              name, instance ? instance : name);
+    }
+
+    while (narg > 0) {
+        mrp_free(args[narg - 1].key);
+        mrp_free(args[narg - 1].str);
+        narg--;
+    }
+
+    lua_pushboolean(L, success);
+    return 1;
+
+ arg_error:
+    while (narg > 0) {
+        mrp_free(args[narg - 1].key);
+        mrp_free(args[narg - 1].str);
+        narg--;
+    }
+
+    return luaL_error(L, "plugin argument table error: %s", argerr);
+}
+
+
+static int load_plugin(lua_State *L)
+{
+    return load(L, FALSE);
+}
+
+
+static int try_load_plugin(lua_State *L)
+{
+    return load(L, TRUE);
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, NULL,
+                             { "plugin_exists"  , plugin_exists   },
+                             { "plugin_loaded"  , plugin_loaded   },
+                             { "load_plugin"    , load_plugin     },
+                             { "try_load_plugin", try_load_plugin });
diff --git a/src/core/lua-bindings/lua-sighandler.c b/src/core/lua-bindings/lua-sighandler.c
new file mode 100644 (file)
index 0000000..be1caa1
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#define SIGHANDLER_LUA_CLASS MRP_LUA_CLASS(sighandler, lua)
+
+/*
+ * Lua sighandler object
+ */
+
+typedef struct {
+    lua_State        *L;                 /* Lua execution context */
+    mrp_mainloop_t   *ml;                /* Murphy mainloop */
+    mrp_sighandler_t *h;                 /* associated murphy sighandler */
+    int               signum;            /* signal number */
+    int               callback;          /* reference to callback */
+    bool              oneshot;           /* true for one-shot sighandlers */
+} sighandler_lua_t;
+
+
+static int sighandler_lua_create(lua_State *L);
+static void sighandler_lua_destroy(void *data);
+static void sighandler_lua_changed(void *data, lua_State *L, int member);
+static ssize_t sighandler_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                       size_t size, lua_State *L, void *data);
+static int sighandler_lua_enable(lua_State *L);
+static int sighandler_lua_disable(lua_State *L);
+
+/*
+ * Lua sighandler class
+ */
+
+#define OFFS(m) MRP_OFFSET(sighandler_lua_t, m)
+#define RDONLY  MRP_LUA_CLASS_READONLY
+#define NOTIFY  MRP_LUA_CLASS_NOTIFY
+#define NOFLAGS MRP_LUA_CLASS_NOFLAGS
+
+MRP_LUA_METHOD_LIST_TABLE(sighandler_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(sighandler_lua_create)
+                          MRP_LUA_METHOD(disable, sighandler_lua_disable)
+                          MRP_LUA_METHOD(enable , sighandler_lua_enable));
+
+MRP_LUA_METHOD_LIST_TABLE(sighandler_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (sighandler_lua_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(sighandler_lua_members,
+    MRP_LUA_CLASS_INTEGER("signal"   , OFFS(signum)   , NULL, NULL, RDONLY )
+    MRP_LUA_CLASS_LFUNC  ("callback" , OFFS(callback) , NULL, NULL, NOFLAGS)
+    MRP_LUA_CLASS_BOOLEAN("oneshot"  , OFFS(oneshot)  , NULL, NULL, NOFLAGS));
+
+typedef enum {
+    SIGHANDLER_MEMBER_SIGNAL,
+    SIGHANDLER_MEMBER_CALLBACK,
+    SIGHANDLER_MEMBER_ONESHOT,
+} sighandler_member_t;
+
+MRP_LUA_DEFINE_CLASS(sighandler, lua, sighandler_lua_t, sighandler_lua_destroy,
+                     sighandler_lua_methods, sighandler_lua_overrides,
+                     sighandler_lua_members, NULL, sighandler_lua_changed,
+                     sighandler_lua_tostring, NULL,
+                     MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_DYNAMIC);
+
+
+static void sighandler_lua_cb(mrp_sighandler_t *hlr, int sig, void *user_data)
+{
+    sighandler_lua_t *h   = (sighandler_lua_t *)user_data;
+    int               one = h->oneshot;
+    const char       *s   = strsignal(sig);
+    int               top;
+
+    MRP_UNUSED(hlr);
+
+    top = lua_gettop(h->L);
+
+    if (mrp_lua_object_deref_value(h, h->L, h->callback, false)) {
+        mrp_lua_push_object(h->L, h);
+        if (s != NULL)
+            lua_pushstring(h->L, s);
+        else
+            lua_pushinteger(h->L, sig);
+
+        if (lua_pcall(h->L, 2, 0, 0) != 0)
+            mrp_log_error("failed to invoke Lua sighandler callback");
+    }
+
+    if (one) {
+        mrp_del_sighandler(h->h);
+        h->h = NULL;
+    }
+
+    lua_settop(h->L, top);
+}
+
+
+static void sighandler_lua_changed(void *data, lua_State *L, int member)
+{
+    sighandler_lua_t *h = (sighandler_lua_t *)data;
+
+    MRP_UNUSED(L);
+    MRP_UNUSED(h);
+
+    mrp_debug("sighandler member #%d (%s) changed", member,
+              sighandler_lua_members[member].name);
+}
+
+
+static int sighandler_lua_create(lua_State *L)
+{
+
+    mrp_context_t    *ctx    = mrp_lua_get_murphy_context();
+    char              e[128] = "";
+    sighandler_lua_t *h;
+    int               narg;
+
+    if (ctx == NULL)
+        luaL_error(L, "failed to get murphy context");
+
+    narg = lua_gettop(L);
+
+    h = (sighandler_lua_t *)mrp_lua_create_object(L, SIGHANDLER_LUA_CLASS,
+                                                  NULL, 0);
+    h->L        = L;
+    h->ml       = ctx->ml;
+    h->callback = LUA_NOREF;
+
+    switch (narg) {
+    case 1:
+        break;
+    case 2:
+        if (mrp_lua_init_members(h, L, -2, e, sizeof(e)) != 1)
+            return luaL_error(L, "failed to initialize sighandler (%s)", e);
+        break;
+    default:
+        return luaL_error(L, "expecting 0 or 1 arguments, got %d", narg);
+    }
+
+    if (h->signum)
+        h->h = mrp_add_sighandler(h->ml, h->signum, sighandler_lua_cb, h);
+    else
+        return luaL_error(L, "signal number must be set in constructor");
+
+    if (h->h == NULL)
+        return luaL_error(L, "failed to create Murphy sighandler");
+
+    return 1;
+}
+
+
+static void sighandler_lua_destroy(void *data)
+{
+    sighandler_lua_t *h = (sighandler_lua_t *)data;
+
+    mrp_debug("destroying Lua sighandler %p", data);
+
+    mrp_del_sighandler(h->h);
+    h->h = NULL;
+
+    mrp_lua_object_unref_value(h, h->L, h->callback);
+
+    h->callback = LUA_NOREF;
+}
+
+
+static sighandler_lua_t *sighandler_lua_check(lua_State *L, int idx)
+{
+    return (sighandler_lua_t *)mrp_lua_check_object(L,
+                                                    SIGHANDLER_LUA_CLASS, idx);
+}
+
+
+static ssize_t sighandler_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                       size_t size, lua_State *L, void *data)
+{
+    sighandler_lua_t *h = (sighandler_lua_t *)data;
+    const char       *s = strsignal(h->signum);
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{%ssighandler %p of '%s'}",
+                        h->oneshot  ? "oneshot " : "", h->h,
+                        s ? s : "unknow signal");
+    }
+}
+
+
+static int sighandler_lua_enable(lua_State *L)
+{
+    sighandler_lua_t *h = sighandler_lua_check(L, -1);
+
+    if (h == NULL) {
+        lua_pushboolean(L, false);
+        return 1;
+    }
+
+    if (h->h == NULL && h->signum != 0)
+        if (h->callback != LUA_NOREF && h->callback != LUA_REFNIL)
+            mrp_add_sighandler(h->ml, h->signum, sighandler_lua_cb, h);
+
+    lua_pushboolean(L, h->h != NULL);
+
+    return 1;
+}
+
+
+static int sighandler_lua_disable(lua_State *L)
+{
+    sighandler_lua_t *h = sighandler_lua_check(L, -1);
+
+    if (h == NULL) {
+        lua_pushboolean(L, false);
+        return 1;
+    }
+
+    mrp_del_sighandler(h->h);
+    h->h = NULL;
+
+    lua_pushboolean(L, true);
+
+    return 1;
+}
+
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, SIGHANDLER_LUA_CLASS,
+                             { "SigHandler", sighandler_lua_create });
diff --git a/src/core/lua-bindings/lua-timer.c b/src/core/lua-bindings/lua-timer.c
new file mode 100644 (file)
index 0000000..eb87494
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+/*
+ * Lua timer object
+ */
+
+#define TIMER_LUA_CLASS MRP_LUA_CLASS(timer, lua)
+
+typedef struct {
+    lua_State     *L;                    /* Lua execution context */
+    mrp_context_t *ctx;                  /* murphy context */
+    mrp_timer_t   *t;                    /* associated murphy timer */
+    unsigned int   msecs;                /* timer interval in milliseconds */
+    int            callback;             /* reference to callback */
+    bool           oneshot;              /* true for one-shot timers */
+} timer_lua_t;
+
+
+static int timer_lua_create(lua_State *L);
+static void timer_lua_destroy(void *data);
+static void timer_lua_changed(void *data, lua_State *L, int member);
+static ssize_t timer_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                  size_t size, lua_State *L, void *data);
+static int timer_lua_start(lua_State *L);
+static int timer_lua_stop(lua_State *L);
+
+
+/*
+ * Lua timer class
+ */
+
+#define OFFS(m) MRP_OFFSET(timer_lua_t, m)
+#define RDONLY  MRP_LUA_CLASS_READONLY
+#define NOTIFY  MRP_LUA_CLASS_NOTIFY
+#define NOFLAGS MRP_LUA_CLASS_NOFLAGS
+
+MRP_LUA_METHOD_LIST_TABLE(timer_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(timer_lua_create)
+                          MRP_LUA_METHOD(stop , timer_lua_stop)
+                          MRP_LUA_METHOD(start, timer_lua_start));
+
+MRP_LUA_METHOD_LIST_TABLE(timer_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (timer_lua_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(timer_lua_members,
+    MRP_LUA_CLASS_INTEGER("interval", OFFS(msecs)   , NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_LFUNC  ("callback", OFFS(callback), NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_BOOLEAN("oneshot" , OFFS(oneshot) , NULL, NULL, NOTIFY));
+
+
+typedef enum {
+    TIMER_MEMBER_INTERVAL,
+    TIMER_MEMBER_CALLBACK,
+    TIMER_MEMBER_ONESHOT
+} timer_member_t;
+
+MRP_LUA_DEFINE_CLASS(timer, lua, timer_lua_t, timer_lua_destroy,
+                     timer_lua_methods, timer_lua_overrides,
+                     timer_lua_members, NULL, timer_lua_changed,
+                     timer_lua_tostring, NULL,
+                     MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_DYNAMIC);
+
+
+static void timer_lua_cb(mrp_timer_t *timer, void *user_data)
+{
+    timer_lua_t *t   = (timer_lua_t *)user_data;
+    int          one = t->oneshot;
+    int          top;
+
+    MRP_UNUSED(timer);
+
+    top = lua_gettop(t->L);
+
+    if (mrp_lua_object_deref_value(t, t->L, t->callback, false)) {
+        mrp_lua_push_object(t->L, t);
+
+        if (lua_pcall(t->L, 1, 0, 0) != 0) {
+            mrp_log_error("failed to invoke Lua timer callback, stopping");
+            mrp_del_timer(t->t);
+            t->t = NULL;
+        }
+    }
+
+    if (one) {
+        mrp_del_timer(t->t);
+        t->t = NULL;
+    }
+
+    lua_settop(t->L, top);
+}
+
+
+static void timer_lua_changed(void *data, lua_State *L, int member)
+{
+    timer_lua_t *t = (timer_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("timer member #%d (%s) changed", member,
+              timer_lua_members[member].name);
+
+    switch (member) {
+    case TIMER_MEMBER_INTERVAL:
+        if (t->t != NULL)
+            mrp_mod_timer(t->t, t->msecs);
+        else {
+        enable:
+            t->t = mrp_add_timer(t->ctx->ml, t->msecs, timer_lua_cb, t);
+            if (t->t == NULL)
+                luaL_error(L, "failed to create Murphy timer");
+        }
+        break;
+
+    case TIMER_MEMBER_CALLBACK:
+        if (t->callback == LUA_NOREF || t->callback == LUA_REFNIL) {
+            mrp_del_timer(t->t);
+            t->t = NULL;
+        }
+        else {
+            if (t->t == NULL)
+                goto enable;
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static int timer_lua_create(lua_State *L)
+{
+
+    mrp_context_t *ctx    = mrp_lua_get_murphy_context();
+    char           e[128] = "";
+    timer_lua_t   *t;
+    int            narg;
+
+    if (ctx == NULL)
+        luaL_error(L, "failed to get murphy context");
+
+    narg = lua_gettop(L);
+
+    t = (timer_lua_t *)mrp_lua_create_object(L, TIMER_LUA_CLASS, NULL, 0);
+
+    t->L        = L;
+    t->ctx      = ctx;
+    t->callback = LUA_NOREF;
+    t->msecs    = 5000;
+
+    switch (narg) {
+    case 1:
+        break;
+    case 2:
+        if (mrp_lua_init_members(t, L, -2, e, sizeof(e)) != 1)
+            return luaL_error(L, "failed to initialize timer members (%s)", e);
+        break;
+    default:
+        return luaL_error(L, "expecting 0 or 1 constructor arguments, "
+                          "got %d", narg);
+    }
+
+    if (t->callback != LUA_NOREF && t->callback != LUA_REFNIL && t->t == NULL) {
+        t->t = mrp_add_timer(t->ctx->ml, t->msecs, timer_lua_cb, t);
+
+        if (t->t == NULL)
+            return luaL_error(L, "failed to create Murphy timer");
+    }
+
+    return 1;
+}
+
+
+static void timer_lua_destroy(void *data)
+{
+    timer_lua_t *t = (timer_lua_t *)data;
+
+    mrp_debug("destroying Lua timer %p", data);
+
+    mrp_del_timer(t->t);
+    t->t = NULL;
+
+    mrp_lua_object_unref_value(t, t->L, t->callback);
+
+    t->callback = LUA_NOREF;
+}
+
+
+static timer_lua_t *timer_lua_check(lua_State *L, int idx)
+{
+    return (timer_lua_t *)mrp_lua_check_object(L, TIMER_LUA_CLASS, idx);
+}
+
+
+static ssize_t timer_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                  size_t size, lua_State *L, void *data)
+{
+    timer_lua_t *t = (timer_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{%s%stimer %p @ %d msecs}",
+                        t->t        ? ""         : "disabled ",
+                        t->oneshot  ? "oneshot " : "",
+                        t->t, t->msecs);
+    }
+}
+
+
+static int timer_lua_start(lua_State *L)
+{
+    timer_lua_t *t = timer_lua_check(L, -1);
+
+    if (t == NULL) {
+        lua_pushboolean(L, false);
+        return 1;
+    }
+
+    if (t->t == NULL && t->callback != LUA_NOREF)
+        t->t = mrp_add_timer(t->ctx->ml, t->msecs, timer_lua_cb, t);
+
+    lua_pushboolean(L, t->t != NULL);
+
+    return 1;
+}
+
+
+static int timer_lua_stop(lua_State *L)
+{
+    timer_lua_t *t = timer_lua_check(L, -1);
+
+    if (t == NULL) {
+        lua_pushboolean(L, false);
+        return 1;
+    }
+
+    mrp_del_timer(t->t);
+    t->t = NULL;
+
+    lua_pushboolean(L, true);
+
+    return 1;
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, TIMER_LUA_CLASS,
+                             { "Timer", timer_lua_create });
diff --git a/src/core/lua-bindings/lua-transport.c b/src/core/lua-bindings/lua-transport.c
new file mode 100644 (file)
index 0000000..605f0fc
--- /dev/null
@@ -0,0 +1,635 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/wsck-transport.h>
+
+#include <murphy/core/lua-utils/error.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+#include <murphy/core/lua-bindings/lua-json.h>
+
+
+/*
+ * Lua transport object
+ */
+
+#define TRANSPORT_LUA_CLASS MRP_LUA_CLASS(transport, lua)
+
+typedef struct {
+    lua_State       *L;                  /* Lua execution context */
+    mrp_context_t   *ctx;                /* murphy context */
+    mrp_transport_t *t;                  /* associated murphy transport */
+    char            *address;            /* transport address */
+    mrp_sockaddr_t   addr;               /* resolved address */
+    const char      *atype;              /* address type */
+    socklen_t        alen;               /* resolved length */
+    char            *encoding;           /* transport encoding mode */
+    bool             closing;            /* whether being closed */
+    struct {
+        int connect;                     /*     connection event */
+        int closed;                      /*     closed event */
+        int recv;                        /*     connected recv event */
+        int recvfrom;                    /*     unconnected recv event */
+    }                callback;           /* event callback references */
+    int              data;               /* referece to callback data */
+} transport_lua_t;
+
+
+/* native transport handling */
+static int transport_create(transport_lua_t *t, char *err, size_t elen);
+static int transport_listen(transport_lua_t *t, char *err, size_t elen);
+static int transport_connect(transport_lua_t *t, char *err, size_t elen);
+static transport_lua_t *transport_accept(transport_lua_t *lt);
+static void transport_disconnect(transport_lua_t *t);
+
+static void event_connect(mrp_transport_t *mt, void *user_data);
+static void event_closed(mrp_transport_t *mt, int error, void *user_data);
+static void event_recv(mrp_transport_t *mt, void *msg, void *user_data);
+static void event_recvfrom(mrp_transport_t *mt, void *msg, mrp_sockaddr_t *addr,
+                           socklen_t alen, void *user_data);
+
+/* Lua transport handling */
+static int transport_lua_create(lua_State *L);
+static int transport_lua_listen(lua_State *L);
+static int transport_lua_connect(lua_State *L);
+static int transport_lua_accept(lua_State *L);
+static void transport_lua_destroy(void *data);
+static int transport_lua_disconnect(lua_State *L);
+static void transport_lua_changed(void *data, lua_State *L, int member);
+static ssize_t transport_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                      size_t size, lua_State *L, void *data);
+
+
+/*
+ * Lua transport class
+ */
+
+#define OFFS(m) MRP_OFFSET(transport_lua_t, m)
+#define CB(m)   OFFS(callback.m)
+#define RO      MRP_LUA_CLASS_READONLY
+#define NOTIFY  MRP_LUA_CLASS_NOTIFY
+#define NOFLAGS MRP_LUA_CLASS_NOFLAGS
+
+MRP_LUA_METHOD_LIST_TABLE(transport_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(transport_lua_create)
+                          MRP_LUA_METHOD(listen    , transport_lua_listen )
+                          MRP_LUA_METHOD(connect   , transport_lua_connect)
+                          MRP_LUA_METHOD(accept    , transport_lua_accept)
+                          MRP_LUA_METHOD(disconnect, transport_lua_disconnect));
+
+MRP_LUA_METHOD_LIST_TABLE(transport_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (transport_lua_create));
+
+MRP_LUA_MEMBER_LIST_TABLE(transport_lua_members,
+    MRP_LUA_CLASS_LFUNC  ("connect" , CB(connect)   , NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_LFUNC  ("closed"  , CB(closed)    , NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_LFUNC  ("recv"    , CB(recv)      , NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_LFUNC  ("recvfrom", CB(recvfrom)  , NULL, NULL, NOTIFY)
+    MRP_LUA_CLASS_ANY    ("data"    , OFFS(data)    , NULL, NULL, NOTIFY   )
+    MRP_LUA_CLASS_STRING ("address" , OFFS(address) , NULL, NULL, NOTIFY|RO)
+    MRP_LUA_CLASS_STRING ("encoding", OFFS(encoding), NULL, NULL, NOTIFY|RO)
+);
+
+typedef enum {
+    TRANSPORT_MEMBER_CONNECT,
+    TRANSPORT_MEMBER_CLOSED,
+    TRANSPORT_MEMBER_RECV,
+    TRANSPORT_MEMBER_RECVFROM,
+    TRANSPORT_MEMBER_DATA,
+    TRANSPORT_MEMBER_ADDRESS,
+    TRANSPORT_MEMBER_ENCODING,
+} transport_member_t;
+
+
+MRP_LUA_DEFINE_CLASS(transport, lua, transport_lua_t, transport_lua_destroy,
+                     transport_lua_methods, transport_lua_overrides,
+                     transport_lua_members, NULL, transport_lua_changed,
+                     transport_lua_tostring, NULL, MRP_LUA_CLASS_EXTENSIBLE);
+
+MRP_LUA_CLASS_CHECKER(transport_lua_t, transport_lua, TRANSPORT_LUA_CLASS);
+
+
+static int set_address(transport_lua_t *t, const char *address,
+                       char *err, size_t elen, int overwrite)
+{
+    MRP_LUA_ERRUSE(err, elen);
+
+    if (t->address != NULL) {
+        if (t->address == address) {
+            if (t->alen > 0 && t->atype != NULL)
+                return 1;
+        }
+        else {
+            if (!overwrite)
+                return mrp_lua_error(-1, t->L,
+                                     "address already set ('%s')", t->address);
+        }
+    }
+
+    if (t->address != address) {
+        mrp_free(t->address);
+        t->address = NULL;
+    }
+
+    t->atype = NULL;
+    t->alen  = 0;
+
+    if (address == NULL)
+        return 1;
+
+    if ((t->address = mrp_strdup(address)) == NULL)
+        return mrp_lua_error(-1, t->L,
+                             "failed to store address '%s'", address);
+
+    t->alen = mrp_transport_resolve(NULL, t->address, &t->addr,
+                                    sizeof(t->addr), &t->atype);
+
+    if (t->alen <= 0) {
+        if (address != t->address)
+            mrp_free(t->address);
+
+        t->atype = NULL;
+        t->alen  = 0;
+
+        return mrp_lua_error(-1, t->L, "failed to resolve '%s'", address);
+    }
+
+    return 0;
+}
+
+
+static int transport_create(transport_lua_t *t, char *err, size_t elen)
+{
+    MRP_LUA_ERRUSE(err, elen);
+
+    static mrp_transport_evt_t events = {
+        { .recvcustom     = event_recv     },
+        { .recvcustomfrom = event_recvfrom },
+          .connection     = event_connect,
+          .closed         = event_closed,
+    };
+    const char *opt, *val;
+    int         flags;
+
+    if (t->alen <= 0) {
+        errno = EADDRNOTAVAIL;
+        return mrp_lua_error(-1, t->L, "no address specified");
+    }
+
+    if (t->t != NULL)
+        return 0;
+
+    flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_MODE_CUSTOM;
+    t->t  = mrp_transport_create(t->ctx->ml, t->atype, &events, t, flags);
+
+    if (t->t == NULL)
+        return mrp_lua_error(-1, t->L, "failed to create transport");
+
+
+    opt = MRP_WSCK_OPT_SENDMODE;
+    val = MRP_WSCK_SENDMODE_TEXT;
+    mrp_transport_setopt(t->t, opt, val);
+
+    return 0;
+}
+
+
+static int transport_listen(transport_lua_t *t, char *err, size_t elen)
+{
+    MRP_LUA_ERRUSE(err, elen);
+
+    if (t->alen <= 0) {
+        errno = EADDRNOTAVAIL;
+        return mrp_lua_error(-1, t->L, "no address specified");
+    }
+
+    if (transport_create(t, MRP_LUA_ERRPASS) < 0)
+        return -1;
+
+    if (!mrp_transport_bind(t->t, &t->addr, t->alen) ||
+        !mrp_transport_listen(t->t, 0))
+        return mrp_lua_error(-1, t->L, "failed to bind transport");
+
+    return 0;
+}
+
+
+static int transport_connect(transport_lua_t *t, char *err, size_t elen)
+{
+    MRP_LUA_ERRUSE(err, elen);
+
+    const char *opt, *val;
+
+    if (t->alen <= 0) {
+        errno = EADDRNOTAVAIL;
+        return mrp_lua_error(-1, t->L, "no address specified");
+    }
+
+    if (t->t != NULL) {
+        errno = EISCONN;
+        return mrp_lua_error(-1, t->L, "transport already active");
+    }
+
+    if (transport_create(t, MRP_LUA_ERRPASS) < 0)
+        return mrp_lua_error(-1, t->L, "failed to connect transport to %s",
+                             t->address);
+
+    opt = MRP_WSCK_OPT_SENDMODE;
+    val = MRP_WSCK_SENDMODE_TEXT;
+    mrp_transport_setopt(t->t, opt, val);
+
+    if (!mrp_transport_connect(t->t, &t->addr, t->alen)) {
+        mrp_transport_destroy(t->t);
+        t->t = NULL;
+
+        return mrp_lua_error(-1, t->L, "failed to connect transport");
+    }
+
+    return 0;
+}
+
+
+static transport_lua_t *transport_accept(transport_lua_t *lt)
+{
+    transport_lua_t *t;
+
+    t = (transport_lua_t *)mrp_lua_create_object(lt->L, TRANSPORT_LUA_CLASS,
+                                                 NULL, 0);
+
+    t->L   = lt->L;
+    t->ctx = lt->ctx;
+    t->callback.connect  = LUA_NOREF;
+    t->callback.closed   = LUA_NOREF;
+    t->callback.recv     = LUA_NOREF;
+    t->callback.recvfrom = LUA_NOREF;
+    t->data              = LUA_NOREF;
+
+    t->t = mrp_transport_accept(lt->t, t, MRP_TRANSPORT_REUSEADDR);
+
+    if (t->t != NULL) {
+        t->callback.recv = mrp_lua_object_getref(lt, t, t->L,lt->callback.recv);
+        t->data          = mrp_lua_object_getref(lt, t, t->L,lt->data);
+
+        return t;
+    }
+
+    /* XXX TODO
+     * Hmm, is it enough to just wait for the next gc cycle, or
+     * should we actively do something to destroy the object ?
+     */
+
+    return NULL;
+}
+
+
+static void transport_disconnect(transport_lua_t *t)
+{
+    mrp_transport_disconnect(t->t);
+    mrp_transport_destroy(t->t);
+    t->t = NULL;
+}
+
+
+
+
+static void transport_lua_changed(void *data, lua_State *L, int member)
+{
+    MRP_LUA_ERRBUF();
+
+    transport_lua_t *t = (transport_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    mrp_debug("member <transport <%s> %p(%p)>.%s changed",
+              t->address ? t->address : "no address", t, t->t,
+              transport_lua_members[member].name);
+
+    switch (member) {
+    case TRANSPORT_MEMBER_CONNECT:
+    case TRANSPORT_MEMBER_CLOSED:
+    case TRANSPORT_MEMBER_RECV:
+    case TRANSPORT_MEMBER_RECVFROM:
+    case TRANSPORT_MEMBER_DATA:
+        break;
+
+    case TRANSPORT_MEMBER_ADDRESS:
+        if (set_address(t, t->address, MRP_LUA_ERRPASS, t->t == NULL) < 0)
+            mrp_lua_error(-1, L, "%s", MRP_LUA_ERR);
+        return;
+
+    case TRANSPORT_MEMBER_ENCODING:
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static int transport_lua_create(lua_State *L)
+{
+    MRP_LUA_ERRBUF();
+
+    mrp_context_t   *ctx  = mrp_lua_get_murphy_context();
+    int              narg = lua_gettop(L);
+    transport_lua_t *t;
+
+    if (ctx == NULL)
+        return mrp_lua_error(-1, L, "failed to get murphy context");
+
+    t = (transport_lua_t *)mrp_lua_create_object(L, TRANSPORT_LUA_CLASS,
+                                                 NULL, 0);
+    t->L   = L;
+    t->ctx = ctx;
+
+    t->callback.connect  = LUA_NOREF;
+    t->callback.closed   = LUA_NOREF;
+    t->callback.recv     = LUA_NOREF;
+    t->callback.recvfrom = LUA_NOREF;
+    t->data              = LUA_NOREF;
+
+    switch (narg) {
+    case 1:
+        break;
+    case 2:
+        if (mrp_lua_init_members(t, L, -2, MRP_LUA_ERRPASS) != 1)
+            return mrp_lua_error(-1, L, "failed to initialize transport (%s)",
+                                 MRP_LUA_ERR);
+        break;
+    default:
+        return mrp_lua_error(-1, L, "expected 0 or 1 arguments, got %d", narg);
+    }
+
+    mrp_lua_push_object(L, t);
+
+    return 1;
+}
+
+
+static int transport_lua_listen(lua_State *L)
+{
+    MRP_LUA_ERRUSE(NULL, 0);
+
+    transport_lua_t *t = transport_lua_check(L, 1);
+    int              narg;
+
+    if ((narg = lua_gettop(L)) != 1)
+        return mrp_lua_error(-1, L, "listen takes no arguments, got %d",
+                             narg - 1);
+
+    return transport_listen(t, MRP_LUA_ERRPASS);
+}
+
+
+static int transport_lua_connect(lua_State *L)
+{
+    MRP_LUA_ERRBUF();
+
+    transport_lua_t *t    = transport_lua_check(L, 1);
+    int              narg = lua_gettop(L);
+
+    if (t->alen <= 0 || t->atype == NULL)
+        return mrp_lua_error(-1, L, "can't connect, no address set");
+
+    if (narg != 1)
+        return mrp_lua_error(-1, L, "connect takes no arguments, %d given",
+                             narg - 1);
+
+    if (transport_connect(t, MRP_LUA_ERRPASS) < 0)
+        return mrp_lua_error(-1, L, "connection failed");
+
+    return 0;
+}
+
+
+static int transport_lua_accept(lua_State *L)
+{
+    MRP_LUA_ERRBUF();
+
+    transport_lua_t *lt   = transport_lua_check(L, 1);
+    int              narg = lua_gettop(L);
+    transport_lua_t *t;
+
+    if (narg != 1)
+        return mrp_lua_error(-1, L, "disconnect takes no arguments, got %d",
+                             narg - 1);
+
+    t = transport_accept(lt);
+
+    if (t != NULL) {
+        mrp_lua_push_object(L, t);
+        return 1;
+    }
+
+    /* XXX TODO
+     * Hmm, is it enough to just wait for the next gc cycle, or
+     * should we actively do something to destroy the object ?
+     */
+
+    return mrp_lua_error(-1, L, "failed to accept connection");
+}
+
+
+static int transport_lua_disconnect(lua_State *L)
+{
+    MRP_LUA_ERRBUF();
+
+    transport_lua_t *t    = transport_lua_check(L, 1);
+    int              narg = lua_gettop(L);
+
+    if (narg != 1)
+        return mrp_lua_error(-1, L, "disconnect takes no arguments, got %d",
+                             narg - 1);
+
+    transport_disconnect(t);
+
+    return 0;
+}
+
+
+static void transport_lua_destroy(void *data)
+{
+    transport_lua_t *t = (transport_lua_t *)data;
+
+    mrp_transport_disconnect(t->t);
+    t->t = NULL;
+    mrp_free(t->address);
+    t->address = NULL;
+
+    mrp_lua_object_unref_value(t, t->L, t->callback.connect);
+    mrp_lua_object_unref_value(t, t->L, t->callback.closed);
+    mrp_lua_object_unref_value(t, t->L, t->callback.recv);
+    mrp_lua_object_unref_value(t, t->L, t->callback.recvfrom);
+    mrp_lua_object_unref_value(t, t->L, t->data);
+    t->callback.connect  = LUA_NOREF;
+    t->callback.closed   = LUA_NOREF;
+    t->callback.recv     = LUA_NOREF;
+    t->callback.recvfrom = LUA_NOREF;
+    t->data              = LUA_NOREF;
+}
+
+
+static ssize_t transport_lua_tostring(mrp_lua_tostr_mode_t mode, char *buf,
+                                      size_t size, lua_State *L, void *data)
+{
+    transport_lua_t *t = (transport_lua_t *)data;
+
+    MRP_UNUSED(L);
+
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_LUA:
+    default:
+        return snprintf(buf, size, "{%stransport <%s> %p}",
+                        t->t && t->t->connected ? "connected" : "",
+                        t->address ? t->address : "no address", t->t);
+    }
+}
+
+
+static void event_connect(mrp_transport_t *mt, void *user_data)
+{
+    transport_lua_t *t = (transport_lua_t *)user_data;
+    int              top;
+
+    MRP_UNUSED(mt);
+
+    mrp_debug("incoming connection on <transport <%s> %p(%p)>",
+              t->address ? t->address : "no address", t, t->t);
+
+    top = lua_gettop(t->L);
+
+    if (mrp_lua_object_deref_value(t, t->L, t->callback.connect, false)) {
+        mrp_lua_push_object(t->L, t);
+        lua_pushliteral(t->L, "<remote address should be here>");
+        mrp_lua_object_deref_value(t, t->L, t->data, true);
+
+        if (lua_pcall(t->L, 3, 0, 0) != 0)
+            mrp_log_error("failed to invoke transport connect callback");
+    }
+
+    lua_settop(t->L, top);
+}
+
+
+static void event_closed(mrp_transport_t *mt, int error, void *user_data)
+{
+    transport_lua_t *t = (transport_lua_t *)user_data;
+    int              top;
+
+    MRP_UNUSED(mt);
+
+    mrp_debug("<transport <%s> %p(%p)> has been closed",
+              t->address ? t->address : "no address", t, t->t);
+
+    top = lua_gettop(t->L);
+
+    if (mrp_lua_object_deref_value(t, t->L, t->callback.closed, false)) {
+        mrp_lua_push_object(t->L, t);
+        lua_pushinteger(t->L, error);
+        mrp_lua_object_deref_value(t, t->L, t->data, true);
+
+        if (lua_pcall(t->L, 3, 0, 0) != 0)
+            mrp_log_error("failed to invoke transport closed callback");
+
+        mrp_transport_destroy(t->t);
+        t->t = NULL;
+    }
+
+    lua_settop(t->L, top);
+}
+
+
+static void event_recv(mrp_transport_t *mt, void *msg, void *user_data)
+{
+    transport_lua_t *t = (transport_lua_t *)user_data;
+    int              top;
+
+    MRP_UNUSED(mt);
+
+    mrp_debug("received message on <transport <%s> %p(%p)>",
+              t->address ? t->address : "no address", t, t->t);
+
+    top = lua_gettop(t->L);
+
+    if (mrp_lua_object_deref_value(t, t->L, t->callback.recv, false)) {
+        mrp_lua_push_object(t->L, t);
+        mrp_json_lua_push(t->L, msg);
+        mrp_lua_object_deref_value(t, t->L, t->data, true);
+
+        if (lua_pcall(t->L, 3, 0, 0) != 0)
+            mrp_log_error("failed to invoke transport recv callback");
+    }
+
+    lua_settop(t->L, top);
+}
+
+
+static void event_recvfrom(mrp_transport_t *mt, void *msg, mrp_sockaddr_t *addr,
+                           socklen_t alen, void *user_data)
+{
+    transport_lua_t *t = (transport_lua_t *)user_data;
+    int              top;
+
+    MRP_UNUSED(mt);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(alen);
+
+    mrp_debug("received message on <transport <%s> %p(%p)>",
+              t->address ? t->address : "no address", t, t->t);
+
+    top = lua_gettop(t->L);
+
+    if (mrp_lua_object_deref_value(t, t->L, t->callback.recvfrom, false)) {
+        mrp_lua_push_object(t->L, t);
+        mrp_json_lua_push(t->L, msg);
+        lua_pushliteral(t->L, "<remote address should be here>");
+        mrp_lua_object_deref_value(t, t->L, t->data, true);
+
+        if (lua_pcall(t->L, 4, 0, 0) != 0)
+            mrp_log_error("failed to invoke transport recvfrom callback");
+    }
+
+    lua_settop(t->L, top);
+}
+
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, TRANSPORT_LUA_CLASS,
+                             { "Transport", transport_lua_create });
diff --git a/src/core/lua-bindings/murphy.h b/src/core/lua-bindings/murphy.h
new file mode 100644 (file)
index 0000000..42b5263
--- /dev/null
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_BINDINGS_H__
+#define __MURPHY_LUA_BINDINGS_H__
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/list.h>
+#include <murphy/core/context.h>
+#include <murphy/core/lua-utils/object.h>
+
+
+typedef struct {
+    const char         *meta;            /* add method to this metatable */
+    luaL_reg           *methods;         /* Lua method table to register */
+    mrp_lua_classdef_t *classdef;        /* class definition or NULL */
+    mrp_list_hook_t     hook;            /* to list of registered bindings */
+} mrp_lua_bindings_t;
+
+
+typedef struct {
+    mrp_context_t **ctxp;                /* murphy context */
+} mrp_lua_murphy_t;
+
+
+/** Macro to automatically register murphy Lua bindings on startup. */
+#define MURPHY_REGISTER_LUA_BINDINGS(_metatbl, _classdef, ...) \
+    static void register_##_metatbl##_bindings(void) MRP_INIT; \
+                                                               \
+    static void register_##_metatbl##_bindings(void) {         \
+        static struct luaL_reg methods[] = {                   \
+            __VA_ARGS__,                                       \
+            { NULL, NULL }                                     \
+        };                                                     \
+        static mrp_lua_bindings_t b = {                        \
+            .meta     = #_metatbl,                             \
+            .methods  = methods,                               \
+            .classdef = _classdef,                             \
+        };                                                     \
+                                                               \
+        mrp_list_init(&b.hook);                                \
+        mrp_lua_register_murphy_bindings(&b);                  \
+    }
+
+
+/** Set murphy context for the bindings. */
+lua_State *mrp_lua_set_murphy_context(mrp_context_t *ctx);
+
+/** Set the path to the main Lua configuration file. */
+void mrp_lua_set_murphy_lua_config_file(const char *path);
+
+/** Get murphy context for the bindings. */
+mrp_context_t *mrp_lua_get_murphy_context(void);
+
+/** Get the common Lua state for the bindings. */
+lua_State *mrp_lua_get_lua_state(void);
+
+/** Get the main Lua configuration directory. */
+const char *mrp_lua_get_murphy_lua_config_dir(void);
+
+/** Register the given lua murphy bindings. */
+int mrp_lua_register_murphy_bindings(mrp_lua_bindings_t *b);
+
+/** Check and get murphy context for the bindings. */
+mrp_context_t *mrp_lua_check_murphy_context(lua_State *L, int index);
+
+/** Produce a debugging dump of the Lua stack (using mrp_debug). */
+void mrp_lua_dump_stack(lua_State *L, const char *prefix);
+
+/*
+ * level of debugging detail
+ */
+
+typedef enum {
+    MRP_LUA_DEBUG_DISABLED = 0,          /* debugging disabled */
+    MRP_LUA_DEBUG_ENABLED,               /* debugging enabled  */
+    MRP_LUA_DEBUG_DETAILED,              /* detailed debugging enabled */
+} mrp_lua_debug_t;
+
+/** Configure murphy lua debugging. */
+int mrp_lua_set_debug(mrp_lua_debug_t level);
+
+#endif /* __MURPHY_LUA_BINDINGS_H__ */
diff --git a/src/core/lua-decision/Makefile b/src/core/lua-decision/Makefile
new file mode 100644 (file)
index 0000000..cfeca66
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C ../.. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C ../.. all
+endif
diff --git a/src/core/lua-decision/element.c b/src/core/lua-decision/element.c
new file mode 100644 (file)
index 0000000..ab85f87
--- /dev/null
@@ -0,0 +1,1234 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <alloca.h>
+
+#include <murphy/common.h>
+#include <murphy/common/debug.h>
+
+#include <murphy/core/context.h>
+#include <murphy/core/scripting.h>
+#include <murphy/core/lua-decision/element.h>
+#include <murphy/core/lua-decision/mdb.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/strarray.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+#define ELEMENT_CLASS           MRP_LUA_CLASS(element, lua)
+#define SINK_CLASS              MRP_LUA_CLASS(sink, lua)
+
+#define ELEMENT_INPUT_CLASSID   MRP_LUA_CLASSID_ROOT "element_input"
+#define ELEMENT_OUTPUT_CLASSID  MRP_LUA_CLASSID_ROOT "element_output"
+
+#define ELEMENT_IDX 1
+#define INPUT_IDX   1
+#define OUTPUT_IDX  2
+
+#define INPUT_MAX      (sizeof(mrp_lua_element_mask_t) * 8)
+#define INPUT_BIT(_i)  (((mrp_lua_element_mask_t)1) << (_i))
+#define INPUT_MASK(_n) (INPUT_BIT(_n) - 1)
+
+typedef enum   field_e        field_t;
+typedef enum   input_type_e   input_type_t;
+
+
+enum field_e {
+    NAME = 1,
+    INPUTS,
+    OUTPUTS,
+    UPDATE,
+    OBJECT,
+    INTERFACE,
+    PROPERTY,
+    TYPE,
+    INITIATE,
+};
+
+enum input_type_e {
+    NUMBER = MRP_FUNCBRIDGE_FLOATING,
+    STRING = MRP_FUNCBRIDGE_STRING,
+    SELECT = MRP_FUNCBRIDGE_OBJECT,
+};
+
+struct mrp_lua_element_input_s {
+    const char *name;
+    input_type_t  type;
+    union {
+        mrp_funcbridge_value_t constant;
+        mrp_lua_mdb_select_t *select;
+    };
+};
+
+
+struct mrp_lua_element_s {
+    MRP_LUA_ELEMENT_FIELDS;
+};
+
+struct mrp_lua_sink_s {
+    MRP_LUA_ELEMENT_FIELDS;
+    const char *object;
+    const char *interface;
+    const char *property;
+    const char *type;
+    mrp_funcbridge_t *initiate;
+};
+
+
+static int  element_create_from_lua(lua_State *);
+static int  element_getfield(lua_State *);
+static int  element_setfield(lua_State *);
+static int  element_tostring(lua_State *);
+static void element_destroy_from_lua(void *);
+static mrp_lua_element_t *element_check(lua_State *, int);
+static void element_install(lua_State *, void *);
+
+static int  sink_create_from_lua(lua_State *);
+static int  sink_getfield(lua_State *);
+static int  sink_setfield(lua_State *);
+static int  sink_tostring(lua_State *);
+static void sink_destroy_from_lua(void *);
+static mrp_lua_sink_t *sink_check(lua_State *, int);
+static void sink_install(lua_State *, void *);
+
+static void element_input_class_create(lua_State *);
+static int  element_input_create_luatbl(lua_State *, int);
+static int  element_input_getfield(lua_State *);
+static int  element_input_setfield(lua_State *);
+static mrp_lua_element_input_t *element_input_create_userdata(lua_State *,
+                                                     int, size_t *,
+                                                     mrp_lua_element_mask_t *);
+
+static mrp_lua_mdb_table_t **element_output_check(lua_State *, int, size_t *);
+
+
+static field_t field_check(lua_State *, int, const char **);
+static field_t field_name_to_type(const char *, size_t);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    element_methods,         /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (element_create_from_lua)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    sink_methods,             /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (sink_create_from_lua)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    element_overrides,       /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (element_create_from_lua)
+    MRP_LUA_OVERRIDE_GETFIELD   (element_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (element_setfield)
+    MRP_LUA_OVERRIDE_STRINGIFY  (element_tostring)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    sink_overrides,           /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (sink_create_from_lua)
+    MRP_LUA_OVERRIDE_GETFIELD   (sink_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (sink_setfield)
+    MRP_LUA_OVERRIDE_STRINGIFY  (sink_tostring)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    element_input_overrides, /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (element_input_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (element_input_setfield)
+);
+
+
+MRP_LUA_CLASS_DEF (
+    element,                     /* class name */
+    lua,                         /* constructor name */
+    mrp_lua_element_t,           /* userdata type */
+    element_destroy_from_lua,    /* userdata destructor */
+    element_methods,             /* class methods */
+    element_overrides            /* override methods */
+);
+
+MRP_LUA_CLASS_DEF (
+    sink,                        /* class name */
+    lua,                         /* constructor name */
+    mrp_lua_sink_t,              /* userdata type */
+    sink_destroy_from_lua,       /* userdata destructor */
+    sink_methods,                /* class methods */
+    sink_overrides               /* override methods */
+);
+
+
+void mrp_lua_create_element_class(lua_State *L)
+{
+    mrp_lua_create_object_class(L, ELEMENT_CLASS);
+    mrp_lua_create_object_class(L, SINK_CLASS);
+
+    element_input_class_create(L);
+}
+
+const char *mrp_lua_get_element_name(mrp_lua_element_t *el)
+{
+    return el ? el->name : "";
+}
+
+int mrp_lua_element_get_input_count(mrp_lua_element_t *el)
+{
+    return el ? (int)el->ninput : -1;
+}
+
+const char *mrp_lua_element_get_input_name(mrp_lua_element_t *el, int inpidx)
+{
+    mrp_lua_element_input_t *inp;
+
+    if (!el || inpidx < 0 || inpidx >= (int)el->ninput || !(inp = el->inputs))
+        return NULL;
+
+    return inp->name;
+}
+
+int mrp_lua_element_get_input_index(mrp_lua_element_t *el, const char *inpnam)
+{
+    mrp_lua_element_input_t *inp;
+    size_t inpidx;
+
+    if (el && inpnam && (inp = el->inputs)) {
+        for (inpidx = 0;   inpidx < el->ninput;   inpidx++) {
+            if (!strcmp(inpnam, el->inputs[inpidx].name))
+                return inpidx;
+        }
+    }
+
+    return -1;
+}
+
+
+int mrp_lua_element_get_column_index(mrp_lua_element_t *el,
+                                     int inpidx,
+                                     const char *colnam)
+{
+    mrp_lua_element_input_t *inp;
+
+    if (el && inpidx >= 0 && inpidx < (int)el->ninput &&
+        (inp = el->inputs + inpidx) && colnam)
+    {
+        if (inp->type != SELECT)
+            return (!colnam || strcmp(colnam, "single_value")) ? -1 : 0;
+        else
+            return mrp_lua_select_get_column_index(inp->select, colnam);
+    }
+
+    return -1;
+}
+
+int mrp_lua_element_get_column_count(mrp_lua_element_t *el, int inpidx)
+{
+    mrp_lua_element_input_t *inp;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type != SELECT)
+            return 1;
+        else
+            return mrp_lua_select_get_column_count(inp->select);
+    }
+
+    return -1;
+}
+
+mqi_data_type_t mrp_lua_element_get_column_type(mrp_lua_element_t *el,
+                                                int inpidx,
+                                                int colidx)
+{
+    mrp_lua_element_input_t *inp;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type == SELECT)
+            return mrp_lua_select_get_column_type(inp->select, colidx);
+        else {
+            switch (inp->type) {
+            case NUMBER:   return mqi_floating;
+            case STRING:   return mqi_string;
+            default:       return -1;
+            }
+        }
+    }
+
+    return -1;
+}
+
+int mrp_lua_element_get_row_count(mrp_lua_element_t *el, int inpidx)
+{
+    mrp_lua_element_input_t *inp;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type != SELECT)
+            return 1;
+        else
+            return mrp_lua_select_get_row_count(inp->select);
+    }
+
+    return -1;
+}
+
+const char *mrp_lua_element_get_string(mrp_lua_element_t *el, int inpidx,
+                                       int colidx, int rowidx,
+                                       char *buf, int len)
+{
+    mrp_lua_element_input_t *inp;
+    const char *s = NULL;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type == SELECT)
+            s = mrp_lua_select_get_string(inp->select, colidx,rowidx, buf,len);
+        else {
+            if (!buf || len < 1)
+                s = (inp->type == STRING) ? inp->constant.string : "";
+            else {
+                s = buf;
+                switch (inp->type) {
+                case NUMBER:
+                    snprintf(buf, len, "%lf", inp->constant.floating);
+                    break;
+                case STRING:
+                    snprintf(buf, len, "%s", inp->constant.string);
+                    break;
+                default:
+                    *buf = '\0';
+                    break;
+                }
+            }
+        }
+    }
+
+    return s;
+}
+
+int32_t mrp_lua_element_get_integer(mrp_lua_element_t *el, int inpidx,
+                                    int colidx, int rowidx)
+{
+    mrp_lua_element_input_t *inp;
+    int32_t i = 0;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type == SELECT)
+            i = mrp_lua_select_get_integer(inp->select, colidx,rowidx);
+        else {
+            switch (inp->type) {
+            case NUMBER:  i = inp->constant.floating;                  break;
+            case STRING:  i = strtol(inp->constant.string, NULL, 10);  break;
+            default:      i = 0;                                       break;
+            }
+        }
+    }
+
+    return i;
+}
+
+uint32_t mrp_lua_element_get_unsigned(mrp_lua_element_t *el, int inpidx,
+                                     int colidx, int rowidx)
+{
+    mrp_lua_element_input_t *inp;
+    uint32_t u = 0;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type == SELECT)
+            u = mrp_lua_select_get_unsigned(inp->select, colidx,rowidx);
+        else {
+            switch (inp->type) {
+            case NUMBER:  u = inp->constant.floating;                   break;
+            case STRING:  u = strtoul(inp->constant.string, NULL, 10);  break;
+            default:      u = 0;                                        break;
+            }
+        }
+    }
+
+    return u;
+}
+
+double mrp_lua_element_get_floating(mrp_lua_element_t *el, int inpidx,
+                                   int colidx, int rowidx)
+{
+    mrp_lua_element_input_t *inp;
+    double f = 0;
+
+    if (el && inpidx >= 0 && inpidx <= (int)el->ninput &&
+        (inp = el->inputs + inpidx))
+    {
+        if (inp->type == SELECT)
+            f = mrp_lua_select_get_floating(inp->select, colidx,rowidx);
+        else {
+            switch (inp->type) {
+            case NUMBER:  f = inp->constant.floating;              break;
+            case STRING:  f = strtod(inp->constant.string, NULL);  break;
+            default:      f = 0;                                   break;
+            }
+        }
+    }
+
+    return f;
+}
+
+
+static int element_create_from_lua(lua_State *L)
+{
+    mrp_lua_element_t *el;
+    int table;
+    size_t fldnamlen;
+    const char *fldnam;
+
+    MRP_LUA_ENTER;
+
+    el = (mrp_lua_element_t *)mrp_lua_create_object(L, ELEMENT_CLASS, NULL,0);
+    el->install = element_install;
+
+    table = lua_gettop(L);
+
+    lua_pushinteger(L, INPUT_IDX);
+    element_input_create_luatbl(L, table);
+    lua_rawset(L, table);
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            el->name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case INPUTS:
+            el->inputs = element_input_create_userdata(L, -1, &el->ninput,
+                                                       &el->inpmask);
+            break;
+
+        case OUTPUTS:
+            el->outputs = element_output_check(L, -1, &el->noutput);
+            break;
+
+        case UPDATE:
+            el->update = mrp_funcbridge_create_luafunc(L, -1);
+            break;
+
+        default:
+            lua_pushvalue(L, -2);
+            lua_pushvalue(L, -2);
+            lua_rawset(L, table);
+            break;
+        }
+
+    } /* MRP_LUA_FOREACH_FIELD */
+
+    if (!el->name)
+        luaL_error(L, "missing mandatory 'name' field");
+    if (!el->inputs || !el->ninput)
+        luaL_error(L, "missing or empty manadatory 'input' field");
+    if (!el->outputs || !el->noutput)
+        luaL_error(L, "missing or empty manadatory 'output' field");
+    if (!el->update)
+        luaL_error(L, "missing or invalid mandatory 'update' field");
+
+    mrp_lua_set_object_name(L, ELEMENT_CLASS, el->name);
+
+    mrp_debug("element '%s' created", el->name);
+
+    if (el->inpmask == INPUT_MASK(el->ninput))
+        element_install(L, el);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int element_getfield(lua_State *L)
+{
+    mrp_lua_element_t *el;
+    field_t fld;
+
+    MRP_LUA_ENTER;
+
+    el  = element_check(L, 1);
+    fld = field_check(L, 2, NULL);
+    lua_pop(L, 1);
+
+    switch (fld) {
+    case NAME:      lua_pushstring(L, el->name);          break;
+    case INPUTS:    lua_rawgeti(L, 1, INPUT_IDX);         break;
+    case OUTPUTS:   lua_pushnil(L);                       break;
+    case UPDATE:    mrp_funcbridge_push(L, el->update);   break;
+    default:        lua_pushnil(L);                       break;
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int element_setfield(lua_State *L)
+{
+    mrp_lua_element_t *el;
+
+    MRP_LUA_ENTER;
+
+    el = element_check(L, 1);
+    luaL_error(L, "'%s' is read-only", el->name);
+
+    MRP_LUA_LEAVE(0);
+}
+
+static int element_tostring(lua_State *L)
+{
+    mrp_lua_element_t *el;
+
+    MRP_LUA_ENTER;
+
+    if ((el = element_check(L, 1)) && el->name)
+        lua_pushstring(L, el->name);
+    else
+        lua_pushstring(L, "<error>");
+
+    MRP_LUA_LEAVE(1);
+}
+
+static void element_destroy_from_lua(void *data)
+{
+    mrp_lua_element_t *el = (mrp_lua_element_t *)data;
+
+    MRP_LUA_ENTER;
+
+    if (el) {
+        mrp_free((void *)el->name);
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static mrp_lua_element_t *element_check(lua_State *L, int idx)
+{
+    return (mrp_lua_element_t *)mrp_lua_check_object(L, ELEMENT_CLASS, idx);
+}
+
+static int element_update_cb(mrp_scriptlet_t *script, mrp_context_tbl_t *ctbl)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_lua_element_t *el = (mrp_lua_element_t *)script->data;
+    mrp_funcbridge_value_t args[1] = { { .pointer = el } };
+    mrp_funcbridge_value_t ret;
+    char t;
+
+    MRP_UNUSED(ctbl);
+
+    mrp_debug("'%s'", el->name);
+
+    if (el->update) {
+        memset(&ret, 0, sizeof(ret));
+        if (!mrp_funcbridge_call_from_c(L, el->update, "o", args, &t, &ret)) {
+            mrp_log_error("failed to call element.lua.%s:update method (%s)",
+                          el->name, ret.string ? ret.string : "NULL");
+            mrp_free((void *)ret.string);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+static void element_install(lua_State *L, void *void_el)
+{
+    static mrp_interpreter_t element_updater = {
+        { NULL, NULL },
+        "element_updater",
+        NULL,
+        NULL,
+        NULL,
+        element_update_cb,
+        NULL
+    };
+
+    mrp_lua_element_t *el = (mrp_lua_element_t *)void_el;
+    mrp_lua_element_input_t *inp;
+    mrp_context_t *ctx;
+    size_t i;
+    char buf[1024], target[1024];
+    const char **depends, *d;
+    char *dep;
+    int ndepend;
+    char *p, *e;
+    size_t len;
+
+    MRP_UNUSED(L);
+
+    MRP_LUA_ENTER;
+
+    ctx = mrp_lua_get_murphy_context();
+
+    if (ctx == NULL || ctx->r == NULL) {
+        mrp_log_error("Invalid or incomplete murphy context");
+        return;
+    }
+
+    depends = alloca(el->ninput * sizeof(depends[0]));
+    ndepend = 0;
+
+    for (i = 0, e = (p = buf) + sizeof(buf);  i < el->ninput && p < e;  i++) {
+        inp = el->inputs + i;
+
+        if (inp->type == SELECT) {
+            d  = mrp_lua_select_name(inp->select);
+            p += snprintf(p, e-p, " _select_%s", d);
+
+            len = strlen(d) + 7 + 1;
+            depends[ndepend++] = dep = alloca(len);
+            sprintf(dep, "_select_%s", d);
+        }
+    }
+
+    for (i = 0;   i < el->noutput;  i++) {
+        snprintf(target, sizeof(target), "_table_%s",
+                 mrp_lua_table_name(el->outputs[i]));
+
+        printf("\%s:%s\n\tupdate(%s)\n\n", target, buf, el->name);
+
+
+        if (!mrp_resolver_add_prepared_target(ctx->r, target, depends, ndepend,
+                                              &element_updater, NULL, el)) {
+            mrp_log_error("Failed to install resolver target for element '%s'.",
+                          el->name);
+            MRP_LUA_LEAVE_ERROR(L,
+                                "Failed to install resolver target for "
+                                "element '%s'.", el->name);
+        }
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+
+static int sink_create_from_lua(lua_State *L)
+{
+    mrp_lua_sink_t *sink;
+    int table;
+    size_t fldnamlen;
+    const char *fldnam;
+
+    MRP_LUA_ENTER;
+
+    sink = (mrp_lua_sink_t *)mrp_lua_create_object(L, SINK_CLASS, NULL,0);
+    sink->install = sink_install;
+
+    table = lua_gettop(L);
+
+    lua_pushinteger(L, INPUT_IDX);
+    element_input_create_luatbl(L, table);
+    lua_rawset(L, table);
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            sink->name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case INPUTS:
+            sink->inputs = element_input_create_userdata(L, -1, &sink->ninput,
+                                                         &sink->inpmask);
+            break;
+
+        case OUTPUTS:
+            luaL_error(L, "sinks can't have outputs");
+            break;
+
+        case OBJECT:
+            sink->object = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case INTERFACE:
+            sink->interface = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case PROPERTY:
+            sink->property = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case TYPE:
+            sink->type = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case INITIATE:
+            sink->initiate = mrp_funcbridge_create_luafunc(L, -1);
+            break;
+
+        case UPDATE:
+            sink->update = mrp_funcbridge_create_luafunc(L, -1);
+            break;
+
+        default:
+            lua_pushvalue(L, -2);
+            lua_pushvalue(L, -2);
+            lua_rawset(L, table);
+            break;
+        }
+
+    } /* MRP_LUA_FOREACH_FIELD */
+
+    if (!sink->name)
+        luaL_error(L, "missing mandatory 'name' field");
+    if (!sink->inputs || !sink->ninput)
+        luaL_error(L, "missing or empty manadatory 'input' field");
+    if (!sink->update)
+        luaL_error(L, "missing or invalid mandatory 'update' field");
+
+    mrp_lua_set_object_name(L, SINK_CLASS, sink->name);
+
+    mrp_debug("sink '%s' created", sink->name);
+
+    if (sink->inpmask == INPUT_MASK(sink->ninput))
+        sink_install(L, sink);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int sink_getfield(lua_State *L)
+{
+    mrp_lua_sink_t *sink;
+    field_t fld;
+
+    MRP_LUA_ENTER;
+
+    sink = sink_check(L, 1);
+    fld = field_check(L, 2, NULL);
+    lua_pop(L, 1);
+
+    switch (fld) {
+    case NAME:      lua_pushstring(L, sink->name);          break;
+    case INPUTS:    lua_rawgeti(L, 1, INPUT_IDX);           break;
+    case OBJECT:    lua_pushstring(L, sink->object);        break;
+    case INTERFACE: lua_pushstring(L, sink->interface);     break;
+    case PROPERTY:  lua_pushstring(L, sink->property);      break;
+    case TYPE:      lua_pushstring(L, sink->type);          break;
+    case UPDATE:    mrp_funcbridge_push(L, sink->update);   break;
+    default:        lua_pushnil(L);                         break;
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int sink_setfield(lua_State *L)
+{
+    mrp_lua_sink_t *sink;
+
+    MRP_LUA_ENTER;
+
+    sink = sink_check(L, 1);
+    luaL_error(L, "'%s' is read-only", sink->name);
+
+    MRP_LUA_LEAVE(0);
+}
+
+static int sink_tostring(lua_State *L)
+{
+    mrp_lua_sink_t *sink;
+
+    MRP_LUA_ENTER;
+
+    if ((sink = sink_check(L, 1)) && sink->name)
+        lua_pushstring(L, sink->name);
+    else
+        lua_pushstring(L, "<error>");
+
+    MRP_LUA_LEAVE(1);
+}
+
+static void sink_destroy_from_lua(void *data)
+{
+    mrp_lua_sink_t *sink = (mrp_lua_sink_t *)data;
+
+    MRP_LUA_ENTER;
+
+    if (sink) {
+        mrp_free((void *)sink->name);
+        mrp_free((void *)sink->object);
+        mrp_free((void *)sink->interface);
+        mrp_free((void *)sink->property);
+        mrp_free((void *)sink->type);
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static mrp_lua_sink_t *sink_check(lua_State *L, int idx)
+{
+    return (mrp_lua_sink_t *)mrp_lua_check_object(L, SINK_CLASS, idx);
+}
+
+static int sink_update_cb(mrp_scriptlet_t *script, mrp_context_tbl_t *ctbl)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_lua_sink_t *sink = (mrp_lua_sink_t *)script->data;
+    mrp_funcbridge_value_t args[1] = { { .pointer = sink } };
+    mrp_funcbridge_value_t ret;
+    char t;
+
+    MRP_UNUSED(ctbl);
+
+    mrp_debug("'%s'", sink->name);
+
+    if (sink->update) {
+        if (!mrp_funcbridge_call_from_c(L, sink->update, "o",args, &t,&ret)) {
+            mrp_log_error("failed to call sink.lua.%s:update method (%s)",
+                          sink->name, ret.string);
+            mrp_free((void *)ret.string);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static void sink_install(lua_State *L, void *void_sink)
+{
+    static mrp_interpreter_t sink_updater = {
+        { NULL, NULL },
+        "sink_updater",
+        NULL,
+        NULL,
+        NULL,
+        sink_update_cb,
+        NULL
+    };
+
+    mrp_lua_sink_t *sink = (mrp_lua_sink_t *)void_sink;
+    mrp_context_t *ctx;
+    mrp_funcbridge_value_t args[1] = { { .pointer = sink } };
+    mrp_funcbridge_value_t ret;
+    char t;
+    mrp_lua_element_input_t *inp;
+    size_t i;
+    char buf[1024], target[1024];
+    const char **depends, *d;
+    char *dep;
+    int ndepend;
+    char *p, *e;
+    size_t len;
+
+    MRP_LUA_ENTER;
+
+    ctx = mrp_lua_get_murphy_context();
+
+    if (ctx == NULL || ctx->r == NULL) {
+        mrp_log_error("Invalid or incomplete murphy context");
+        return;
+    }
+
+    if (sink->initiate) {
+        if (!mrp_funcbridge_call_from_c(L, sink->initiate, "o",args, &t,&ret)){
+            mrp_log_error("failed to call sink.lua.%s:initiate method (%s)",
+                          sink->name, ret.string);
+            mrp_free((void *)ret.string);
+            return;
+        }
+        if (t != MRP_FUNCBRIDGE_BOOLEAN) {
+            mrp_log_error("sink.lua.%s:initiate returned '%c' type instead of "
+                          "'b' (boolean)", sink->name, t);
+            return;
+        }
+        if (!ret.boolean) {
+            mrp_log_error("sink.lua.%s:initiate failed", sink->name);
+            return;
+        }
+    }
+
+    depends = alloca(sink->ninput * sizeof(depends[0]));
+    ndepend = 0;
+
+    for (i = 0, e = (p = buf) + sizeof(buf);  i < sink->ninput && p < e;  i++){
+        inp = sink->inputs + i;
+
+        if (inp->type == SELECT) {
+            d  = mrp_lua_select_name(inp->select);
+            p += snprintf(p, e-p, " _select_%s", d);
+
+            len = strlen(d) + 7 + 1;
+            depends[ndepend++] = dep = alloca(len);
+            sprintf(dep, "_select_%s", d);
+        }
+    }
+
+    snprintf(target, sizeof(target), "_sink_%s", sink->name);
+
+    printf("\%s:%s\n\tupdate(%s)\n\n", target, buf, sink->name);
+
+
+    if (!mrp_resolver_add_prepared_target(ctx->r, target, depends, ndepend,
+                                          &sink_updater, NULL, sink))
+    {
+        mrp_log_error("Failed to install resolver target for element '%s'.",
+                      sink->name);
+
+        MRP_LUA_LEAVE_ERROR(L, "Failed to install resolver target for "
+                            "element '%s'.", sink->name);
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static void element_input_class_create(lua_State *L)
+{
+    /* create a metatable for input's */
+    luaL_newmetatable(L, ELEMENT_INPUT_CLASSID);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, element_input_overrides, 0);
+}
+
+static int element_input_create_luatbl(lua_State *L, int el)
+{
+    MRP_LUA_ENTER;
+
+    el = (el < 0) ? lua_gettop(L) + el + 1 : el;
+
+    luaL_checktype(L, el, LUA_TTABLE);
+
+    lua_createtable(L, 2, 0);
+
+    luaL_getmetatable(L, ELEMENT_INPUT_CLASSID);
+    lua_setmetatable(L, -2);
+
+    lua_pushinteger(L, ELEMENT_IDX);
+    lua_pushvalue(L, el);
+    lua_rawset(L, -3);
+
+    MRP_LUA_LEAVE(0);
+}
+
+static int element_input_getfield(lua_State *L)
+{
+    mrp_lua_element_t *el;
+    const char *inpnam;
+    mrp_lua_element_input_t *inp;
+    size_t i;
+
+    MRP_LUA_ENTER;
+
+    lua_rawgeti(L, 1, INPUT_IDX);
+    el = element_check(L, -1);
+    lua_pop(L, 1);
+
+    inpnam = luaL_checklstring(L, 2, NULL);
+
+    mrp_debug("reading %s.inputs.%s", el->name, inpnam);
+
+    for (i = 0;  i < el->ninput;  i++) {
+        inp = el->inputs + i;
+        if (!strcmp(inpnam, inp->name)) {
+            switch (inp->type) {
+            case NUMBER:    lua_pushnumber(L, inp->constant.floating);   break;
+            case STRING:    lua_pushstring(L, inp->constant.string);     break;
+            case SELECT:    mrp_lua_push_select(L, inp->select, false);  break;
+            default:        lua_pushnil(L);                              break;
+            }
+            return 1;
+        }
+    }
+
+    lua_pushnil(L);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int element_input_setfield(lua_State *L)
+{
+    mrp_lua_element_t *el;
+    const char *inpnam;
+    mrp_lua_element_input_t *inp;
+    size_t i;
+
+    MRP_LUA_ENTER;
+
+    lua_rawgeti(L, 1, INPUT_IDX);
+    el = element_check(L, -1);
+    lua_pop(L, 1);
+
+    inpnam = luaL_checklstring(L, 2, NULL);
+
+    mrp_debug("writing %s.inputs.%s", el->name, inpnam);
+
+    for (i = 0; i < el->ninput;  i++) {
+        inp = el->inputs + i;
+
+        if (!strcmp(inpnam, inp->name)) {
+            luaL_argcheck(L, !inp->type, 1, "input already assigned");
+
+            switch (lua_type(L, 3)) {
+            case LUA_TNUMBER:
+                inp->type = NUMBER;
+                inp->constant.floating = lua_tonumber(L, 3);
+                break;
+            case LUA_TSTRING:
+                inp->type = STRING;
+                inp->constant.string = lua_tolstring(L, 3, NULL);
+                break;
+            case LUA_TTABLE:
+                if ((inp->select = mrp_lua_to_select(L, 3))) {
+                    inp->type = SELECT;
+                    break;
+                }
+                /* intentional fall through */
+            default:
+                luaL_error(L, "invalid input type '%s' for %s",
+                           lua_typename(L, lua_type(L, 3)), inpnam);
+                break;
+            } /* switch type */
+
+            if ((el->inpmask |= INPUT_BIT(i)) == INPUT_MASK(el->ninput))
+                el->install(L, el);
+
+            break;
+        }
+    } /* for inp */
+
+    MRP_LUA_LEAVE(0);
+}
+
+static mrp_lua_element_input_t *element_input_create_userdata(lua_State *L,
+                                                              int idx,
+                                                              size_t *ret_len,
+                                           mrp_lua_element_mask_t *ret_inpmask)
+{
+    mrp_lua_element_input_t arr[INPUT_MAX + 1], *i, *inp;
+    mrp_lua_element_mask_t inpmask;
+    const char *name;
+    size_t namlgh;
+    size_t len;
+
+    idx = (idx < 0) ? lua_gettop(L) + idx + 1 : idx;
+    len = 0;
+    inpmask = 0;
+
+    luaL_checktype(L, idx, LUA_TTABLE);
+
+    memset(arr, 0, sizeof(arr));
+
+    MRP_LUA_FOREACH_FIELD(L, idx, name, namlgh) {
+        if (len >= INPUT_MAX)
+            luaL_error(L, "too many inputs (max %d allowes)", INPUT_MAX);
+
+        i = arr + len++;
+
+        if (namlgh < 1) {
+            if (lua_type(L, -1) == LUA_TSTRING)
+                i->name = mrp_strdup(luaL_checkstring(L, -1));
+            else {
+                luaL_error(L, "invalid type '%s' for input name",
+                           lua_typename(L, lua_type(L, -1)));
+            }
+        }
+        else {
+            switch (lua_type(L, -1)) {
+
+            case LUA_TNUMBER:
+                i->name = mrp_strdup(name);
+                i->type = NUMBER;
+                i->constant.floating = luaL_checknumber(L, -1);
+                break;
+
+            case LUA_TSTRING:
+                i->name = mrp_strdup(name);
+                i->type = STRING;
+                i->constant.string = mrp_strdup(luaL_checkstring(L, -1));
+                break;
+
+            case LUA_TTABLE:
+                i->name = mrp_strdup(name);
+                i->type = SELECT;
+                i->select = mrp_lua_select_check(L, -1);
+                break;
+
+            default:
+                luaL_error(L, "invalid input type %s",
+                           lua_typename(L, lua_type(L, -1)));
+                break;
+            }
+
+            inpmask |= INPUT_BIT(len - 1);
+        }
+    } /* MRP_LUA_FOREACH_FIELD */
+
+    if (!(inp = mrp_alloc(sizeof(mrp_lua_element_input_t) * (len + 1))))
+        luaL_error(L, "can't allocate memory");
+
+    memcpy(inp, arr, sizeof(mrp_lua_element_input_t) * len);
+    memset(inp + len, 0, sizeof(mrp_lua_element_input_t));
+
+    if (ret_len)
+        *ret_len = len;
+
+    if (ret_inpmask)
+        *ret_inpmask = inpmask;
+
+    return inp;
+}
+
+static mrp_lua_mdb_table_t **element_output_check(lua_State *L,
+                                                  int idx,
+                                                  size_t *ret_len)
+{
+    mrp_lua_mdb_table_t **arr;
+    size_t len, i;
+    size_t size;
+
+    luaL_checktype(L, idx, LUA_TTABLE);
+    len  = luaL_getn(L, idx);
+    size = sizeof(mrp_lua_mdb_table_t *) * (len + 1);
+
+    if (!(arr = mrp_alloc(size))) {
+        luaL_error(L, "can't allocate %d byte long memory", size);
+        return NULL;
+    }
+
+    lua_pushvalue(L, idx);
+
+    for (i = 0;  i < len;  i++) {
+        lua_pushnumber(L, (int)(i+1));
+        lua_gettable(L, -2);
+
+        arr[i] = mrp_lua_table_check(L, -1);
+
+        lua_pop(L, 1);
+    }
+
+    arr[i] = NULL;
+
+    lua_pop(L, 1);
+
+    if (ret_len)
+        *ret_len = len;
+
+    return arr;
+}
+
+static field_t field_check(lua_State *L, int idx, const char **ret_fldnam)
+{
+    const char *fldnam;
+    size_t fldnamlen;
+    field_t fldtyp;
+
+    if (!(fldnam = lua_tolstring(L, idx, &fldnamlen)))
+        fldtyp = 0;
+    else
+        fldtyp = field_name_to_type(fldnam, fldnamlen);
+
+    if (ret_fldnam)
+        *ret_fldnam = fldnam;
+
+    return fldtyp;
+}
+
+static field_t field_name_to_type(const char *name, size_t len)
+{
+    switch (len) {
+
+    case 4:
+        if (!strcmp(name, "name"))
+            return NAME;
+        if (!strcmp(name, "type"))
+            return TYPE;
+        break;
+
+    case 6:
+        if (!strcmp(name, "inputs"))
+            return INPUTS;
+        if (!strcmp(name, "update"))
+            return UPDATE;
+        if (!strcmp(name, "object"))
+            return OBJECT;
+        break;
+
+    case 7:
+        if (!strcmp(name, "outputs"))
+            return OUTPUTS;
+        break;
+
+    case 8:
+        if (!strcmp(name, "property"))
+            return PROPERTY;
+        if (!strcmp(name, "initiate"))
+            return INITIATE;
+        break;
+
+    case 9:
+        if (!strcmp(name, "interface"))
+            return INTERFACE;
+        break;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+const char *mrp_lua_sink_get_interface(mrp_lua_sink_t *s)
+{
+    return s ? s->interface : "";
+}
+
+const char *mrp_lua_sink_get_object(mrp_lua_sink_t *s)
+{
+    return s ? s->object : "";
+}
+
+const char *mrp_lua_sink_get_type(mrp_lua_sink_t *s)
+{
+    return s ? s->type : "";
+}
+
+const char *mrp_lua_sink_get_property(mrp_lua_sink_t *s)
+{
+    return s ? s->property : "";
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-decision/element.h b/src/core/lua-decision/element.h
new file mode 100644 (file)
index 0000000..5735dbf
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_ELEMENT_H__
+#define __MURPHY_LUA_ELEMENT_H__
+
+#include <lua.h>
+#include <murphy-db/mqi-types.h>
+
+#define MRP_LUA_ELEMENT_FIELDS                                  \
+    const char              *name;                              \
+    mrp_lua_element_mask_t   inpmask;                           \
+    size_t                   ninput;                            \
+    mrp_lua_element_input_t *inputs;                            \
+    size_t                   noutput;                           \
+    mrp_lua_mdb_table_t    **outputs;                           \
+    void                   (*install)(lua_State *, void *);     \
+    mrp_funcbridge_t        *update
+
+#define mrp_lua_get_sink_name(s) \
+    mrp_lua_get_element_name((mrp_lua_element_t *)(s))
+#define mrp_lua_get_sink_name(s) \
+    mrp_lua_get_element_name((mrp_lua_element_t *)(s))
+#define mrp_lua_sink_get_input_count(s) \
+    mrp_lua_element_get_input_count((mrp_lua_element_t *)(s))
+#define mrp_lua_sink_get_input_name(s,i) \
+    mrp_lua_element_get_input_name((mrp_lua_element_t *)(s),i)
+#define mrp_lua_sink_get_input_index(s,n) \
+    mrp_lua_element_get_input_index((mrp_lua_element_t *)(s),n);
+#define mrp_lua_sink_get_column_index(s,i,n) \
+    mrp_lua_element_get_column_index((mrp_lua_element_t *)(s),i,n)
+#define mrp_lua_sink_get_column_count(s,i) \
+    mrp_lua_element_get_column_count((mrp_lua_element_t *)(s),i)
+#define mrp_lua_sink_get_column_type(s,i,c) \
+    mrp_lua_element_get_column_type((mrp_lua_element_t *)(s),i,c)
+#define mrp_lua_sink_get_row_count(s,i) \
+    mrp_lua_element_get_row_count((mrp_lua_element_t *)(s),i)
+#define mrp_lua_sink_get_string(s,i,c,r,b,l) \
+    mrp_lua_element_get_string((mrp_lua_element_t *)(s),i,c,r,b,l)
+#define mrp_lua_sink_get_integer(s,i,c,r) \
+    mrp_lua_element_get_integer((mrp_lua_element_t *)(s),i,c,r)
+#define mrp_lua_sink_get_unsigned(s,i,c,r) \
+    mrp_lua_element_get_unsigned((mrp_lua_element_t *)(s),i,c,r)
+#define mrp_lua_sink_get_floating(s,i,c,r) \
+    mrp_lua_element_get_floating((mrp_lua_element_t *)(s),i,c,r)
+
+
+typedef struct mrp_lua_element_s         mrp_lua_element_t;
+typedef struct mrp_lua_sink_s            mrp_lua_sink_t;
+typedef struct mrp_lua_element_input_s   mrp_lua_element_input_t;
+typedef uint32_t                         mrp_lua_element_mask_t;
+
+void mrp_lua_create_element_class(lua_State *L);
+
+const char *mrp_lua_get_element_name(mrp_lua_element_t *el);
+int mrp_lua_element_get_input_count(mrp_lua_element_t *el);
+const char *mrp_lua_element_get_input_name(mrp_lua_element_t *el, int inpidx);
+int mrp_lua_element_get_input_index(mrp_lua_element_t *el, const char *inpnam);
+int mrp_lua_element_get_column_index(mrp_lua_element_t *el, int inpidx,
+                                     const char *colnam);
+int mrp_lua_element_get_column_count(mrp_lua_element_t *el, int inpidx);
+
+mqi_data_type_t mrp_lua_element_get_column_type(mrp_lua_element_t *el,
+                                                int inpidx, int colidx);
+int mrp_lua_element_get_row_count(mrp_lua_element_t *el, int inpidx);
+const char *mrp_lua_element_get_string(mrp_lua_element_t *el, int inpidx,
+                                      int colidx, int rowidx,
+                                      char * buf, int len);
+int32_t mrp_lua_element_get_integer(mrp_lua_element_t *el, int inpidx,
+                                   int colidx, int rowidx);
+
+uint32_t mrp_lua_element_get_unsigned(mrp_lua_element_t *el, int inpidx,
+                                     int colidx, int rowidx);
+double mrp_lua_element_get_floating(mrp_lua_element_t *el, int inpidx,
+                                   int colidx, int rowidx);
+
+const char *mrp_lua_sink_get_interface(mrp_lua_sink_t *s);
+const char *mrp_lua_sink_get_object(mrp_lua_sink_t *s);
+const char *mrp_lua_sink_get_type(mrp_lua_sink_t *s);
+const char *mrp_lua_sink_get_property(mrp_lua_sink_t *s);
+
+
+#endif  /* __MURPHY_LUA_ELEMENT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-decision/mdb.c b/src/core/lua-decision/mdb.c
new file mode 100644 (file)
index 0000000..378a791
--- /dev/null
@@ -0,0 +1,1746 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common.h>
+#include <murphy/common/debug.h>
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+
+#include <murphy/core/context.h>
+#include <murphy/core/scripting.h>
+#include <murphy/core/lua-decision/mdb.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/strarray.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+
+#define TABLE_CLASS         MRP_LUA_CLASS(mdb, table)
+#define SELECT_CLASS        MRP_LUA_CLASS(mdb, select)
+
+#define TABLE_ROW_CLASSID   MRP_LUA_CLASSID_ROOT "table_row"
+#define SELECT_ROW_CLASSID  MRP_LUA_CLASSID_ROOT "select_row"
+
+
+typedef enum   field_e        field_t;
+typedef struct row_s          row_t;
+typedef union  value_u        value_t;
+typedef struct const_def_s    const_def_t;
+
+
+
+enum field_e {
+    NAME = 1,
+    INDEX,
+    COLUMNS,
+    TABLE,
+    CONDITION,
+    STATEMENT,
+    SINGLEVAL,
+    CREATE
+};
+
+
+struct mrp_lua_mdb_table_s {
+    bool                builtin;
+    mqi_handle_t        handle;
+    const char         *name;
+    mrp_lua_strarray_t *index;
+    size_t              ncolumn;
+    mqi_column_def_t   *columns;
+    size_t              nrow;
+};
+
+
+struct mrp_lua_mdb_select_s {
+    const char *name;
+    const char *table_name;
+    mrp_lua_strarray_t *columns;
+    const char *condition;
+    struct {
+        const char *string;
+        mql_statement_t *precomp;
+    } statement;
+    mql_result_t *result;
+    size_t nrow;
+};
+
+struct row_s {
+    int index;
+    void *data;
+};
+
+union value_u {
+    char *string;
+    int32_t integer;
+    uint32_t unsignd;
+    double floating;
+};
+
+struct const_def_s {
+    const char *name;
+    mqi_data_type_t value;
+};
+
+
+
+static int  table_create_from_lua(lua_State *);
+static int  table_getfield(lua_State *);
+static int  table_setfield(lua_State *);
+static int  table_tostring(lua_State *);
+static int  table_insert(lua_State *);
+static int  table_replace(lua_State *);
+static int  table_update(lua_State *);
+static int  table_delete(lua_State *);
+static void table_destroy_from_lua(void *);
+
+static void table_row_class_create(lua_State *);
+/* static int  table_row_create(lua_State *, int, void *, int); */
+static int  table_row_getfield(lua_State *);
+static int  table_row_setfield(lua_State *);
+static int  table_row_getlength(lua_State *);
+static int  table_row_getvalues(lua_State *, mrp_lua_mdb_table_t *, int, bool,
+                                mqi_column_desc_t *, value_t *);
+static void table_row_resetvalues(mrp_lua_mdb_table_t *, mqi_column_desc_t *,
+                                  value_t *);
+static mrp_lua_mdb_table_t *table_row_check(lua_State *, int, int *);
+
+static int  select_create_from_lua(lua_State *);
+static int  select_getfield(lua_State *);
+static int  select_setfield(lua_State *);
+static void select_destroy_from_lua(void *);
+static int  select_update(lua_State *, int, mrp_lua_mdb_select_t *);
+static int  select_update_from_lua(lua_State *);
+static int  select_update_from_resolver(mrp_scriptlet_t *,mrp_context_tbl_t *);
+static void select_install(lua_State *, mrp_lua_mdb_select_t *);
+
+static void select_row_class_create(lua_State *);
+/* static int  select_row_create(lua_State *, int, void *, int); */
+static int  select_row_getfield(lua_State *);
+static int  select_row_setfield(lua_State *);
+static int  select_row_getlength(lua_State *);
+static mrp_lua_mdb_select_t *select_row_check(lua_State *, int, int *);
+
+static bool define_constants(lua_State *);
+
+static field_t field_check(lua_State *, int, const char **);
+static field_t field_name_to_type(const char *, size_t);
+
+static mqi_column_def_t *check_coldefs(lua_State *, int, size_t *);
+static int push_coldefs(lua_State *, mqi_column_def_t *, size_t);
+static void free_coldefs(mqi_column_def_t *);
+
+static int row_create(lua_State *, int, void *, int, const char *);
+static row_t *row_check(lua_State *, int, const char *);
+
+static void adjust_lua_table_size(lua_State *, int, void *, size_t, size_t,
+                                  const char *);
+static bool create_mdb_table(mrp_lua_mdb_table_t *);
+static mqi_cond_entry_t *condition_check(lua_State *,int,mrp_lua_mdb_table_t*);
+
+
+MRP_LUA_METHOD_LIST_TABLE (
+    table_methods,           /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (table_create_from_lua)
+    MRP_LUA_METHOD     (insert,  table_insert         )
+    MRP_LUA_METHOD     (replace, table_replace        )
+    MRP_LUA_METHOD     (update,  table_update         )
+    MRP_LUA_METHOD     (delete,  table_delete         )
+);
+
+#if 0
+MRP_LUA_METHOD_LIST_TABLE (
+    table_row_methods,       /* methodlist name */
+);
+#endif
+
+MRP_LUA_METHOD_LIST_TABLE (
+    select_methods,          /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (select_create_from_lua)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    table_overrides,         /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (table_create_from_lua)
+    MRP_LUA_OVERRIDE_GETFIELD   (table_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (table_setfield)
+    MRP_LUA_OVERRIDE_STRINGIFY  (table_tostring)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    table_row_overrides,     /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (table_row_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (table_row_setfield)
+    MRP_LUA_OVERRIDE_GETLENGTH  (table_row_getlength)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    select_overrides,        /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (select_create_from_lua)
+    MRP_LUA_OVERRIDE_GETFIELD   (select_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (select_setfield)
+    MRP_LUA_METHOD     (update,  select_update_from_lua)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    select_row_overrides,    /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (select_row_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (select_row_setfield)
+    MRP_LUA_OVERRIDE_GETLENGTH  (select_row_getlength)
+);
+
+MRP_LUA_CLASS_DEF (
+   mdb,                      /* class name */
+   table,                    /* constructor name */
+   mrp_lua_mdb_table_t,      /* userdata type */
+   table_destroy_from_lua,   /* userdata destructor */
+   table_methods,            /* class methods */
+   table_overrides           /* override methods */
+);
+
+MRP_LUA_CLASS_DEF (
+   mdb,                      /* class name */
+   select,                   /* constructor name */
+   mrp_lua_mdb_select_t,     /* userdata type */
+   select_destroy_from_lua,  /* userdata destructor */
+   select_methods,           /* class methods */
+   select_overrides          /* override methods */
+);
+
+void mrp_lua_create_mdb_class(lua_State *L)
+{
+    mrp_lua_create_object_class(L, TABLE_CLASS);
+    mrp_lua_create_object_class(L, SELECT_CLASS);
+
+    table_row_class_create(L);
+    select_row_class_create(L);
+
+    define_constants(L);
+
+    mrp_lua_findtable(L, MRP_LUA_GLOBALTABLE, "builtin.table", 20);
+}
+
+mrp_lua_mdb_table_t *mrp_lua_create_builtin_table(lua_State    *L,
+                                                  mqi_handle_t  handle)
+{
+    mrp_lua_mdb_table_t *tbl = NULL;
+
+    MRP_UNUSED(L);
+    MRP_UNUSED(handle);
+
+    return tbl;
+}
+
+mrp_lua_mdb_table_t *mrp_lua_table_check(lua_State *L, int idx)
+{
+    return (mrp_lua_mdb_table_t *)mrp_lua_check_object(L, TABLE_CLASS, idx);
+}
+
+mrp_lua_mdb_table_t *mrp_lua_to_table(lua_State *L, int idx)
+{
+    return (mrp_lua_mdb_table_t *)mrp_lua_to_object(L, TABLE_CLASS, idx);
+}
+
+int mrp_lua_push_table(lua_State *L, mrp_lua_mdb_table_t *tbl)
+{
+    return mrp_lua_push_object(L, tbl);
+}
+
+const char *mrp_lua_table_name(mrp_lua_mdb_table_t *tbl)
+{
+    return (tbl && tbl->name) ? tbl->name : "<unknown>";
+}
+
+mrp_lua_mdb_select_t *mrp_lua_select_check(lua_State *L, int idx)
+{
+    return (mrp_lua_mdb_select_t *)mrp_lua_check_object(L, SELECT_CLASS, idx);
+}
+
+mrp_lua_mdb_select_t *mrp_lua_to_select(lua_State *L, int idx)
+{
+    return (mrp_lua_mdb_select_t *)mrp_lua_to_object(L, SELECT_CLASS, idx);
+}
+
+int mrp_lua_push_select(lua_State *L,mrp_lua_mdb_select_t *sel,bool singleval)
+{
+    mql_result_t *rslt;
+    const char *str;
+    lua_Number num;
+    char buf[1024];
+
+    if (!singleval)
+        mrp_lua_push_object(L, sel);
+    else {
+        if (!(rslt = sel->result) || sel->nrow < 1)
+            lua_pushnil(L);
+        else {
+            switch (mql_result_rows_get_row_column_type(rslt, 0)) {
+            case mqi_string:
+                str = mql_result_rows_get_string(rslt, 0, 0, buf,sizeof(buf));
+                lua_pushstring(L, str);
+                break;
+            case mqi_integer:
+            case mqi_unsignd:
+            case mqi_floating:
+                num = mql_result_rows_get_floating(rslt, 0, 0);
+                lua_pushnumber(L, num);
+                break;
+            default:
+                lua_pushnil(L);
+                break;
+            }
+        }
+    }
+
+    return 1;
+}
+
+const char *mrp_lua_select_name(mrp_lua_mdb_select_t *sel)
+{
+    return (sel && sel->name) ? sel->name : "<unknown>";
+}
+
+int mrp_lua_select_get_column_index(mrp_lua_mdb_select_t *sel,
+                                    const char *colnam)
+{
+    mrp_lua_strarray_t *cols;
+    size_t colidx;
+
+    if (sel && colnam && (cols = sel->columns)) {
+        for (colidx = 0;   colidx < cols->nstring;   colidx++) {
+            if (!strcmp(colnam, cols->strings[colidx]))
+                return (int)colidx;
+        }
+    }
+
+    return -1;
+}
+
+int mrp_lua_select_get_column_count(mrp_lua_mdb_select_t *sel)
+{
+    return sel ? mql_result_rows_get_row_column_count(sel->result) : -1;
+}
+
+mqi_data_type_t mrp_lua_select_get_column_type(mrp_lua_mdb_select_t *sel,
+                                               int colidx)
+{
+    return sel ? mql_result_rows_get_row_column_type(sel->result, colidx) : -1;
+}
+
+int mrp_lua_select_get_row_count(mrp_lua_mdb_select_t *sel)
+{
+    return sel ? mql_result_rows_get_row_count(sel->result) : -1;
+}
+
+const char *mrp_lua_select_get_string(mrp_lua_mdb_select_t *sel,
+                                      int colidx, int rowidx,
+                                      char * buf, int len)
+{
+    return sel ? mql_result_rows_get_string(sel->result, colidx,rowidx,
+                                            buf,len) : NULL;
+}
+
+int32_t mrp_lua_select_get_integer(mrp_lua_mdb_select_t *sel,
+                                   int colidx, int rowidx)
+{
+    return sel ? mql_result_rows_get_integer(sel->result, colidx,rowidx) : 0;
+}
+
+uint32_t mrp_lua_select_get_unsigned(mrp_lua_mdb_select_t *sel,
+                                     int colidx, int rowidx)
+{
+    return sel ? mql_result_rows_get_unsigned(sel->result, colidx,rowidx) : 0;
+}
+
+double mrp_lua_select_get_floating(mrp_lua_mdb_select_t *sel,
+                                   int colidx, int rowidx)
+{
+    return sel ? mql_result_rows_get_floating(sel->result,colidx,rowidx) : 0.0;
+}
+
+
+static int table_create_from_lua(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+    size_t fldnamlen;
+    const char *fldnam;
+    mqi_column_def_t defs[MQI_COLUMN_MAX+1], *d;
+    int ndef;
+    size_t i;
+
+    MRP_LUA_ENTER;
+
+    if (!lua_istable(L, 2))
+        luaL_error(L, "expecting table as argument");
+
+    tbl = (mrp_lua_mdb_table_t *)mrp_lua_create_object(L, TABLE_CLASS, NULL,0);
+
+    tbl->builtin = true;
+    tbl->handle = MQI_HANDLE_INVALID;
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            tbl->name = mrp_strdup(luaL_checkstring(L, -1));
+            tbl->handle = mqi_get_table_handle((char *)tbl->name);
+            break;
+
+        case INDEX:
+            tbl->index = mrp_lua_check_strarray(L, -1);
+            break;
+
+        case COLUMNS:
+            tbl->columns = check_coldefs(L, -1, &tbl->ncolumn);
+            break;
+
+        case CREATE:
+            if (!lua_isboolean(L, -1)) {
+                luaL_error(L, "attempt to assign non-boolean "
+                           "value to 'create' field");
+            }
+            tbl->builtin = !lua_toboolean(L, -1);
+            break;
+
+        default:
+            luaL_error(L, "unexpected field '%s'", fldnam);
+            break;
+        }
+
+    } /* MRP_LUA_FOREACH_FIELD */
+
+    if (!tbl->name)
+        luaL_error(L, "mandatory 'name' field is unspecified");
+
+    if (tbl->builtin) {
+        if (tbl->handle == MQI_HANDLE_INVALID)
+            luaL_error(L, "table '%s' do not exist", tbl->name);
+        if (tbl->columns && tbl->ncolumn > 0)
+            luaL_error(L, "can't specify columns for an existing table");
+        if ((ndef = mqi_describe(tbl->handle, defs, MQI_COLUMN_MAX)) < 0)
+            luaL_error(L, "can't get column definitions of '%s'", tbl->name);
+
+        tbl->ncolumn = ndef;
+        tbl->columns = mrp_allocz(sizeof(mqi_column_def_t) * (ndef + 1));
+
+        memcpy(tbl->columns, defs, sizeof(mqi_column_def_t) * ndef);
+        for (d = tbl->columns;  d->name;  d++)
+            d->name = mrp_strdup(d->name);
+    }
+    else {
+        if (tbl->handle != MQI_HANDLE_INVALID) {
+            luaL_error(L, "attempt to create an already existing table '%s'",
+                       tbl->name);
+        }
+        if (tbl->columns && tbl->ncolumn > 0) {
+            if (!create_mdb_table(tbl))
+                luaL_error(L, "failed to create MDB table '%s'", tbl->name);
+            if (tbl->index) {
+                for (d = tbl->columns;  d->name;   d++) {
+                    for (i = 0;  i < tbl->index->nstring;  i++) {
+                        if (!strcmp(d->name, tbl->index->strings[i]))
+                            d->flags |= MQI_COLUMN_KEY;
+                    }
+                }
+            }
+        }
+        else {
+            luaL_error(L,"mandatory 'column' field is unspecified or invalid");
+        }
+    }
+
+    mrp_lua_set_object_name(L, TABLE_CLASS, tbl->name);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_getfield(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl = mrp_lua_table_check(L, 1);
+    const char *fldnam;
+    field_t fld;
+
+    MRP_LUA_ENTER;
+
+    if (lua_type(L, 2) == LUA_TNUMBER) {
+        mrp_debug("reading row %d in '%s'", (int)lua_tointeger(L,-1), tbl->name);
+        lua_rawget(L, 1);
+    }
+    else {
+        fld = field_check(L, 2, &fldnam);
+        lua_pop(L, 1);
+
+        mrp_debug("reading '%s' property of '%s'", fldnam, tbl->name);
+
+        if (!tbl)
+            lua_pushnil(L);
+        else {
+            switch (fld) {
+            case NAME:     lua_pushstring(L, tbl->name);                 break;
+            case INDEX:    mrp_lua_push_strarray(L, tbl->index);         break;
+            case COLUMNS:  push_coldefs(L, tbl->columns, tbl->ncolumn);  break;
+            default:       lua_pushnil(L);                               break;
+            }
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_setfield(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+    size_t rowidx;
+
+    MRP_LUA_ENTER;
+
+    tbl = mrp_lua_table_check(L, 1);
+
+    if (lua_type(L, 2) != LUA_TNUMBER)
+        luaL_error(L, "'%s' is read-only", tbl->name);
+    else {
+        rowidx = lua_tointeger(L, 2);
+
+        if (rowidx-- < 1)
+            luaL_error(L, "invalid row index %u", rowidx);
+        if (rowidx > tbl->nrow)
+            luaL_error(L, "row index '%u' is out of sequence", rowidx);
+
+        if (rowidx == tbl->nrow) {
+            adjust_lua_table_size(L, 1, tbl, tbl->nrow, tbl->nrow+1,
+                                  TABLE_ROW_CLASSID);
+            tbl->nrow++;
+        }
+        else {
+            lua_pushvalue(L, 2);
+            lua_rawget(L, 1);
+            luaL_checktype(L, -1, LUA_TTABLE);
+        }
+
+        mrp_debug("setting row %zu in table '%s'\n", rowidx+1, tbl->name);
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+
+static int table_tostring(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+
+    MRP_LUA_ENTER;
+
+    tbl = mrp_lua_table_check(L, 1);
+
+    if (tbl && tbl->name)
+        lua_pushstring(L, tbl->name);
+    else
+        lua_pushstring(L, "<error>");
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_insert(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+    mqi_column_desc_t desc[MQI_COLUMN_MAX+1];
+    value_t values[MQI_COLUMN_MAX];
+    void *data[2];
+    mqi_handle_t th;
+    int inserted;
+    int sts;
+
+    MRP_LUA_ENTER;
+
+    inserted = -1;
+
+    tbl = mrp_lua_table_check(L, 1);
+    sts = table_row_getvalues(L, tbl, 2, true, desc, values);
+
+    data[0] = values;
+    data[1] = NULL;
+
+    if (!sts)
+        luaL_error(L, "insert failed: some columns do not have value");
+    else {
+        th = mqi_begin_transaction();
+
+        if ((inserted = MQI_INSERT_INTO(tbl->handle, desc, data)) == 1)
+            mqi_commit_transaction(th);
+        else {
+            mqi_rollback_transaction(th);
+            table_row_resetvalues(tbl, desc, values);
+            luaL_error(L, "insert failed: %s", strerror(errno));
+        }
+
+        table_row_resetvalues(tbl, desc, values);
+    }
+
+    lua_pushinteger(L, inserted);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_replace(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+    mqi_column_desc_t desc[MQI_COLUMN_MAX+1];
+    value_t values[MQI_COLUMN_MAX];
+    void *data[2];
+    mqi_handle_t th;
+    int inserted;
+    int sts;
+
+    MRP_LUA_ENTER;
+
+    inserted = -1;
+
+    tbl = mrp_lua_table_check(L, 1);
+    sts = table_row_getvalues(L, tbl, 2, true, desc, values);
+
+    data[0] = values;
+    data[1] = NULL;
+
+    if (!sts)
+        luaL_error(L, "replace failed: some columns do not have value");
+    else {
+        th = mqi_begin_transaction();
+
+        if ((inserted = MQI_REPLACE(tbl->handle, desc, data)) >= 0)
+            mqi_commit_transaction(th);
+        else {
+            mqi_rollback_transaction(th);
+            table_row_resetvalues(tbl, desc, values);
+            luaL_error(L, "replace failed: %s", strerror(errno));
+        }
+
+        table_row_resetvalues(tbl, desc, values);
+    }
+
+    lua_pushinteger(L, inserted);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_update(lua_State *L)
+{
+    int narg;
+    mrp_lua_mdb_table_t *tbl;
+    mqi_column_desc_t desc[MQI_COLUMN_MAX+1];
+    value_t values[MQI_COLUMN_MAX];
+    mqi_cond_entry_t *cond;
+    mqi_handle_t th;
+    int updated;
+    int sts;
+
+    MRP_LUA_ENTER;
+
+    narg = lua_gettop(L);
+    tbl  = mrp_lua_table_check(L, 1);
+    sts  = table_row_getvalues(L, tbl, 2, false, desc, values);
+    cond = NULL;
+
+    if (!sts) {
+        luaL_error(L, "update failed: no values");
+        updated = FALSE; /* not reached, avoid uninitialized usage warning */
+    }
+    else if (narg > 2 && !(cond = condition_check(L, 3, tbl))) {
+        luaL_error(L, "update failed: invalid condition");
+        updated = FALSE; /* not reached, avoid uninitialized usage warning */
+    }
+    else {
+        th = mqi_begin_transaction();
+
+        if ((updated = MQI_UPDATE(tbl->handle, desc, &values, cond)) >= 0)
+            mqi_commit_transaction(th);
+        else {
+            mqi_rollback_transaction(th);
+            table_row_resetvalues(tbl, desc, values);
+            luaL_error(L, "update failed: %s", strerror(errno));
+        }
+
+        table_row_resetvalues(tbl, desc, values);
+    }
+
+    lua_pushinteger(L, updated);
+
+    MRP_LUA_LEAVE(1);
+}
+
+
+static int table_delete(lua_State *L)
+{
+    int narg;
+    mrp_lua_mdb_table_t *tbl;
+    mqi_cond_entry_t *cond;
+    mqi_handle_t th;
+    int deleted;
+
+    MRP_LUA_ENTER;
+
+    narg = lua_gettop(L);
+    tbl  = mrp_lua_table_check(L, 1);
+    cond = NULL;
+
+    if (narg > 1 && !(cond = condition_check(L, 2, tbl))) {
+        luaL_error(L, "delete failed: invalid condition");
+        deleted = FALSE; /* not reached, avoid uninitialized usage warning */
+    }
+    else {
+        th = mqi_begin_transaction();
+
+        if ((deleted = MQI_DELETE(tbl->handle, cond)) >= 0)
+            mqi_commit_transaction(th);
+        else {
+            mqi_rollback_transaction(th);
+            luaL_error(L, "delete failed: %s", strerror(errno));
+        }
+    }
+
+    lua_pushinteger(L, deleted);
+
+    MRP_LUA_LEAVE(1);
+}
+
+
+static void table_destroy_from_lua(void *data)
+{
+    mrp_lua_mdb_table_t *tbl = (mrp_lua_mdb_table_t *)data;
+
+    MRP_LUA_ENTER;
+
+    if (tbl) {
+        mrp_free((void *)tbl->name);
+        mrp_lua_free_strarray(tbl->index);
+        free_coldefs(tbl->columns);
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+
+static void table_row_class_create(lua_State *L)
+{
+    /* create a metatable for row's */
+    luaL_newmetatable(L, TABLE_ROW_CLASSID);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, table_row_overrides, 0);
+}
+
+#if 0
+static int table_row_create(lua_State *L, int tbl, void *data, int rowidx)
+{
+    int n;
+
+    MRP_LUA_ENTER;
+
+    n = row_create(L, tbl, data, rowidx, TABLE_ROW_CLASSID);
+
+    MRP_LUA_LEAVE(n);
+}
+#endif
+
+static int table_row_getfield(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+    int rowidx;
+
+    MRP_LUA_ENTER;
+
+    tbl = table_row_check(L, 1, &rowidx);
+
+    mrp_debug("reading field in row %d of '%s' table\n", rowidx+1, tbl->name);
+
+    lua_pushnil(L);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_row_setfield(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+    int rowidx;
+
+    MRP_LUA_ENTER;
+
+    tbl = table_row_check(L, 1, &rowidx);
+
+    mrp_debug("writing field in row %d of '%s' table\n", rowidx+1, tbl->name);
+
+    MRP_LUA_LEAVE(0);
+}
+
+static int table_row_getlength(lua_State *L)
+{
+    mrp_lua_mdb_table_t *tbl;
+
+    MRP_LUA_ENTER;
+
+    tbl = table_row_check(L, 1, NULL);
+    lua_pushinteger(L, tbl->ncolumn);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int table_row_getvalues(lua_State *L, mrp_lua_mdb_table_t *tbl,
+                               int idx, bool all_fields,
+                               mqi_column_desc_t *desc, value_t *values)
+{
+    int sts = 0;
+    mqi_column_def_t *c;
+    mqi_column_desc_t *d;
+    value_t *v;
+    size_t i;
+    int nfield;
+
+    idx = (idx < 0) ? lua_gettop(L) + idx + 1 : idx;
+
+    MRP_LUA_ENTER;
+
+    if (desc && values) {
+        for (i = 0, nfield = 0, sts = 1;    sts && i < tbl->ncolumn;    i++) {
+            c = tbl->columns + i;
+            d = desc + nfield;
+            v = values + nfield;
+
+            d->cindex = i;
+            d->offset = sizeof(value_t) * nfield;
+
+            lua_pushstring(L, c->name);
+            lua_gettable(L, idx);
+
+            if (lua_isnil(L, -1)) {
+                if (all_fields)
+                    sts = 0;
+            }
+            else {
+                switch (c->type) {
+                case mqi_string:
+                    v->string = mrp_strdup(luaL_checklstring(L, -1, NULL));
+                    break;
+                case mqi_integer:
+                    v->integer = luaL_checkinteger(L, -1);
+                    break;
+                case mqi_unsignd:
+                    if ((v->integer = luaL_checkinteger(L, -1)) < 0)
+                        sts = 0;
+                    break;
+                case mqi_floating:
+                    v->floating = luaL_checknumber(L, -1);
+                    break;
+                default:
+                    sts = 0;
+                    break;
+                }
+
+                nfield++;
+            }
+
+            lua_pop(L, 1);
+        } /* for */
+
+        d = desc + nfield;
+        d->cindex = -1;
+        d->offset = -1;
+
+        if (!nfield)
+            sts = 0;
+        if (!sts)
+            table_row_resetvalues(tbl, desc, values);
+    }
+
+    MRP_LUA_LEAVE(sts);
+}
+
+static void table_row_resetvalues(mrp_lua_mdb_table_t *tbl,
+                                  mqi_column_desc_t *desc,
+                                  value_t *values)
+{
+    int cidx;
+    mqi_column_desc_t *d;
+
+    for (d = desc;  (cidx = d->cindex) >= 0;  d++) {
+        if (tbl->columns[cidx].type == mqi_string)
+            mrp_free(*(void **)((void *)values + d->offset));
+    }
+
+    memset(values, 0, sizeof(value_t) * tbl->ncolumn);
+}
+
+
+static mrp_lua_mdb_table_t *table_row_check(lua_State *L,
+                                            int  idx,
+                                            int *ret_rowidx)
+{
+    row_t *row = row_check(L, idx, TABLE_ROW_CLASSID);
+
+    if (ret_rowidx)
+        *ret_rowidx = row->index;
+
+    return (mrp_lua_mdb_table_t *)row->data;
+}
+
+
+static int select_create_from_lua(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel;
+    size_t fldnamlen;
+    const char *fldnam;
+    const char *condition;
+    char  cols[1024];
+    char  qry[2048];
+
+    MRP_LUA_ENTER;
+
+    if (!lua_istable(L, 2))
+        luaL_error(L, "expecting table as argument");
+
+    sel = (mrp_lua_mdb_select_t *)mrp_lua_create_object(L,SELECT_CLASS,NULL,0);
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            sel->name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case TABLE:
+            sel->table_name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case COLUMNS:
+            sel->columns = mrp_lua_check_strarray(L, -1);
+            break;
+
+        case CONDITION:
+            condition = luaL_checkstring(L, -1);
+
+            if (strchr(condition, '%'))
+                luaL_error(L, "non-static condition '%s'", condition);
+            else
+                sel->condition = mrp_strdup(condition);
+            break;
+
+        default:
+            luaL_error(L, "unexpected field '%s'", fldnam);
+            break;
+        }
+    } /* MRP_LUA_FOREACH_FIELD */
+
+    if (!sel->name)
+        luaL_error(L, "mandatory 'name' field is missing");
+    if (!sel->table_name)
+        luaL_error(L, "mandatory 'table' field is missing");
+    if (!sel->columns || !sel->columns->nstring)
+        luaL_error(L, "mandatory 'column' field is missing or invalid");
+
+    mrp_lua_print_strarray(sel->columns, cols, sizeof(cols));
+
+    if (!sel->condition) {
+        snprintf(qry, sizeof(qry), "SELECT %s FROM %s",
+                 cols, sel->table_name);
+    }
+    else {
+        snprintf(qry, sizeof(qry), "SELECT %s FROM %s WHERE %s",
+                 cols, sel->table_name, sel->condition);
+    }
+
+    sel->statement.string = mrp_strdup(qry);
+
+    mrp_lua_set_object_name(L, SELECT_CLASS, sel->name);
+
+    mrp_debug("select '%s' created", sel->name);
+
+    select_install(L, sel);
+
+    select_update(L, -1, sel);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int select_getfield(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel = mrp_lua_select_check(L, 1);
+    field_t fld;
+    const char *fldnam;
+
+    MRP_LUA_ENTER;
+
+    if (!sel)
+        lua_pushnil(L);
+    else {
+        if (lua_type(L, 2) == LUA_TNUMBER) {
+            mrp_debug("reading row %d in '%s'", (int)lua_tointeger(L,-1),
+                      sel->name);
+            lua_rawget(L, 1);
+        }
+        else {
+            fld = field_check(L, 2, &fldnam);
+            lua_pop(L, 1);
+
+            mrp_debug("reading property %s in '%s'", fldnam, sel->name);
+
+            if (fld) {
+                switch (fld) {
+                case NAME:      lua_pushstring(L, sel->name);            break;
+                case TABLE:     lua_pushstring(L, sel->table_name);      break;
+                case COLUMNS:   mrp_lua_push_strarray(L, sel->columns);  break;
+                case CONDITION: lua_pushstring(L, sel->condition);       break;
+                case STATEMENT: lua_pushstring(L,sel->statement.string); break;
+                case SINGLEVAL: mrp_lua_push_select(L, sel, true);       break;
+                default:        lua_pushnil(L);                          break;
+                }
+            }
+            else {
+                if (!fldnam || !luaL_getmetafield(L, 1, fldnam))
+                    lua_pushnil(L);
+            }
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int select_setfield(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel;
+
+    MRP_LUA_ENTER;
+
+    sel = mrp_lua_select_check(L, 1);
+
+    luaL_error(L, "'%s' is read-only", sel->name);
+
+    MRP_LUA_LEAVE(0);
+}
+
+
+static void select_destroy_from_lua(void *data)
+{
+    mrp_lua_mdb_select_t *sel = (mrp_lua_mdb_select_t *)data;
+
+    MRP_LUA_ENTER;
+
+    if (sel) {
+        mrp_lua_free_strarray(sel->columns);
+        mrp_free((void *)sel->name);
+        mrp_free((void *)sel->table_name);
+        mrp_free((void *)sel->condition);
+        mrp_free((void *)sel->statement.string);
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static int select_update(lua_State *L, int tbl, mrp_lua_mdb_select_t *sel)
+{
+    mql_statement_t *statement;
+    mql_result_t *result;
+    int nrow;
+
+    MRP_LUA_ENTER;
+
+    if (!sel->statement.precomp)
+        sel->statement.precomp = mql_precompile(sel->statement.string);
+
+    if (!(statement = sel->statement.precomp))
+        nrow = 0;
+    else {
+        mql_result_free(sel->result);
+        sel->result = NULL;
+
+        result = mql_exec_statement(mql_result_rows, statement);
+        if (!mql_result_is_success(result)) {
+            nrow = -mql_result_error_get_code(result);
+        }
+        else {
+            sel->result = result;
+            nrow = mql_result_rows_get_row_count(result);
+        }
+    }
+
+    mrp_debug("\"%s\" resulted %d rows", sel->statement.string, nrow);
+
+    if (nrow >= 0) {
+        adjust_lua_table_size(L, tbl,sel, sel->nrow, nrow, SELECT_ROW_CLASSID);
+        sel->nrow = nrow;
+    }
+
+    MRP_LUA_LEAVE(nrow);
+}
+
+static int select_update_from_lua(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel;
+    int nrow;
+
+    MRP_LUA_ENTER;
+
+    sel = mrp_lua_select_check(L, 1);
+
+    mrp_debug("update request for select '%s'\n", sel->name);
+
+    nrow = select_update(L, 1, sel);
+
+    lua_pushinteger(L, nrow < 0 ? 0 : nrow);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int select_update_from_resolver(mrp_scriptlet_t *script,
+                                       mrp_context_tbl_t *ctbl)
+{
+    mrp_lua_mdb_select_t *sel = (mrp_lua_mdb_select_t *)script->data;
+    lua_State *L = mrp_lua_get_lua_state();
+    int nrow;
+
+    MRP_LUA_ENTER;
+
+    MRP_UNUSED(ctbl);
+
+    if (!sel || !L)
+        return -EINVAL;
+
+    mrp_debug("update request for select '%s'", sel->name);
+
+    mrp_lua_push_object(L, sel);
+
+    nrow = select_update(L, -1, sel);
+
+    lua_pop(L, 1);
+
+    MRP_LUA_LEAVE(nrow >= 0);
+}
+
+
+static void select_install(lua_State *L, mrp_lua_mdb_select_t *sel)
+{
+    static mrp_interpreter_t select_updater = {
+        { NULL, NULL },
+        "select_updater",
+        NULL,
+        NULL,
+        NULL,
+        select_update_from_resolver,
+        NULL
+    };
+
+    mrp_context_t *ctx;
+    char target[1024], table[1024];
+    const char *depends;
+
+    MRP_LUA_ENTER;
+
+    MRP_UNUSED(L);
+
+    ctx = mrp_lua_get_murphy_context();
+
+    if (ctx == NULL || ctx->r == NULL) {
+        printf("Invalid or incomplete murphy context.\n");
+        return;
+    }
+
+    snprintf(target, sizeof(target), "_select_%s", sel->name);
+    snprintf(table , sizeof(table) , "$%s" , sel->table_name);
+
+    depends = table;
+
+    printf("\n%s: %s\n\tupdate(%s)\n", target, depends, sel->name);
+
+
+
+    if (!mrp_resolver_add_prepared_target(ctx->r, target, &depends, 1,
+                                          &select_updater, NULL, sel))
+    {
+        mrp_log_error("Failed to install resolver target for element '%s'.",
+                      sel->name);
+        MRP_LUA_LEAVE_ERROR(L,
+                            "Failed to install resolver target for "
+                            "element '%s'.", sel->name);
+    }
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static void select_row_class_create(lua_State *L)
+{
+    /* create a metatable for row's */
+    luaL_newmetatable(L, SELECT_ROW_CLASSID);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, select_row_overrides, 0);
+}
+
+#if 0
+static int select_row_create(lua_State *L, int tbl, void *data, int rowidx)
+{
+    int n;
+
+    MRP_LUA_ENTER;
+
+    n = row_create(L, tbl, data, rowidx, SELECT_ROW_CLASSID);
+
+    MRP_LUA_LEAVE(n);
+}
+#endif
+
+static int select_row_getfield(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel;
+    mrp_lua_strarray_t *cols;
+    mql_result_t *rslt;
+    const char *fldnam;
+    int rowidx;
+    int colidx;
+    const char *string;
+    lua_Number number;
+    char buf[1024];
+
+    MRP_LUA_ENTER;
+
+    sel  = select_row_check(L, 1, &rowidx);
+    cols = sel->columns;
+    rslt = sel->result;
+
+    mrp_debug("reading field in row %d of '%s' selection\n",
+              rowidx+1, sel ? sel->name : "<unknwon>");
+
+    if (!sel || !rslt || (size_t)rowidx >= sel->nrow)
+        lua_pushnil(L); /* we should never get here actually */
+
+
+    switch (lua_type(L, 2)) {
+    case LUA_TSTRING:
+        fldnam = lua_tostring(L, 2);
+        for (colidx = 0;   colidx < (int)cols->nstring;   colidx++) {
+            if (!strcmp(fldnam, cols->strings[colidx]))
+                break;
+        }
+        goto get_data;
+
+    case LUA_TNUMBER:
+        colidx = lua_tointeger(L, 2) - 1;
+        /* intentional fall through */
+
+    get_data:
+        if (colidx < 0 || colidx >= (int)cols->nstring)
+            goto no_data;
+
+        switch (mql_result_rows_get_row_column_type(rslt, colidx)) {
+        case mqi_string:
+            string = mql_result_rows_get_string(rslt, colidx, rowidx,
+                                                buf, sizeof(buf));
+            lua_pushstring(L, string);
+            break;
+        case mqi_integer:
+        case mqi_unsignd:
+        case mqi_floating:
+            number = mql_result_rows_get_floating(rslt, colidx, rowidx);
+            lua_pushnumber(L, number);
+            break;
+        default:
+            goto no_data;
+        }
+        break;
+
+    default:
+    no_data:
+        lua_pushnil(L);
+        break;
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int select_row_setfield(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel;
+    int rowidx;
+
+    MRP_LUA_ENTER;
+
+    sel = select_row_check(L, 1, &rowidx);
+
+    luaL_error(L, "attempt to write row %u of read-only selection '%s'",
+               rowidx+1, sel->name);
+
+    MRP_LUA_LEAVE(0);
+}
+
+static int  select_row_getlength(lua_State *L)
+{
+    mrp_lua_mdb_select_t *sel;
+
+    MRP_LUA_ENTER;
+
+    sel = select_row_check(L, 1, NULL);
+    lua_pushinteger(L, sel->columns->nstring);
+
+    MRP_LUA_LEAVE(1);
+}
+
+
+static mrp_lua_mdb_select_t *select_row_check(lua_State *L,
+                                              int idx,
+                                              int *ret_rowidx)
+{
+    row_t *row = row_check(L, idx, SELECT_ROW_CLASSID);
+
+    if (ret_rowidx)
+        *ret_rowidx = row->index;
+
+    return (mrp_lua_mdb_select_t *)row->data;
+}
+
+static bool define_constants(lua_State *L)
+{
+    static const_def_t const_defs[] = {
+        { "string"   , mqi_string   },
+        { "integer"  , mqi_integer  },
+        { "unsigned" , mqi_unsignd  },
+        { "floating" , mqi_floating },
+        {    NULL    , mqi_unknown  }
+    };
+
+    const_def_t *cd;
+    bool success = false;
+
+    lua_getglobal(L, "mdb");
+
+    if (lua_istable(L, -1)) {
+        for (cd = const_defs;   cd->name;   cd++) {
+            lua_pushinteger(L, cd->value);
+            lua_setfield(L, -2, cd->name);
+        }
+
+        lua_pop(L, 1);
+
+        success = true;
+    }
+
+    return success;
+}
+
+static field_t field_check(lua_State *L, int idx, const char **ret_fldnam)
+{
+    const char *fldnam;
+    size_t fldnamlen;
+    field_t fldtyp;
+
+    if (!(fldnam = lua_tolstring(L, idx, &fldnamlen)))
+        fldtyp = 0;
+    else
+        fldtyp = field_name_to_type(fldnam, fldnamlen);
+
+    if (ret_fldnam)
+        *ret_fldnam = fldnam;
+
+    return fldtyp;
+}
+
+static field_t field_name_to_type(const char *name, size_t len)
+{
+    switch (len) {
+
+    case 4:
+        if (!strcmp(name, "name"))
+            return NAME;
+        break;
+
+    case 5:
+        if (!strcmp(name, "index"))
+            return INDEX;
+        if (!strcmp(name, "table"))
+            return TABLE;
+        break;
+
+    case 6:
+        if (!strcmp(name, "create"))
+            return CREATE;
+        break;
+
+    case 7:
+        if (!strcmp(name, "columns"))
+            return COLUMNS;
+        break;
+
+    case 9:
+        if (!strcmp(name, "statement"))
+            return STATEMENT;
+        if (!strcmp(name, "condition"))
+            return CONDITION;
+        break;
+
+    case 12:
+        if (!strcmp(name, "single_value"))
+            return SINGLEVAL;
+        break;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+
+static mqi_column_def_t *check_coldefs(lua_State *L, int t, size_t *ret_len)
+{
+    size_t tlen, dlen;
+    size_t size;
+    mqi_column_def_t *coldefs, *cd;
+    size_t i,j;
+
+    t = (t < 0) ? lua_gettop(L) + t + 1 : t;
+
+    luaL_checktype(L, t, LUA_TTABLE);
+    tlen  = lua_objlen(L, t);
+    size = sizeof(mqi_column_def_t) * (tlen + 1);
+
+    if (!(coldefs = mrp_allocz(size)))
+        luaL_error(L, "can't allocate %d byte long memory", size);
+    else {
+        for (i = 0;  i < tlen;  i++) {
+            cd = coldefs + i;
+
+            lua_pushinteger(L, (int)(i+1));
+            lua_gettable(L, t);
+
+            if (!lua_istable(L, -1))
+                goto error;
+
+            luaL_checktype(L, -1, LUA_TTABLE);
+
+            dlen = lua_objlen(L, -1);
+
+            for (j = 0;  j < dlen;  j++) {
+                lua_pushnumber(L, (int)(j+1));
+                lua_gettable(L, -2);
+
+                switch (j) {
+                case 0:    cd->name   = mrp_strdup(lua_tostring(L, -1)); break;
+                case 1:    cd->type   = lua_tointeger(L, -1);            break;
+                case 2:    cd->length = lua_tointeger(L, -1);            break;
+                default:   cd->type   = mqi_error;                       break;
+                }
+
+                lua_pop(L, 1);
+            }
+
+            lua_pop(L, 1);
+
+            if ( cd->name == NULL ||
+                 field_name_to_type(cd->name, strlen(cd->name)) ||
+                (cd->type != mqi_string  &&
+                 cd->type != mqi_integer &&
+                 cd->type != mqi_unsignd &&
+                 cd->type != mqi_floating ))
+                 goto error;
+        }
+
+        if (ret_len)
+            *ret_len = tlen;
+    }
+
+    return coldefs;
+
+ error:
+    free_coldefs(coldefs);
+    luaL_argerror(L, i+1, "malformed column definition");
+    if (ret_len)
+        *ret_len = 0;
+    return NULL;
+}
+
+static int push_coldefs(lua_State *L, mqi_column_def_t *coldefs, size_t hint)
+{
+    mqi_column_def_t *cd;
+    int i;
+
+    if (!coldefs)
+        lua_pushnil(L);
+    else {
+        lua_createtable(L, hint, 0);
+
+        for (cd = coldefs, i = 1;  cd->name;   cd++, i++) {
+            lua_pushinteger(L, i);
+
+            lua_createtable(L, cd->length ? 3 : 2, 0);
+
+            lua_pushinteger(L, 1);
+            lua_pushstring(L, cd->name);
+            lua_settable(L, -3);
+
+            lua_pushinteger(L, 2);
+            lua_pushinteger(L, cd->type);
+            lua_settable(L, -3);
+
+            if (cd->length) {
+                lua_pushinteger(L, 3);
+                lua_pushinteger(L, cd->length);
+                lua_settable(L, -3);
+            }
+
+            lua_settable(L, -3);
+        }
+    }
+
+    return 1;
+}
+
+static void free_coldefs(mqi_column_def_t *coldefs)
+{
+    mqi_column_def_t *cd;
+
+    if (coldefs) {
+        for (cd = coldefs;  cd->name;  cd++)
+            mrp_free((void *)cd->name);
+        mrp_free(coldefs);
+    }
+}
+
+static int row_create(lua_State *L, int tbl, void *data, int rowidx,
+                      const char *class_id)
+{
+    row_t *row;
+
+    tbl = (tbl < 0) ? lua_gettop(L) + tbl + 1 : tbl;
+
+    luaL_checktype(L, tbl, LUA_TTABLE);
+
+    row = lua_newuserdata(L, sizeof(row_t));
+
+    row->data = data;
+    row->index = rowidx;
+
+    luaL_getmetatable(L, class_id);
+    lua_setmetatable(L, -2);
+
+    return 1;
+}
+
+static row_t *row_check(lua_State *L, int  idx, const char *class_id)
+{
+    row_t *row;
+
+    idx = (idx < 0) ? lua_gettop(L) + idx + 1 : idx;
+
+    row = (row_t *)luaL_checkudata(L, idx, class_id);
+
+    return row;
+}
+
+
+static void adjust_lua_table_size(lua_State *L,
+                                  int tbl,
+                                  void *data,
+                                  size_t old_size,
+                                  size_t new_size,
+                                  const char *class_id)
+{
+    size_t rowidx;
+
+    tbl = (tbl < 0) ? lua_gettop(L) + tbl + 1 : tbl;
+
+    luaL_checktype(L, tbl, LUA_TTABLE);
+
+    if (old_size < new_size) {
+        for (rowidx = old_size;   rowidx < new_size;   rowidx++) {
+            lua_pushinteger(L, (int)(rowidx+1));
+            row_create(L, tbl, data, rowidx, class_id);
+            lua_rawset(L, tbl);
+        }
+        return;
+    }
+
+    if (old_size > new_size) {
+        for (rowidx = new_size; rowidx < old_size; rowidx++) {
+            lua_pushinteger(L, (int)(rowidx+1));
+            lua_pushnil(L);
+            lua_rawset(L, tbl);
+        }
+        return;
+    }
+}
+
+static bool create_mdb_table(mrp_lua_mdb_table_t *tbl)
+{
+    char **index;
+
+    if (!tbl->columns || !tbl->ncolumn)
+        tbl->handle = MQI_HANDLE_INVALID;
+    else {
+        if (!tbl->index || !tbl->index->nstring)
+            index = NULL;
+        else
+            index = (char **)tbl->index->strings;
+
+        tbl->handle = mqi_create_table((char *)tbl->name, MQI_TEMPORARY,
+                                       index, tbl->columns);
+
+        if (tbl->handle == MQI_HANDLE_INVALID)
+            mrp_debug("failed to create table '%s'", tbl->name);
+        else
+            mrp_debug("table '%s' has been sucessfully created", tbl->name);
+    }
+
+    return (tbl->handle != MQI_HANDLE_INVALID);
+}
+
+static mqi_cond_entry_t *condition_check(lua_State *L,
+                                         int idx,
+                                         mrp_lua_mdb_table_t *tbl)
+{
+#define ALIGN(x)  ((((x) + (sizeof(void*)-1)) / sizeof(void*)) * sizeof(void*))
+
+    mqi_column_def_t *col;
+    mqi_variable_t *var;
+    mqi_cond_entry_t *cond, *conds;
+    size_t ncond;
+    const char *fldnam;
+    size_t fldnamlen;
+    size_t total_size, cond_size, pool_size;
+    int i;
+    size_t sl, pl;
+    char *pool;
+    const char *s;
+
+    idx = (idx < 0) ? lua_gettop(L) + idx + 1 : idx;
+
+    luaL_checktype(L, idx, LUA_TTABLE);
+
+    cond_size  = ALIGN(sizeof(mqi_cond_entry_t) * (MQI_COND_MAX));
+    pool_size  = 16384;
+    total_size = cond_size + pool_size;
+
+    conds = mrp_alloc(total_size);
+    pool  = (char *)conds + cond_size;
+
+    pl = 0;
+    ncond = 0;
+
+    MRP_LUA_FOREACH_FIELD(L, idx, fldnam, fldnamlen) {
+        if ((i = mqi_get_column_index(tbl->handle, (char *)fldnam)) < 0)
+            luaL_error(L, "invalid field name '%s' in condition", fldnam);
+
+        MRP_ASSERT(i < (int)tbl->ncolumn, "internal error: column index is "
+                   "out of range");
+
+        col = tbl->columns + i;
+
+        if (ncond + 4 >= MQI_COND_MAX ||
+            pl + (col->length + sizeof(void *)) > pool_size)
+            goto too_complex;
+
+        if (ncond > 0) {
+            cond = conds + ncond++;
+            cond->type = mqi_operator;
+            cond->u.operator_ = mqi_and;
+        }
+
+        cond = conds + ncond++;
+        cond->type = mqi_column;
+        cond->u.column = i;
+
+        cond = conds + ncond++;
+        cond->type = mqi_operator;
+        cond->u.operator_ = mqi_eq;
+
+        cond = conds + ncond++;
+        cond->type = mqi_variable;
+        var = &cond->u.variable;
+
+        var->type = col->type;
+        var->flags = col->flags;
+        var->v.generic = pool + pl;
+
+        switch (col->type) {
+        case mqi_string:
+            if (!(s = luaL_checklstring(L,-1,&sl)) || (int)sl >= col->length) {
+                luaL_error(L, "expect max %u character long string for '%s'",
+                           col->length, col->name);
+            }
+            *var->v.varchar = pool + (pl += sizeof(void *));
+            memcpy(pool + pl, s, sl+1);
+            break;
+        case mqi_integer:
+            *var->v.integer = luaL_checkinteger(L, -1);
+            break;
+        case mqi_unsignd:
+            if ((*var->v.integer = luaL_checkinteger(L, -1)) < 0) {
+                luaL_error(L, "attempt to compare '%s' to negative value",
+                           col->name);
+            }
+            break;
+        case mqi_floating:
+            *var->v.floating = luaL_checknumber(L, -1);
+            break;
+        default:
+            luaL_error(L, "unsupported type '%s' in condition (column '%s')",
+                       mqi_data_type_str(col->type), col->name);
+            break;
+        }
+
+        pl = ALIGN(pl + col->length);
+    }
+
+    cond = conds + ncond++;
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_end;
+
+
+    return conds;
+
+ too_complex:
+    luaL_error(L, "condition is too complex");
+    return NULL;
+
+#undef ALIGN
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-decision/mdb.h b/src/core/lua-decision/mdb.h
new file mode 100644 (file)
index 0000000..86c5bd7
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_MDB_H__
+#define __MURPHY_LUA_MDB_H__
+
+#include <murphy-db/mqi.h>
+
+typedef struct mrp_lua_mdb_table_s        mrp_lua_mdb_table_t;
+typedef struct mrp_lua_mdb_select_s       mrp_lua_mdb_select_t;
+typedef struct mrp_lua_mdb_dependency_s   mrp_lua_mdb_dependency_t;
+
+void mrp_lua_create_mdb_class(lua_State *L);
+mrp_lua_mdb_table_t *mrp_lua_create_builtin_table(lua_State *L,
+                                                  mqi_handle_t handle);
+mrp_lua_mdb_table_t *mrp_lua_table_check(lua_State *L, int idx);
+mrp_lua_mdb_table_t *mrp_lua_to_table(lua_State *L, int idx);
+int mrp_lua_push_table(lua_State *L, mrp_lua_mdb_table_t *tbl);
+const char *mrp_lua_table_name(mrp_lua_mdb_table_t *tbl);
+
+mrp_lua_mdb_select_t *mrp_lua_select_check(lua_State *L, int idx);
+mrp_lua_mdb_select_t *mrp_lua_to_select(lua_State *L, int idx);
+int mrp_lua_push_select(lua_State *L,mrp_lua_mdb_select_t *sel,bool singleval);
+const char * mrp_lua_select_name(mrp_lua_mdb_select_t *sel);
+int mrp_lua_select_get_column_index(mrp_lua_mdb_select_t *sel,
+                                    const char *colnam);
+int mrp_lua_select_get_column_count(mrp_lua_mdb_select_t *sel);
+mqi_data_type_t mrp_lua_select_get_column_type(mrp_lua_mdb_select_t *sel,
+                                               int colidx);
+int mrp_lua_select_get_row_count(mrp_lua_mdb_select_t *sel);
+const char *mrp_lua_select_get_string(mrp_lua_mdb_select_t *sel,
+                                      int colidx, int rowidx,
+                                      char * buf, int len);
+int32_t mrp_lua_select_get_integer(mrp_lua_mdb_select_t *sel,
+                                   int colidx, int rowidx);
+
+uint32_t mrp_lua_select_get_unsigned(mrp_lua_mdb_select_t *sel,
+                                     int colidx, int rowidx);
+double mrp_lua_select_get_floating(mrp_lua_mdb_select_t *sel,
+                                   int colidx, int rowidx);
+
+int mrp_lua_dependency_add(lua_State *L, const char *name);
+
+
+#endif  /* __MURPHY_LUA_MDB_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-decision/murphy-lua-decision.pc.in b/src/core/lua-decision/murphy-lua-decision.pc.in
new file mode 100644 (file)
index 0000000..0ebfb88
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-lua-decison
+Description: Murphy policy framework, LUA support library for building decision networks
+Requires:
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-lua-decision -lmurphy-lua-utils @LUA_LIBS@
+Cflags: -I${includedir}
diff --git a/src/core/lua-decision/tests/Makefile.am b/src/core/lua-decision/tests/Makefile.am
new file mode 100644 (file)
index 0000000..b3ddeb2
--- /dev/null
@@ -0,0 +1,18 @@
+AM_CPPFLAGS = -I$(top_builddir)/src/murphy-db/include -I$(top_builddir)
+AM_CFLAGS   = $(WARNING_CFLAGS) $(AM_CPPFLAGS)
+
+
+noinst_PROGRAMS  = decision-test
+
+# lua decision network test
+decision_test_SOURCES = decision-test.c
+decision_test_CFLAGS  = $(AM_CFLAGS) $(LUA_CFLAGS)
+decision_test_LDADD   =        ../../../libmurphy-lua-utils.la         \
+                       ../../../libmurphy-lua-decision.la      \
+                       ../../../libmurphy-resolver.la          \
+                       ../../../libmurphy-core.la              \
+                       ../../../libmurphy-common.la            \
+                       ../../../murphy-db/mql/libmql.la        \
+                       ../../../murphy-db/mqi/libmqi.la        \
+                       ../../../murphy-db/mdb/libmdb.la        \
+                       $(LUA_LIBS)
diff --git a/src/core/lua-decision/tests/decision-test.c b/src/core/lua-decision/tests/decision-test.c
new file mode 100644 (file)
index 0000000..983b2be
--- /dev/null
@@ -0,0 +1,458 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <libgen.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common.h>
+
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-utils/strarray.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-decision/mdb.h>
+#include <murphy/core/lua-decision/element.h>
+
+
+#define VOLUME_CLASS   MRP_LUA_CLASS(volume, limit)
+
+
+typedef enum {
+    DEVICE = 1,
+    STREAM
+} volume_type_t;
+
+typedef enum {
+    TYPE = 1,
+    NAME,
+    DEVICES,
+    STREAMS,
+    LIMIT,
+    UPDATE,
+} volume_field_t;
+
+typedef struct volume_s {
+    volume_type_t       type;
+    const char         *name;
+    mrp_lua_strarray_t *nodes;
+    double              limit;
+    mrp_funcbridge_t   *update;
+    void               *user_data;
+} volume_t;
+
+static int  volume_create(lua_State *);
+static int  volume_getfield(lua_State *);
+static int  volume_setfield(lua_State *);
+static void volume_destroy(void *);
+
+MRP_LUA_METHOD_LIST_TABLE (
+   volume_methods,
+   MRP_LUA_METHOD_CONSTRUCTOR  (volume_create)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+   volume_overrides,
+   MRP_LUA_OVERRIDE_CALL       (volume_create)
+   MRP_LUA_OVERRIDE_GETFIELD   (volume_getfield)
+   MRP_LUA_OVERRIDE_SETFIELD   (volume_setfield)
+);
+
+
+MRP_LUA_CLASS_DEF (
+   volume,                      /* class name */
+   limit,                       /* constructor name */
+   volume_t,                    /* userdata type */
+   volume_destroy,              /* userdata destructor */
+   volume_methods,
+   volume_overrides
+);
+
+
+static size_t    nvol;
+static volume_t *vols[5];
+
+static int volume_create(lua_State *L)
+{
+    int         table;
+    size_t      fldnamlen;
+    const char *fldnam;
+    volume_t   *vol;
+
+    vol = (volume_t *)mrp_lua_create_object(L, VOLUME_CLASS, NULL,0);
+
+    table = lua_gettop(L);
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (fldnamlen) {
+        case 7:
+            if (!strcmp(fldnam, "devices")) {
+                if (vol->nodes) {
+                    return luaL_error(L, "streams and devices are "
+                                      "mutually exclusive");
+                }
+                vol->type  = DEVICE;
+                vol->nodes = mrp_lua_check_strarray(L, -1);
+                break;
+            }
+            if (!strcmp(fldnam, "streams")) {
+                if (vol->nodes) {
+                    return luaL_error(L, "streams and devices are "
+                                      "mutually exclusive");
+                }
+                vol->type  = STREAM;
+                vol->nodes = mrp_lua_check_strarray(L, -1);
+                break;
+            }
+            goto not_userdata;
+
+        case 6:
+            if (!strcmp(fldnam, "update")) {
+                vol->update = mrp_funcbridge_create_luafunc(L, -1);
+                break;
+            }
+            goto not_userdata;
+
+        case 5:
+            if (!strcmp(fldnam, "limit")) {
+                vol->limit = luaL_checknumber(L, -1);
+                break;
+            }
+            goto not_userdata;
+
+        case 4:
+            if (!strcmp(fldnam, "type")) {
+                return luaL_error(L, "type field is readonly");
+            }
+            if (!strcmp(fldnam, "name")) {
+                vol->name = luaL_checklstring(L, -1, NULL);
+                break;
+            }
+            goto not_userdata;
+
+        default:
+        not_userdata:
+            lua_pushvalue(L, -2);
+            lua_pushvalue(L, -2);
+            lua_rawset(L, table);
+            break;
+        }
+    } /* MRP_LUA_FOREACH_FIELD */
+
+    if (!vol->type || !vol->nodes)
+        return luaL_error(L, "Either streams or devices must be present");
+    if (!vol->name)
+        return luaL_error(L, "name is not present");
+
+    mrp_lua_set_object_name(L, VOLUME_CLASS, vol->name);
+
+    vols[nvol++] = vol;
+
+    printf("volume %p\n", vol);
+
+    return 1;
+}
+
+static volume_t *checkvolume(lua_State *L)
+{
+    return (volume_t *)mrp_lua_check_object(L, VOLUME_CLASS, 1);
+}
+
+static volume_field_t checkfield(lua_State *L)
+{
+    size_t      len;
+    const char *name;
+
+    name = luaL_checklstring(L, 2, &len);
+
+    switch (len) {
+    case 4:
+        if (!strcmp(name, "type"))
+            return TYPE;
+        if (!strcmp(name, "name"))
+            return NAME;
+        break;
+    case 5:
+        if (!strcmp(name, "limit"))
+            return LIMIT;
+        break;
+    case 6:
+        if (!strcmp(name, "update"))
+            return UPDATE;
+        break;
+    case 7:
+        if (!strcmp(name, "streams"))
+            return STREAMS;
+        if (!strcmp(name, "devices"))
+            return DEVICES;
+        break;
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+static void volume_destroy(void *data)
+{
+    volume_t *vol = (volume_t *)data;
+    size_t i;
+
+    printf("*** volume destroyed\n");
+
+    mrp_lua_free_strarray(vol->nodes);
+
+    for (i = 0; i < nvol; i++) {
+        if (vols[i] == vol) {
+            while ((i+1) < sizeof(vols)/sizeof(vols[0]) && vols[i+1]) {
+                vols[i] = vols[i+1];
+                i++;
+            }
+            vols[i] = NULL;
+            break;
+        }
+    }
+}
+
+static const char *volumetype2str(volume_type_t type)
+{
+    switch (type) {
+    case DEVICE:     return "device";
+    case STREAM:     return "stream";
+    default:         return "<unknown>";
+    }
+}
+
+
+static int volume_getfield(lua_State *L)
+{
+    volume_t         *vol = checkvolume(L);
+    volume_field_t fld = checkfield(L);
+    char buf[256];
+
+    printf("index %d for %s volume (node %s) \n",
+           fld, volumetype2str(vol->type),
+           mrp_lua_print_strarray(vol->nodes, buf, sizeof(buf)));
+
+    switch (fld) {
+    case TYPE:
+        lua_pushstring(L, volumetype2str(vol->type));
+        break;
+    case STREAMS:
+        if (vol->type == STREAM)
+            mrp_lua_push_strarray(L, vol->nodes);
+        else
+            lua_pushnil(L);
+        break;
+    case DEVICES:
+        if (vol->type == DEVICE)
+            mrp_lua_push_strarray(L, vol->nodes);
+        else
+            lua_pushnil(L);
+        break;
+    case LIMIT:
+        lua_pushnumber(L, vol->limit);
+        break;
+    case UPDATE:
+        mrp_funcbridge_push(L, vol->update);
+        break;
+    default:
+        lua_pushvalue(L, 2);
+        lua_rawget(L, 1);
+        break;
+    }
+
+    return 1;
+}
+
+static int volume_setfield(lua_State *L)
+{
+    volume_t       *vol = checkvolume(L);
+    volume_field_t  fld = checkfield(L);
+    char            buf[256];
+
+    printf("new index %d for %s volume (node %s) \n",
+           fld, volumetype2str(vol->type),
+           mrp_lua_print_strarray(vol->nodes, buf, sizeof(buf)));
+
+    switch (fld) {
+    case STREAMS:
+        if (vol->type != STREAM) {
+            return luaL_error(L, "attempt to set sterams for device "
+                              "volume limit");
+        }
+        goto set_nodes;
+    case DEVICES:
+        if (vol->type != STREAM) {
+            return luaL_error(L, "attempt to set sterams for device "
+                              "volume limit");
+        }
+        goto set_nodes;
+    set_nodes:
+        mrp_lua_free_strarray(vol->nodes);
+        vol->nodes = mrp_lua_check_strarray(L, 3);
+        break;
+    case LIMIT:
+        vol->limit = luaL_checknumber(L, 3);
+        break;
+    case UPDATE:
+        vol->update = mrp_funcbridge_create_luafunc(L, 3);
+    default:
+        lua_rawset(L, 1);
+        break;
+    }
+
+    return 0;
+}
+
+
+static void volume_openlib(lua_State *L)
+{
+
+    mrp_lua_create_object_class(L, VOLUME_CLASS);
+}
+
+
+bool my_update_func(lua_State *L, void *data,
+                    const char *signature, mrp_funcbridge_value_t *args,
+                    char  *ret_type, mrp_funcbridge_value_t *ret_val)
+{
+    MRP_UNUSED(L);
+
+    printf("**** %s(%p) signature='%s' arg1=%p arg2='%s'\n",
+           __FUNCTION__, data, signature,
+           signature[0] == 'o' ? args[0].pointer : NULL,
+           signature[1] == 's' ? args[1].string  : "<undefined>");
+
+    *ret_type = MRP_FUNCBRIDGE_FLOATING;
+    ret_val->floating = 3.1415;
+
+    return true;
+}
+
+int main(int argc, char **argv)
+{
+    mrp_funcbridge_value_t args[] = {
+        {.pointer = NULL },
+        {.string  = "Hello world, here I am"}
+    };
+
+    const char *pnam = basename(argv[0]);
+    lua_State *L;
+    char buf[512];
+    int error;
+    volume_t *v;
+    mrp_funcbridge_t *fb;
+    mrp_funcbridge_value_t ret;
+    char t;
+
+    if (argc > 2) {
+        printf("Usage: %s [file]\n", pnam);
+        exit(1);
+    }
+
+    if (!(L = luaL_newstate())) {
+        printf("failed to initialize Lua\n");
+        exit(1);
+    }
+
+    printf("Lua initialized\n");
+
+    luaL_openlibs(L);
+    mrp_create_funcbridge_class(L);
+    mrp_lua_create_mdb_class(L);
+    mrp_lua_create_element_class(L);
+    volume_openlib(L);
+
+    mrp_funcbridge_create_cfunc(L, "my_update_func", "os",
+                                my_update_func, (void *)0x1234);
+
+    if (argc == 2) {
+        error = luaL_loadfile(L, argv[1]) ||
+                lua_pcall(L, 0, 0, 0);
+        if (error) {
+            printf("%s\n", lua_tostring(L, -1));
+            lua_pop(L, 1);
+        }
+
+        if ((v = args[0].pointer = vols[0]) && (fb = v->update)) {
+            char value[32];
+            if (!mrp_funcbridge_call_from_c(L, fb, "os", args, &t, &ret))
+                printf("*** call failed: %s\n", ret.string);
+            else {
+                switch (t) {
+                case MRP_FUNCBRIDGE_NO_DATA:
+                    snprintf(value, sizeof(value), "<no data>");
+                    break;
+                case MRP_FUNCBRIDGE_STRING:
+                    snprintf(value, sizeof(value), "%s", ret.string);
+                    break;
+                case MRP_FUNCBRIDGE_INTEGER:
+                    snprintf(value, sizeof(value), "%d", ret.integer);
+                    break;
+                case MRP_FUNCBRIDGE_FLOATING:
+                    snprintf(value, sizeof(value), "%lf", ret.floating);
+                    break;
+                default:
+                    snprintf(value, sizeof(value), "<unsupported>");
+                    break;
+                }
+                printf("*** return value %s\n", value);
+            }
+        }
+    }
+    else {
+        printf("%s> ", pnam);
+        fflush(stdout);
+
+        while (fgets(buf, sizeof(buf), stdin)) {
+            error = luaL_loadbuffer(L, buf, strlen(buf), "line") ||
+                    lua_pcall(L, 0, 0, 0);
+            if (error) {
+                printf("%s\n", lua_tostring(L, -1));
+                lua_pop(L, 1);
+            }
+
+            printf("%s> ", pnam);
+            fflush(stdout);
+        }
+    }
+
+    lua_close(L);
+
+    return 0;
+}
diff --git a/src/core/lua-decision/tests/decision-test.lua b/src/core/lua-decision/tests/decision-test.lua
new file mode 100644 (file)
index 0000000..e64b73e
--- /dev/null
@@ -0,0 +1,171 @@
+--[[
+for n in pairs(_G) do
+    print(n)
+end
+--]]
+
+print_table = function (tbl)
+   print("-------- mdb start -------");
+   for kn, n in pairs(tbl) do
+      if type(n) ~= "table" then
+        if type(n) == "function" then
+           print(kn.."=<function>")
+        else
+           print(kn.."="..n)
+        end
+      else
+        print("'"..kn.."' table ")
+        for km, m in pairs(n) do
+           if type(n) == "function" then
+              print("   "..km.."=<function>")
+           elseif type(n) == "table" then
+              print("   '"..km.."' table")
+           else
+              print("   "..km.."="..m)
+           end
+        end
+      end
+   end
+   print("--------- mdb end --------");
+end
+
+print_table(mdb)
+
+rows = {{key="a",value="A"},{key="b",value="B"},{key="c",value="C"}}
+
+print("rows[2].value="..rows[2].value)
+
+print("mdb.string="..mdb.string)
+
+mdb.table {
+    name    = "amb",
+    index   = {"key"},
+    columns = {{"key", mdb.string, 16}, {"value", mdb.floating}}
+}
+
+print("mdb.amb="..tostring(mdb.table.amb))
+
+index = "{"
+
+for _, k in ipairs(mdb.table.amb.index) do
+    index = index.." "..k
+end
+
+index = index.." }"
+
+
+coldefs = "{"
+
+for _, cd in ipairs(mdb.table.amb.columns) do
+    local name, type, length
+
+    for i, v in ipairs(cd) do
+        if i == 1 then  name = v   end
+        if i == 2 then  type = v   end
+        if i == 3 then  length = v end
+    end
+
+    coldefs = coldefs.." {"..name..","..type
+
+    if length then
+        coldefs = coldefs..","..length
+    end
+
+    coldefs = coldefs.."}"
+end
+
+coldefs = coldefs.." }"
+
+print("mdb.table.amb.name="..mdb.table.amb.name)
+print("mdb.table.amb.index="..index)
+print("mdb.table.amb.columns="..coldefs)
+
+mdb.table.amb[1] = { key = "foo", value = 3.1415 }
+
+mdb.select {
+           name = "speed",
+           table = "amb",
+           columns = {"value"},
+           condition = "key = 'speed'"
+}
+
+--[[
+print("mdb.select.speed.statement="..mdb.select.speed.statement)
+
+q = mdb.select.speed[0]
+
+mdb.select.speed:update()
+
+print_table(mdb)
+
+--]]
+
+element.lua {
+   name    = "speed2volume",
+   inputs  = { "bar", foo = mdb.select.speed, param = 5 }, 
+   outputs = {  mdb.table { name = "speedvol",
+                           index = {"zone", "device"},
+                           columns = {{"zone", mdb.string, 16},
+                                      {"device", mdb.string, 16},
+                                      {"value", mdb.floating}}
+                          }
+            },
+   update  = function(self)
+               print("*** element "..self.name.." update function called")
+            end
+}
+
+-- print_table(element)
+
+element.lua.speed2volume.inputs.bar = mdb.select {name = "rpm",
+                                                 table = "amb",
+                                                 columns = {"value"},
+                                                 condition = "key = 'rpm'"}
+
+element.lua.speed2volume:update()
+print("speed2volume.inputs.param="..element.lua.speed2volume.inputs.param)
+-- print("speed2volume.inputs.foo[0].value="..element.lua.speed2volume.inputs.foo[0].value)
+
+volume.limit {
+    name = "speed_adjust",
+    devices = {"speaker", "headphone", "headset"},
+    limit = -0.5,
+    extra = 2468,
+    update = builtin.method.my_update_func
+
+    --[[
+    update = function(self, arg)
+                 print("*** lua update function arg="..arg.." extra="..self.extra)
+                 return 987.0
+             end
+    --]]
+}
+
+
+foo = volume.limit.speed_adjust
+
+for k,v in pairs(foo) do
+    print(k..": "..type(v))
+end
+
+a = pairs(foo)
+print("a "..type(a))
+
+print("limit "..foo.limit)
+
+foo.limit = -31.2
+foo.yoyo = "a"
+
+print("limit "..foo.limit)
+print("type "..foo.type)
+print("yoyo "..foo.yoyo)
+print("extra "..foo.extra)
+print("volume.limit.speed_adjust "..type(volume.limit.speed_adjust))
+print("builtin.method.my_update_func "..type(builtin.method.my_update_func))
+
+-- builtin.my_update_func()
+print("update returned "..foo:update("Hello world"))
+-- volume.speed_adjust=nil
+-- foo=nil
+collectgarbage()
+print("done");
diff --git a/src/core/lua-utils/Makefile b/src/core/lua-utils/Makefile
new file mode 100644 (file)
index 0000000..cfeca66
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C ../.. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C ../.. all
+endif
diff --git a/src/core/lua-utils/error.c b/src/core/lua-utils/error.c
new file mode 100644 (file)
index 0000000..87d17bd
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdarg.h>
+#include <lauxlib.h>
+
+#include "murphy/common/log.h"
+#include "murphy/core/lua-utils/error.h"
+
+
+int mrp_lua_set_error(lua_State *L, char *errbuf, size_t bufsize, ...)
+{
+    va_list  ap;
+    char     buf[bufsize ? bufsize : 256];
+    char    *err;
+    size_t   size;
+    char    *fmt;
+
+    err  = errbuf ? errbuf  : buf;
+    size = errbuf ? bufsize : sizeof(buf);
+
+    va_start(ap, bufsize);
+    fmt = va_arg(ap, char *);
+    vsnprintf(err, size, fmt, ap);
+    va_end(ap);
+
+    if (errbuf != NULL)
+        return -1;
+
+    if (L != NULL)
+        luaL_error(L, "%s", errbuf);
+
+    mrp_log_error("Lua error in non-throwable context, '%s'", err);
+    return -1;
+}
diff --git a/src/core/lua-utils/error.h b/src/core/lua-utils/error.h
new file mode 100644 (file)
index 0000000..ea5678a
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_ERROR_H__
+#define __MURPHY_LUA_ERROR_H__
+
+#include <stdlib.h>
+#include <setjmp.h>
+#include <lua.h>
+
+#include "murphy/common/debug.h"
+#include "murphy/common/mm.h"
+#include "murphy/common/list.h"
+
+/*
+ * basic error handling
+ */
+
+/** Macro to declare an error buffer for mrp_lua_{error, throw}. */
+#define MRP_LUA_ERRBUF(...)                                             \
+    size_t _ela[] = { 256, __VA_ARGS__ };                               \
+    size_t _errl  = MRP_ARRAY_SIZE(_ela) == 1 ? _ela[0] : _ela[1];      \
+    char   _errb[_errl]
+
+/** Macro to mark arguments to be used by mrp_lua_{error, throw}. */
+#define MRP_LUA_ERRUSE(_ebuf, _esize)                                   \
+    size_t  _errl = _esize;                                             \
+    char   *_errb = _ebuf
+
+/** Macro to pass on error buffer argument to a function call. */
+#define MRP_LUA_ERRPASS _errb, _errl
+
+/** Macro to omit error buffer from a function call. */
+#define MRP_LUA_ERRNONE NULL, 0
+
+/** Active error buffer. */
+#define MRP_LUA_ERR _errb
+
+/** Macro to pass up, throw, or print an error. */
+#define mrp_lua_error(_retval, _L, ...)                                 \
+    mrp_lua_set_error(_L, _errb, _errl, __VA_ARGS__),                   \
+        _retval
+
+/** Macro to throw an error/exception. */
+#define mrp_lua_throw(_L, ...)                                          \
+    mrp_lua_set_error(_L, _errb, _errl, __VA_ARGS__)
+
+/** The low-level error formatting/throwing/printing routine. */
+int mrp_lua_set_error(lua_State *L, char *errbuf, size_t size, ...);
+
+
+/*
+ * chained error-path cleanup
+ */
+
+
+/** Error trap callback type. */
+typedef int (*mrp_lua_trapcb_t)(lua_State *L, void *data);
+
+/** An error trap callback buffer. */
+typedef struct {
+    mrp_list_hook_t   hook;              /* hook to trap-list */
+    mrp_lua_trapcb_t  cb;                /* trap callback */
+    void             *data;              /* trap callback data */
+    const char       *name;              /* stringified data */
+} mrp_lua_trap_t;
+
+/** Lua error buffer with a chain of trap handlers. */
+typedef struct {
+    mrp_list_hook_t traps;               /* traps to call during cleanup */
+    lua_State      *L;                   /* lua state buffer */
+    char            buf[1024];           /* error message buffer */
+    char           *err;                 /* buffer write pointer */
+    size_t          len;                 /* remaining buffer length */
+    int             error;               /* error code */
+    jmp_buf         jmp;                 /* saved jump location */
+} mrp_lua_errbuf_t;
+
+
+#define MRP_LUA_CATCH(_err, _L)                                         \
+    mrp_lua_errbuf_t _err##_buf = {                                     \
+        .traps = { .prev = &_err.traps, .next = &_err.traps },          \
+        .buf   = { '\0' },                                              \
+        .len   = 0,                                                     \
+        .error = 0,                                                     \
+    }, _err = &_err##_buf;                                              \
+    if (setjmp(&(_err)->loc) != 0) {                                    \
+        mrp_log_error("%s", (_err)->buf);                               \
+        mrp_lua_trigger_traps(_err, NULL, _L, -1);                      \
+    }
+
+#define MRP_LUA_THROW(_err, _ret, _L, _fmt, ...) do {                   \
+        ssize_t _n;                                                     \
+                                                                        \
+        _n = snprintf((_err)->err, (_err)->len, _fmt, __VA_ARGS__);     \
+        if (_n >= (_err)->len)                                          \
+            (_err)->len = 0;                                            \
+        else                                                            \
+            (_err)->len -= (size_t)n;                                   \
+                                                                        \
+        longjmp(&(_err)->jmp, _ret);                                    \
+    } while (0)
+
+
+/** Macro to declare an error buffer/error-path cleanup chain. */
+#define MRP_LUA_TRAPCHAIN(_err)                                         \
+    mrp_lua_errbuf_t _err = {                                           \
+        .traps = { .prev = &_err.traps, .next = &_err.traps },          \
+        .buf   = { '\0' },                                              \
+        .len   = 0,                                                     \
+        .error = 0,                                                     \
+    }
+
+/** Macro to push a new item to the cleanup chain. */
+#define mrp_lua_push_trap(_err, _cb, _data) do {                        \
+        mrp_lua_trap_t *_trap = mrp_allocz(sizeof(*_trap));             \
+                                                                        \
+        mrp_clear(_trap);                                               \
+        mrp_list_init(&_trap->hook);                                    \
+        _trap->cb   = _cb;                                              \
+        _trap->data = _data;                                            \
+        _trap->name = #_data;                                           \
+        mrp_list_append(&(_err)->traps, &_trap->hook);                  \
+    } while (0)
+
+/** Macro to run trap handlers then execute (usually return) back up. */
+#define mrp_lua_trigger_traps(_err, _guardptr, _L, _retval) do {            \
+        void             *_guard = (void *)_guardptr;                       \
+        mrp_list_hook_t  *_p, *_n;                                          \
+        mrp_lua_trap_t   *_t;                                               \
+                                                                            \
+        if (_guard == NULL)                                                 \
+            _guard = (void *)_err;                                          \
+                                                                            \
+        mrp_list_foreach(&(_err)->traps, _p, _n) {                          \
+            if ((void *)_p > _guard)                                        \
+                break;                                                      \
+                                                                            \
+            _t = mrp_list_entry(_p, typeof(*_t), hook);                     \
+                                                                            \
+            mrp_list_delete(_p);                                            \
+            mrp_debug_at(__FILE__, __LINE__, "mrp_lua_run_traps",           \
+                        "Running trap '%s'...", _t->name);                  \
+            if (_t->cb(_L, _t->data) < 0)                                   \
+                mrp_log_error("Uh-oh... fasten your seatbelts and prepare " \
+                              "for crash. Trap handler reported failure."); \
+            mrp_free(_p);                                                   \
+        }                                                                   \
+                                                                            \
+        return _retval;                                                     \
+    } while (0)
+
+/** Macro to purge trap handlers up to a given guard. */
+#define mrp_lua_cancel_traps(_err, _guardptr) do {                          \
+        void             *_guard = (void *)_guardptr;                       \
+        mrp_list_hook_t  *_p, *_n;                                          \
+        mrp_lua_trap_t   *_t;                                               \
+                                                                            \
+        if (_guard == NULL)                                                 \
+            _guard = (void *)_err;                                          \
+                                                                            \
+        mrp_list_foreach_back(&(_err)->traps, _p, _n) {                     \
+            if ((void *)_p > _guard)                                        \
+                break;                                                      \
+                                                                            \
+            _t = mrp_list_entry(_p, typeof(*_t), hook);                     \
+                                                                            \
+            mrp_list_delete(_p);                                            \
+            mrp_debug_at(__FILE__, __LINE__, "mrp_lua_cancel_traps",        \
+                        "Cancelling trap '%s'...", _t->name);               \
+            mrp_free(_p);                                                   \
+        }                                                                   \
+    } while (0)
+
+
+/*
+ * debugging
+ */
+
+/** Workhorse macro to produce stack dumps */
+#define __mrp_lua_stackdump(_fl, _ln, _fn, _L, _fmt, _args...) do {         \
+    int _i, _depth, _t;                                                     \
+                                                                            \
+    if (_fmt && *_fmt)                                                      \
+        mrp_debug_at(_fl, _ln, _fn, _fmt, ## _args);                        \
+                                                                            \
+    if ((_depth = lua_gettop(_L)) > 0)                                      \
+        mrp_debug_at(_fl, _ln, _fn, "   Lua stack (depth: %d)", _depth);    \
+    else                                                                    \
+        mrp_debug_at(_fl, _ln, _fn, "   Lua stack: empty");                 \
+                                                                            \
+    for (_i = -1; _i > -1 - _depth; _i--) {                                 \
+        switch ((_t = lua_type(_L, _i))) {                                  \
+        case LUA_TSTRING:                                                   \
+            mrp_debug_at(_fl, _ln, _fn, "      [#%d:'%s']", _i,             \
+                         lua_tostring(_L, _i));                             \
+            break;                                                          \
+        case LUA_TNUMBER:                                                   \
+            if (((int)lua_tointeger(_L,_i) == (double)lua_tonumber(_L,_i))) \
+                mrp_debug_at(_fl, _ln, _fn, "      [#%d: %d]", _i,          \
+                          (int)lua_tointeger(_L, _i));                      \
+            else                                                            \
+                mrp_debug_at(_fl, _ln, _fn, "      [#%d: %f]", _i,          \
+                          (double)lua_tonumber(_L, _i));                    \
+            break;                                                          \
+        case LUA_TTABLE:                                                    \
+            mrp_debug_at(_fl, _ln, _fn, "      [#%d: {%p}]", _i,            \
+                         lua_topointer(_L, _i));                            \
+            break;                                                          \
+        default:                                                            \
+            mrp_debug_at(_fl, _ln, _fn, "      [#%d: %s]", _i,              \
+                         lua_typename(_L, _t));                             \
+        }                                                                   \
+    }                                                                       \
+} while (0)
+
+
+/** Macro to produce a simple debug dump of the Lua stack. */
+#define mrp_lua_stackdump(L, fmt, args...) \
+    __mrp_lua_stackdump(__FILE__, __LINE__, __FUNCTION__, L, fmt, ## args)
+
+
+#endif /* __MURPHY_LUA_ERROR_H__ */
diff --git a/src/core/lua-utils/funcbridge.c b/src/core/lua-utils/funcbridge.c
new file mode 100644 (file)
index 0000000..0e594c4
--- /dev/null
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common.h>
+
+#include <murphy/core/lua-utils/error.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/lua-utils.h>
+
+#define FUNCBRIDGE_METATABLE             "LuaBook.funcbridge"
+#define FUNCBRIDGE_USERDATA_METATABLE    "LuaBook.funcbridge.userdata"
+
+#define FUNCARRAY_METATABLE              "LuaBook.funcarray"
+#define FUNCARRAY_USERDATA_METATABLE     "LuaBook.funcarray.userdata"
+
+static mrp_funcbridge_t *create_funcbridge(lua_State *, int, int);
+static mrp_funcbridge_t *check_funcbridge(lua_State *, int);
+static int call_funcbridge_from_lua(lua_State *);
+static int get_funcbridge_field(lua_State *);
+static int set_funcbridge_field(lua_State *);
+static int funcbridge_destructor(lua_State *);
+
+static int call_funcarray_from_lua(lua_State *);
+static int get_funcarray_field(lua_State *);
+static int set_funcarray_field(lua_State *);
+static mrp_funcarray_t *to_funcarray(lua_State *, int);
+static int funcarray_destructor(lua_State *);
+
+static int make_lua_call(lua_State *, mrp_funcbridge_t *, int);
+
+
+void mrp_create_funcbridge_class(lua_State *L)
+{
+    static const struct luaL_reg class_methods [] = {
+        { NULL, NULL }
+    };
+
+    static const struct luaL_reg override_methods [] = {
+        { "__call"    , call_funcbridge_from_lua },
+        { "__index"   , get_funcbridge_field     },
+        { "__newindex", set_funcbridge_field     },
+        {      NULL   ,     NULL                 }
+    };
+
+    luaL_newmetatable(L, FUNCBRIDGE_USERDATA_METATABLE);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    lua_pushcfunction(L, funcbridge_destructor);
+    lua_setfield(L, -2, "__gc");
+    lua_pop(L, 1);
+
+    luaL_newmetatable(L, FUNCBRIDGE_METATABLE);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, override_methods, 0);
+    lua_pop(L, 1);
+
+    luaL_openlib(L, "builtin.method", class_methods, 0);
+    lua_pushvalue(L, -2);
+    lua_setmetatable(L, -2);
+    lua_pop(L, 1);
+}
+
+
+void mrp_create_funcarray_class(lua_State *L)
+{
+    static const struct luaL_reg override_methods [] = {
+        { "__call"    , call_funcarray_from_lua },
+        { "__index"   , get_funcarray_field     },
+        { "__newindex", set_funcarray_field     },
+        {      NULL   ,     NULL                }
+    };
+
+    luaL_newmetatable(L, FUNCARRAY_USERDATA_METATABLE);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    lua_pushcfunction(L, funcarray_destructor);
+    lua_setfield(L, -2, "__gc");
+    lua_pop(L, 1);
+
+    luaL_newmetatable(L, FUNCARRAY_METATABLE);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, override_methods, 0);
+    lua_pop(L, 1);
+}
+
+
+int parse_signature(const char *signature, char **sigp, mrp_lua_type_t **typep)
+{
+    char            type[256];
+    char           *sigs;
+    mrp_lua_type_t *types, t;
+    int             ntype;
+    int             len;
+    size_t          l;
+
+    *sigp  = sigs  = NULL;
+    *typep = types = NULL;
+    ntype  = 0;
+
+    if (signature == NULL)
+        return 0;
+
+    if ((len = strlen(signature)) == 0)
+        return 0;
+
+    if ((sigs = mrp_allocz(len + 1)) == NULL)
+        return -1;
+
+    if ((types = mrp_allocz_array(typeof(*types), len + 1)) == NULL) {
+        mrp_free(sigs);
+        return -1;
+    }
+
+    *sigp  = sigs;
+    *typep = types;
+
+    while (*signature) {
+        switch (*signature) {
+        case MRP_FUNCBRIDGE_STRING:
+        case MRP_FUNCBRIDGE_INTEGER:
+        case MRP_FUNCBRIDGE_FLOATING:
+        case MRP_FUNCBRIDGE_BOOLEAN:
+        case MRP_FUNCBRIDGE_POINTER:
+        case MRP_FUNCBRIDGE_OBJECT:
+            *sigs++ = *signature++;
+            break;
+
+        case MRP_FUNCBRIDGE_ARRAY:
+            *sigs++ = *signature;
+
+            if (!signature[1] || signature[2] != MRP_FUNCBRIDGE_ARRAY_END) {
+                mrp_log_error("Invalid array reference in signature at '%s'.",
+                              signature);
+                errno = EINVAL;
+                goto fail;
+            }
+
+            switch (signature[1]) {
+            case MRP_FUNCBRIDGE_STRING:
+                types[ntype++] = MRP_LUA_STRING_ARRAY;
+                break;
+            case MRP_FUNCBRIDGE_INTEGER:
+                types[ntype++] = MRP_LUA_INTEGER_ARRAY;
+                break;
+            case MRP_FUNCBRIDGE_FLOATING:
+                types[ntype++] = MRP_LUA_DOUBLE_ARRAY;
+                break;
+            case MRP_FUNCBRIDGE_BOOLEAN:
+                types[ntype++] = MRP_LUA_BOOLEAN_ARRAY;
+                break;
+            case MRP_FUNCBRIDGE_ANY:
+                types[ntype++] = MRP_LUA_ANY;
+                break;
+            default:
+                mrp_log_error("Invalid array type in signature at '%s'.",
+                              signature);
+                errno = EINVAL;
+                goto fail;
+            }
+
+            signature += 3;
+            break;
+
+        case MRP_FUNCBRIDGE_MRPLUATYPE:
+            *sigs++ = *signature;
+
+            if (signature[1] != '(' || !signature[2]) {
+                mrp_log_error("Invalid object signagure at '%s'.", signature);
+                errno = EINVAL;
+                goto fail;
+            }
+
+            signature += 2;
+
+            l = 0;
+            while (*signature && *signature != ')' && l < sizeof(type) - 1) {
+                type[l++] = *signature++;
+            }
+
+            if (*signature != ')' || l >= sizeof(type) - 1) {
+                errno = E2BIG;
+                goto fail;
+            }
+            type[l] = '\0';
+
+            if ((t = mrp_lua_class_type(type)) == MRP_LUA_NONE) {
+                if (type[0] == MRP_FUNCBRIDGE_ANY && type[1] == '\0')
+                    t = MRP_LUA_ANY;
+            }
+
+            if (t == MRP_LUA_NONE) {
+                mrp_log_error("Function bridge signature references "
+                              "unknown type '%s'.", type);
+                errno = EINVAL;
+                goto fail;
+            }
+
+            types[ntype++] = t;
+            signature++;
+            break;
+
+        default:
+            mrp_log_error("Invalid type in signature at '%s'.", signature);
+            errno = EINVAL;
+            goto fail;
+        }
+    }
+
+    types[ntype++] = MRP_LUA_NONE;
+    *sigs = '\0';
+
+    mrp_realloc(types, sizeof(*types) * ntype);
+
+    return 0;
+
+ fail:
+    mrp_free(*sigp);
+    mrp_free(*typep);
+
+    *sigp  = NULL;
+    *typep = NULL;
+
+    return -1;
+}
+
+
+
+mrp_funcbridge_t *mrp_funcbridge_create_cfunc(lua_State *L, const char *name,
+                                              const char *signature,
+                                              mrp_funcbridge_cfunc_t func,
+                                              void *data)
+{
+    int builtin, top;
+    mrp_funcbridge_t *fb;
+
+    top = lua_gettop(L);
+
+    if (mrp_lua_findtable(L, MRP_LUA_GLOBALTABLE, "builtin.method", 20))
+        fb = NULL;
+    else {
+        builtin = lua_gettop(L);
+
+        fb = create_funcbridge(L, 0, 1);
+
+        fb->type = MRP_C_FUNCTION;
+        if (parse_signature(signature, &fb->c.signature, &fb->c.sigtypes) < 0) {
+            mrp_log_error("Failed to parse signature '%s'.", signature);
+            fb->c.signature = mrp_strdup(signature);
+        }
+        else
+            mrp_debug("signature '%s' parsed into '%s'", signature,
+                      fb->c.signature);
+
+        fb->c.func = func;
+        fb->c.data = data;
+
+        lua_pushstring(L, name);
+        lua_pushvalue(L, -2);
+
+        lua_rawset(L, builtin);
+
+        lua_settop(L, top);
+    }
+
+    return fb;
+}
+
+
+mrp_funcbridge_t *mrp_funcbridge_create_luafunc(lua_State *L, int f)
+{
+    mrp_funcbridge_t *fb;
+
+    if (f < 0 && f > LUA_REGISTRYINDEX)
+        f = lua_gettop(L) + f + 1;
+
+    switch (lua_type(L, f)) {
+
+    case LUA_TTABLE:
+        fb = check_funcbridge(L, f);
+        break;
+
+    case LUA_TFUNCTION:
+        fb = create_funcbridge(L, 1, 1);
+        lua_pushvalue(L, f);
+        lua_rawseti(L, -2, 1);
+        lua_pop(L, 1);
+        fb->type = MRP_LUA_FUNCTION;
+        break;
+
+    default:
+        luaL_argcheck(L, false, f < 0 ? lua_gettop(L) + f + 1 : f,
+                      "'builtin.method.xxx' or lua function expected");
+        fb = NULL;
+        break;
+    }
+
+    return fb;
+}
+
+
+mrp_funcbridge_t *mrp_funcbridge_ref(lua_State *L, mrp_funcbridge_t *fb)
+{
+    if (fb->dead)
+        return NULL;
+
+    MRP_UNUSED(L);
+
+    fb->refcnt++;
+
+    return fb;
+}
+
+void mrp_funcbridge_unref(lua_State *L, mrp_funcbridge_t *fb)
+{
+    if (fb == NULL)
+        return;
+
+    if (fb->refcnt > 1)
+        fb->refcnt--;
+    else {
+        free((void *)fb->c.signature);
+        fb->c.signature = NULL;
+
+        if (fb->luatbl) {
+            luaL_unref(L, LUA_REGISTRYINDEX, fb->luatbl);
+            fb->luatbl = 0;
+        }
+    }
+}
+
+bool mrp_funcbridge_call_from_c(lua_State *L,
+                                mrp_funcbridge_t *fb,
+                                const char *signature,
+                                mrp_funcbridge_value_t *args,
+                                char *ret_type,
+                                mrp_funcbridge_value_t *ret_value)
+{
+    char t;
+    int i;
+    int sp;
+    mrp_funcbridge_value_t *a;
+    int sts;
+    bool success;
+
+    if (!fb)
+        success = false;
+    else {
+        mrp_lua_checkstack(L, -1);
+
+        switch (fb->type) {
+
+        case MRP_C_FUNCTION:
+            if (!strcmp(signature, fb->c.signature))
+                success = fb->c.func(L, fb->c.data, signature, args, ret_type,
+                                     ret_value);
+            else {
+                char errmsg[256];
+
+                snprintf(errmsg, sizeof(errmsg),
+                         "mismatching signature @ C invocation ('%s' != '%s')",
+                         signature, fb->c.signature);
+
+                *ret_type = MRP_FUNCBRIDGE_STRING;
+                ret_value->string = mrp_strdup(errmsg);
+                success = false;
+            }
+            break;
+
+        case MRP_LUA_FUNCTION:
+            sp = lua_gettop(L);
+            mrp_funcbridge_push(L, fb);
+            lua_rawgeti(L, -1, 1);
+            luaL_checktype(L, -1, LUA_TFUNCTION);
+            for (i = 0;   (t = signature[i]);   i++) {
+                a = args + i;
+                switch (t) {
+                case MRP_FUNCBRIDGE_STRING:
+                    lua_pushstring(L, a->string);
+                    break;
+                case MRP_FUNCBRIDGE_INTEGER:
+                    lua_pushinteger(L, a->integer);
+                    break;
+                case MRP_FUNCBRIDGE_FLOATING:
+                    lua_pushnumber(L, a->floating);
+                    break;
+                case MRP_FUNCBRIDGE_BOOLEAN:
+                    lua_pushboolean(L, a->boolean);
+                    break;
+                case MRP_FUNCBRIDGE_OBJECT:
+                    mrp_lua_push_object(L, a->pointer);
+                    break;
+                case MRP_FUNCBRIDGE_MRPLUATYPE:
+                    mrp_lua_push_object(L, a->pointer);
+                    break;
+                default:
+                    success = false;
+                    goto done;
+                }
+
+                if (!(i % 20) && i)
+                    mrp_lua_checkstack(L, -1);
+            }
+
+            sts = lua_pcall(L, i, 1, 0);
+
+            MRP_ASSERT(!sts || (sts && lua_type(L, -1) == LUA_TSTRING),
+                       "lua pcall did not return error string when failed");
+
+            switch (lua_type(L, -1)) {
+            case LUA_TSTRING:
+                *ret_type = MRP_FUNCBRIDGE_STRING;
+                ret_value->string = mrp_strdup(lua_tolstring(L, -1, NULL));
+                break;
+            case LUA_TNUMBER:
+                *ret_type = MRP_FUNCBRIDGE_FLOATING;
+                ret_value->floating = lua_tonumber(L, -1);
+                break;
+            case LUA_TBOOLEAN:
+                *ret_type = MRP_FUNCBRIDGE_BOOLEAN;
+                ret_value->boolean = lua_toboolean(L, -1);
+                break;
+            case LUA_TTABLE:
+            {
+                int size = 4; /* array size (initial) */
+                int item_size = 0; /* array item size (calculated) */
+                void *items = NULL;
+                int allowed_type = LUA_TNIL;
+                bool first = true;
+                int j = 0;
+
+                *ret_type = MRP_FUNCBRIDGE_ARRAY;
+
+                /* push NIL to stack as the first key */
+                lua_pushnil(L);
+
+                while (lua_next(L, -2)) {
+                    if (first == true) {
+                        first = false;
+                        allowed_type = lua_type(L, -1);
+                        switch (allowed_type) {
+                        case LUA_TNUMBER:
+                            ret_value->array.type = MRP_FUNCBRIDGE_FLOATING;
+                            item_size = sizeof(lua_Number);
+                            break;
+                        case LUA_TBOOLEAN:
+                            ret_value->array.type = MRP_FUNCBRIDGE_BOOLEAN;
+                            item_size = sizeof(int);
+                            break;
+                        case LUA_TSTRING:
+                            ret_value->array.type = MRP_FUNCBRIDGE_STRING;
+                            item_size = sizeof(char *);
+                            break;
+                        default:
+                            goto error;
+                        }
+                        items = mrp_allocz(size * item_size);
+                        if (!items) {
+                            goto error;
+                        }
+                    }
+                    else {
+                        /* check that all members of the table are of the same
+                         * type */
+                        if (lua_type(L, -1) != allowed_type) {
+                            goto error;
+                        }
+                    }
+
+                    if (size == j+1) {
+                        /* check size */
+                        size *= 2;
+                        items = mrp_realloc(items, size * item_size);
+                        if (!items) {
+                            goto error;
+                        }
+                    }
+
+                    switch (allowed_type) {
+                    case LUA_TNUMBER:
+                    {
+                        lua_Number *arr = (lua_Number *) items;
+                        arr[j] = lua_tonumber(L, -1);
+                        break;
+                    }
+                    case LUA_TBOOLEAN:
+                    {
+                        int *arr = (int *) items;
+                        arr[j] = lua_toboolean(L, -1);
+                        break;
+                    }
+                    case LUA_TSTRING:
+                    {
+                        char **arr = (char **) items;
+                        char *value = mrp_strdup(lua_tostring(L, -1));
+
+                        if (!value) {
+                            goto error;
+                        }
+
+                        arr[j] = value;
+                        break;
+                    }
+                    default:
+                        /* impossible */
+                        break;
+                    }
+
+                    j++;
+
+                    /* remove the value, keep key */
+                    lua_pop(L, 1);
+                }
+
+                ret_value->array.nitem = j;
+                ret_value->array.items = items;
+
+                break;
+            error:
+                if (allowed_type == LUA_TSTRING) {
+                    /* we need to free possibly allocated strings */
+                    int k;
+                    char **arr = items;
+
+                    if (items) {
+                        for (k = 0; k < j; k++) {
+                            mrp_free(arr[k]);
+                        }
+                    }
+                }
+
+                mrp_free(items);
+
+                *ret_type = MRP_FUNCBRIDGE_NO_DATA;
+                memset(ret_value, 0, sizeof(*ret_value));
+                sts = 1; /* success is later calculated from !sts */
+                mrp_log_error("funcbridge: error reading array from Lua");
+                break;
+            }
+
+            default:
+                *ret_type = MRP_FUNCBRIDGE_NO_DATA;
+                memset(ret_value, 0, sizeof(*ret_value));
+                break;
+            }
+            success = !sts;
+        done:
+            lua_settop(L, sp);
+            break;
+
+        default:
+            success = false;
+            break;
+        }
+    }
+
+    return success;
+}
+
+
+int mrp_funcbridge_push(lua_State *L, mrp_funcbridge_t *fb)
+{
+    if (!fb)
+        lua_pushnil(L);
+    else
+        lua_rawgeti(L, LUA_REGISTRYINDEX, fb->luatbl);
+    return 1;
+}
+
+
+mrp_funcarray_t *mrp_funcarray_create(lua_State *L)
+{
+    int table;
+    mrp_funcarray_t *fa;
+
+    lua_createtable(L, 0, 1);
+    table = lua_gettop(L);
+
+    luaL_getmetatable(L, FUNCARRAY_METATABLE);
+    lua_setmetatable(L, table);
+
+    lua_pushliteral(L, "userdata");
+
+    fa = (mrp_funcarray_t *)lua_newuserdata(L,sizeof(mrp_funcarray_t));
+    memset(fa, 0, sizeof(mrp_funcarray_t));
+
+    luaL_getmetatable(L, FUNCARRAY_USERDATA_METATABLE);
+    lua_setmetatable(L, -2);
+
+    lua_rawset(L, table);
+
+    return fa;
+}
+
+
+
+bool mrp_funcarray_call_from_c(lua_State *L,
+                               mrp_funcarray_t *fa,
+                               const char *signature,
+                               mrp_funcbridge_value_t *args)
+{
+    size_t i;
+    bool success, ok;
+    char rtyp;
+    mrp_funcbridge_value_t rval;
+
+    if (!fa || (fa->nfunc > 0 && !fa->funcs))
+        success = false;
+    else {
+        success = true;
+
+        for (i = 0;   i < fa->nfunc;   i++) {
+            ok = mrp_funcbridge_call_from_c(L, fa->funcs[i], signature, args,
+                                            &rtyp, &rval);
+            if (!ok || rtyp != MRP_FUNCBRIDGE_BOOLEAN || !rval.boolean)
+                success = false;
+        }
+    }
+
+    return success;
+}
+
+mrp_funcarray_t *mrp_funcarray_check(lua_State *L, int t)
+{
+    mrp_funcarray_t *fa;
+    size_t i, len;
+
+    if (t < 0 && t > LUA_REGISTRYINDEX)
+        t = lua_gettop(L) + t + 1;
+
+    switch (lua_type(L, t)) {
+
+    case LUA_TFUNCTION:
+        fa = mrp_funcarray_create(L);
+
+        fa->funcs = calloc(1, sizeof(mrp_funcbridge_t *));
+        if (!fa->funcs)
+            return NULL;
+
+        fa->nfunc = 1;
+        fa->funcs[0] = mrp_funcbridge_create_luafunc(L, t);
+
+        break;
+
+    case LUA_TTABLE:
+        if (!(fa = to_funcarray(L, t))) {
+            fa = mrp_funcarray_create(L);
+
+            len = luaL_getn(L, t);
+
+            fa->funcs = calloc(len, sizeof(mrp_funcbridge_t *));
+            fa->nfunc = len;
+
+            for (i = 0;  i < len;  i++) {
+                lua_pushnumber(L, (int)(i + 1));
+                lua_gettable(L, t);
+
+                fa->funcs[i] = mrp_funcbridge_create_luafunc(L, -1);
+
+                lua_pop(L, 1);
+            }
+
+            lua_replace(L, t);
+        }
+        break;
+
+    default:
+        luaL_typerror(L, t, "function array");
+        fa = NULL;
+        break;
+    }
+
+    return fa;
+}
+
+
+static mrp_funcbridge_t *create_funcbridge(lua_State *L, int narr, int nrec)
+{
+    int table;
+    mrp_funcbridge_t *fb;
+
+    lua_createtable(L, narr, nrec);
+    table = lua_gettop(L);
+
+    luaL_getmetatable(L, FUNCBRIDGE_METATABLE);
+    lua_setmetatable(L, table);
+
+    lua_pushliteral(L, "userdata");
+
+    fb = (mrp_funcbridge_t *)lua_newuserdata(L,sizeof(mrp_funcbridge_t));
+    memset(fb, 0, sizeof(mrp_funcbridge_t));
+
+    luaL_getmetatable(L, FUNCBRIDGE_USERDATA_METATABLE);
+    lua_setmetatable(L, -2);
+
+    lua_rawset(L, table);
+
+    lua_pushvalue(L, -1);
+    fb->luatbl = luaL_ref(L, LUA_REGISTRYINDEX);
+
+    fb->refcnt = 1;
+
+    return fb;
+}
+
+
+static mrp_funcbridge_t *check_funcbridge(lua_State *L, int t)
+{
+    mrp_funcbridge_t *fb;
+
+    luaL_checktype(L, t, LUA_TTABLE);
+
+    lua_pushvalue(L, t);
+    lua_pushliteral(L, "userdata");
+    lua_rawget(L, -2);
+
+    fb = luaL_checkudata(L, -1, FUNCBRIDGE_USERDATA_METATABLE);
+    luaL_argcheck(L, fb != NULL, 1, "'function bridge' expected");
+
+    lua_pop(L, 2);
+
+    return fb;
+}
+
+static int call_funcbridge_from_lua(lua_State *L)
+{
+    mrp_funcbridge_t *fb = check_funcbridge(L, 1);
+
+    return make_lua_call(L, fb, 1);
+}
+
+static int get_funcbridge_field(lua_State *L)
+{
+    lua_pushnil(L);
+    return 1;
+}
+
+static int set_funcbridge_field(lua_State *L)
+{
+    return luaL_error(L, "attempt to write a readonly object");
+}
+
+static int funcbridge_destructor(lua_State *L)
+{
+    mrp_funcbridge_t *fb;
+
+    fb = luaL_checkudata(L, -1, FUNCBRIDGE_USERDATA_METATABLE);
+
+    if (!fb->dead) {
+        fb->dead = true;
+        mrp_funcbridge_unref(L, fb);
+    }
+
+    return 0;
+}
+
+static int call_funcarray_from_lua(lua_State *L)
+{
+    mrp_funcarray_t *fa;
+    int narg, top;
+    size_t i;
+    int j;
+    int success;
+
+    top  = lua_gettop(L);
+    narg = top - 1;
+
+    if (!(fa = to_funcarray(L, 1)))
+        luaL_typerror(L, 1, "function array");
+
+    if (fa->nfunc > 0 && !fa->funcs)
+        luaL_error(L, "attempt to call a corruptfunction array");
+
+    success = true;
+
+    for (i = 0;   i < fa->nfunc;   i++) {
+        mrp_funcbridge_push(L, fa->funcs[i]);
+
+        for (j = 0;  j < narg;  j++)
+            lua_pushvalue(L, j+2);
+
+        make_lua_call(L, fa->funcs[i], top+1);
+
+        if (!lua_isboolean(L, -1) || !lua_toboolean(L, -1))
+            success = false;
+
+        lua_settop(L, top);
+    }
+
+    lua_pushboolean(L, success);
+    lua_replace(L, 1);
+
+    lua_settop(L, 1);
+
+    return 1;
+}
+
+static int get_funcarray_field(lua_State *L)
+{
+    lua_pushnil(L);
+    return 1;
+}
+
+static int set_funcarray_field(lua_State *L)
+{
+    luaL_error(L, "attempt to change a function array");
+    return 0;
+}
+
+static mrp_funcarray_t *to_funcarray(lua_State *L, int t)
+{
+    mrp_funcarray_t *fa = NULL;
+
+    t = (t < 0) ? lua_gettop(L) + t + 1 : t;
+
+    if (t < 0 && t > LUA_REGISTRYINDEX)
+        t = lua_gettop(L) + t + 1;
+
+    if (lua_istable(L, t)) {
+        lua_pushstring(L, "userdata");
+        lua_rawget(L, t);
+
+        if (!lua_isnil(L, -1))
+            fa = luaL_checkudata(L, -1, FUNCARRAY_USERDATA_METATABLE);
+
+        lua_pop(L, 1);
+    }
+
+    return fa;
+}
+
+static int funcarray_destructor(lua_State *L)
+{
+    mrp_funcarray_t *fa;
+    size_t i;
+
+    fa = luaL_checkudata(L, -1, FUNCARRAY_USERDATA_METATABLE);
+
+    if (fa->funcs) {
+        for (i = 0;   i < fa->nfunc;   i++)
+            mrp_funcbridge_unref(L, fa->funcs[i]);
+
+        free(fa->funcs);
+    }
+
+    memset(fa, 0, sizeof(mrp_funcarray_t));
+
+    return 0;
+}
+
+
+static inline int funcbridge_type(mrp_lua_type_t type)
+{
+#define MAP(_t, _fbt) case MRP_LUA_##_t: return MRP_FUNCBRIDGE_##_fbt;
+    switch (type) {
+        MAP(STRING , STRING);
+        MAP(INTEGER, INTEGER);
+        MAP(DOUBLE , FLOATING);
+        MAP(BOOLEAN, BOOLEAN);
+        MAP(OBJECT , OBJECT);
+        MAP(STRING_ARRAY , ARRAY);
+        MAP(INTEGER_ARRAY, ARRAY);
+        MAP(DOUBLE_ARRAY , ARRAY);
+        MAP(BOOLEAN_ARRAY, ARRAY);
+    default:
+        return MRP_FUNCBRIDGE_UNSUPPORTED;
+    }
+}
+
+
+static inline int funcbridge_elemtype(mrp_lua_type_t type)
+{
+    switch (type) {
+        MAP(STRING_ARRAY , STRING);
+        MAP(INTEGER_ARRAY, INTEGER);
+        MAP(DOUBLE_ARRAY , FLOATING);
+        MAP(BOOLEAN_ARRAY, BOOLEAN);
+    default:
+        return MRP_FUNCBRIDGE_UNSUPPORTED;
+    }
+}
+
+
+static int autobridge_patch(lua_State *L, void *object, int npop, int *refs)
+{
+    int i;
+
+    /* remove nref elements from the bottom, save references to them */
+    for (i = 1; i <= npop; i++) {
+        lua_pushvalue(L, 1);
+        lua_remove(L, 1);
+    }
+
+    for (i = -npop; i <= -1; i++) {
+        refs[npop+i] = luaL_ref(L, LUA_REGISTRYINDEX);
+    }
+
+    if (object && mrp_lua_check_object(L, NULL, 1) != object) {
+        mrp_log_error("wrong stack detected before calling autobridge");
+#if 0
+        /*
+         * Hmm... on a second though, let's not count on that only
+         * the self/object argument is missing for the autobridge
+         * method call. Who knows what else is wrong with the stack...
+         */
+        if (object != NULL) {
+            mrp_lua_push_object(L, object);
+            lua_insert(L, 1);
+        }
+#endif
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static void autobridge_restore(lua_State *L, void *object, int npop, int *refs)
+{
+    int i;
+
+    MRP_UNUSED(object);
+
+    lua_settop(L, 0);
+
+    for (i = 1; i <= npop; i++) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, refs[i-1]);
+        luaL_unref(L, LUA_REGISTRYINDEX, refs[i-1]);
+        lua_insert(L, 1);
+    }
+}
+
+
+static int make_lua_call(lua_State *L, mrp_funcbridge_t *fb, int f)
+{
+#define ARG_MAX   256
+#define ARRAY_MAX 256
+
+    int ret;
+    int i, n, m, b, e, tidx;
+    const char *s;
+    char t;
+    mrp_funcbridge_value_t args[ARG_MAX];
+    mrp_funcbridge_value_t *a, r;
+    mrp_lua_type_t          type;
+    int                     elem;
+    size_t                  tlen;
+    int                     status;
+    int                     refs[3] = { LUA_NOREF, LUA_NOREF, LUA_NOREF };
+
+    e = lua_gettop(L);
+    f = (f < 0) ? e + f + 1 : f;
+    b = f + 1 + (fb->autobridge ? 1 : 0);
+    n = e - b + 1;
+
+    mrp_debug("fn:%d, beg:%d, end:%d, num:%d", f, b, e, n);
+
+    switch (fb->type) {
+
+    case MRP_C_FUNCTION:
+        m = strlen(fb->c.signature);
+
+        if (n >= ARG_MAX - 1 || n > m)
+            return luaL_error(L, "too many arguments (%d > %d)", n, m);
+        if (n < m)
+            return luaL_error(L, "too few arguments (%d < %d)", n, m);
+
+        tidx = 0;
+        for (i = b, s = fb->c.signature, a= args;    i <= e;    i++, s++, a++){
+            switch (*s) {
+            case MRP_FUNCBRIDGE_STRING:
+                a->string = luaL_checklstring(L, i, NULL);
+                break;
+            case MRP_FUNCBRIDGE_INTEGER:
+                a->integer = luaL_checkinteger(L, i);
+                break;
+            case MRP_FUNCBRIDGE_FLOATING:
+                a->floating = luaL_checknumber(L, i);
+                break;
+            case MRP_FUNCBRIDGE_OBJECT:
+                a->pointer = mrp_lua_check_object(L, NULL, i);
+                break;
+            case MRP_FUNCBRIDGE_BOOLEAN:
+                a->boolean = lua_toboolean(L, i);
+                break;
+            case MRP_FUNCBRIDGE_ARRAY:
+                if (fb->c.sigtypes == NULL ||
+                    (type = fb->c.sigtypes[tidx++]) == MRP_LUA_NONE) {
+                invalid_array_type:
+                    return luaL_error(L, "type info missing for array or "
+                                      "array argument %d", (i - b + 1));
+                }
+
+                switch (type) {
+                case MRP_LUA_STRING_ARRAY:
+                    tlen = sizeof(char  *);
+                    elem = MRP_FUNCBRIDGE_STRING;
+                    break;
+                case MRP_LUA_INTEGER_ARRAY:
+                    tlen = sizeof(int32_t);
+                    elem = MRP_FUNCBRIDGE_INTEGER;
+                    break;
+                case MRP_LUA_DOUBLE_ARRAY:
+                    tlen = sizeof(double );
+                    elem = MRP_FUNCBRIDGE_DOUBLE;
+                    break;
+                case MRP_LUA_BOOLEAN_ARRAY:
+                    tlen = sizeof(bool   );
+                    elem = MRP_FUNCBRIDGE_BOOLEAN;
+                    break;
+                case MRP_LUA_ANY:
+                    tlen = sizeof(double);
+                    elem = MRP_FUNCBRIDGE_ANY;
+                    break;
+                default: goto invalid_array_type;
+                }
+
+                a->array.items = alloca(tlen * ARRAY_MAX);
+                a->array.nitem = ARRAY_MAX;
+
+                if (mrp_lua_object_collect_array(L, i, &a->array.items,
+                                                 &a->array.nitem, &type,
+                                                 false, NULL, 0) < 0) {
+                    return luaL_error(L, "failed to collect array");
+                }
+
+                if (elem == MRP_FUNCBRIDGE_ANY) {
+                    elem = funcbridge_elemtype(type);
+                    if (elem == MRP_FUNCBRIDGE_UNSUPPORTED)
+                        goto invalid_array_type;
+                }
+                a->array.type = elem;
+                break;
+
+            default:
+                return luaL_error(L, "argument %d has unsupported type '%c'",
+                                  (i - b) + 1, i);
+            }
+        }
+        memset(a, 0, sizeof(*a));
+
+        if (fb->autobridge && fb->usestack) {
+            mrp_debug("patching stack for autobridge %p", fb->c.func);
+
+            if (autobridge_patch(L, fb->c.data, 1, refs) < 0) {
+                autobridge_restore(L, fb->c.data, 1, refs);
+                return luaL_error(L, "incorrect stack to call autobridge %p",
+                                  fb->c.func);
+            }
+        }
+
+        status = fb->c.func(L, fb->c.data, fb->c.signature, args, &t, &r);
+
+        if (fb->autobridge && fb->usestack) {
+            mrp_debug("restoring stack after autobridge call");
+
+            autobridge_restore(L, fb->c.data, 1, refs);
+        }
+
+        if (!status)
+            return luaL_error(L, "c function invocation failed");
+
+        switch (t) {
+        case MRP_FUNCBRIDGE_NO_DATA:
+            ret = 0;
+            break;
+        case MRP_FUNCBRIDGE_STRING:
+            ret = 1;
+            lua_pushstring(L, r.string);
+            break;
+        case MRP_FUNCBRIDGE_INTEGER:
+            ret = 1;
+            lua_pushinteger(L, r.integer);
+            break;
+        case MRP_FUNCBRIDGE_FLOATING:
+            ret = 1;
+            lua_pushnumber(L, r.floating);
+            break;
+        default:
+            ret = 0;
+            lua_pushnil(L);
+        }
+        break;
+
+    case MRP_LUA_FUNCTION:
+        lua_rawgeti(L, f, 1);
+        luaL_checktype(L, -1, LUA_TFUNCTION);
+        lua_replace(L, f);
+        lua_pcall(L, n, 1, 0);
+        ret = 1;
+        break;
+
+    default:
+        return luaL_error(L, "internal error");
+    }
+
+    return ret;
+
+#undef ARG_MAX
+#undef ARRAY_MAX
+}
+
+
+int mrp_call_funcbridge(lua_State *L, mrp_funcbridge_t *fb, int f)
+{
+    return make_lua_call(L, fb, f);
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-utils/funcbridge.h b/src/core/lua-utils/funcbridge.h
new file mode 100644 (file)
index 0000000..e81da0a
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_FUNCBRIDGE_H__
+#define __MURPHY_LUA_FUNCBRIDGE_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+typedef union  mrp_funcbridge_value_u  mrp_funcbridge_value_t;
+typedef enum   mrp_funcbridge_type_e   mrp_funcbridge_type_t;
+typedef struct mrp_funcbridge_s        mrp_funcbridge_t;
+typedef struct mrp_funcarray_s         mrp_funcarray_t;
+
+typedef bool (*mrp_funcbridge_cfunc_t)(lua_State *, void *,
+                                       const char *, mrp_funcbridge_value_t *,
+                                       char *, mrp_funcbridge_value_t *);
+
+#include "murphy/core/lua-utils/object.h"
+
+#define MRP_FUNCBRIDGE_NO_DATA      0
+#define MRP_FUNCBRIDGE_UNSUPPORTED '?'
+#define MRP_FUNCBRIDGE_STRING      's'
+#define MRP_FUNCBRIDGE_INTEGER     'd'
+#define MRP_FUNCBRIDGE_FLOATING    'f'
+#define MRP_FUNCBRIDGE_DOUBLE      MRP_FUNCBRIDGE_FLOATING
+#define MRP_FUNCBRIDGE_BOOLEAN     'b'
+#define MRP_FUNCBRIDGE_POINTER     'p'
+#define MRP_FUNCBRIDGE_OBJECT      'o'
+#define MRP_FUNCBRIDGE_ARRAY       '['
+#define MRP_FUNCBRIDGE_ARRAY_END   ']'
+#define MRP_FUNCBRIDGE_MRPLUATYPE  'O'
+#define MRP_FUNCBRIDGE_ANY         '*'
+enum mrp_funcbridge_type_e {
+    MRP_C_FUNCTION = 1,
+    MRP_LUA_FUNCTION
+};
+
+
+union mrp_funcbridge_value_u {
+    const char *string;
+    int32_t     integer;
+    double      floating;
+    bool        boolean;
+    void       *pointer;
+    struct {
+        void   *items;
+        size_t  nitem;
+        char    type;
+    } array;
+};
+
+struct mrp_funcbridge_s {
+    mrp_funcbridge_type_t   type;
+    struct {
+        char *signature;
+        mrp_lua_type_t *sigtypes;
+        mrp_funcbridge_cfunc_t func;
+        void *data;
+    }                       c;
+    int                     luatbl;
+    int                     refcnt;
+    int                     dead : 1;
+    int                     autobridge : 1;  /* autobridged member */
+    int                     usestack : 1;    /* also uses the Lua stack */
+};
+
+struct mrp_funcarray_s {
+    size_t             nfunc;
+    mrp_funcbridge_t **funcs;
+    int                luatbl;
+};
+
+
+void mrp_create_funcbridge_class(lua_State *);
+void mrp_create_funcarray_class(lua_State *);
+
+
+mrp_funcbridge_t *mrp_funcbridge_create_cfunc(lua_State *, const char *,
+                                              const char *,
+                                              mrp_funcbridge_cfunc_t, void *);
+mrp_funcbridge_t *mrp_funcbridge_create_luafunc(lua_State *, int);
+mrp_funcbridge_t *mrp_funcbridge_ref(lua_State *L, mrp_funcbridge_t *);
+void              mrp_funcbridge_unref(lua_State *L, mrp_funcbridge_t *);
+bool              mrp_funcbridge_call_from_c(lua_State *,  mrp_funcbridge_t *,
+                                             const char *,
+                                             mrp_funcbridge_value_t *,
+                                             char *,
+                                             mrp_funcbridge_value_t *);
+int mrp_call_funcbridge(lua_State *L, mrp_funcbridge_t *fb, int f);
+mrp_funcbridge_t *mrp_funcbridge_check(lua_State *, int);
+int               mrp_funcbridge_push(lua_State *, mrp_funcbridge_t *);
+
+mrp_funcarray_t  *mrp_funcarray_create(lua_State *);
+bool              mrp_funcarray_call_from_c(lua_State *, mrp_funcarray_t *,
+                                            const char *,
+                                            mrp_funcbridge_value_t *);
+mrp_funcarray_t  *mrp_funcarray_check(lua_State *, int);
+
+
+
+#endif  /* __MURPHY_LUA_FUNCBRIDGE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-utils/include.c b/src/core/lua-utils/include.c
new file mode 100644 (file)
index 0000000..143bec1
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/file-utils.h>
+
+#include <murphy/core/lua-utils/include.h>
+
+
+/*
+ * tracking data for include-once files
+ */
+
+typedef struct {
+    mrp_list_hook_t hook;                /* to list of files included */
+    dev_t           dev;                 /* device id */
+    ino_t           ino;                 /* file id */
+} file_t;
+
+
+static inline int once_included(mrp_list_hook_t *files, dev_t dev, ino_t ino)
+{
+    mrp_list_hook_t *p, *n;
+    file_t          *f;
+
+    if (files == NULL)
+        return FALSE;
+
+    mrp_list_foreach(files, p, n) {
+        f = mrp_list_entry(p, typeof(*f), hook);
+
+        if (f->dev == dev && f->ino == ino)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static int save_included(mrp_list_hook_t *files, const char *path, dev_t dev,
+                         ino_t ino)
+{
+    file_t *f;
+
+    MRP_UNUSED(path);
+
+    if (files == NULL)
+        return -1;
+
+    if ((f = mrp_allocz(sizeof(*f))) == NULL)
+        return -1;
+
+    mrp_list_init(&f->hook);
+    f->dev = dev;
+    f->ino = ino;
+    mrp_list_append(files, &f->hook);
+
+    return 0;
+}
+
+
+int mrp_lua_include_file(lua_State *L, const char *file, const char **dirs,
+                         mrp_list_hook_t *files)
+{
+    struct stat st;
+    char        path[PATH_MAX];
+
+    if (mrp_find_file(file, dirs, R_OK, path, sizeof(path)) < 0)
+        return -1;
+
+    if (files != NULL) {
+        if (stat(path, &st) < 0)
+            return -1;
+
+        if (once_included(files, st.st_dev, st.st_ino))
+            return 0;
+    }
+
+    mrp_debug("file '%s' resolved to '%s' for inclusion", file, path);
+
+    if (!luaL_loadfile(L, path) && !lua_pcall(L, 0, 0, 0)) {
+        if (files != NULL)
+            save_included(files, path, st.st_dev, st.st_ino);
+
+        return 0;
+    }
+
+    errno = EINVAL;
+    return -1;
+}
diff --git a/src/core/lua-utils/include.h b/src/core/lua-utils/include.h
new file mode 100644 (file)
index 0000000..5a30c4e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012, 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_INCLUDE_H__
+#define __MURPHY_LUA_INCLUDE_H__
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/list.h>
+
+/** Include (read and evaluate) the given Lua file. */
+int mrp_lua_include_file(lua_State *L, const char *file, const char **dirs,
+                         mrp_list_hook_t *files);
+
+/** Include the given Lua file, search the given directories if necessary. */
+#define mrp_lua_include(_L, _file, _dirs)                        \
+    mrp_lua_include_file(_L, _file, _dirs, NULL)
+
+/** Include the given Lua file at most once. */
+#define mrp_lua_include_once(_L, _file, _dirs, _included)        \
+    mrp_lua_include_file(_L, _file, _dirs, _included)
+
+#endif /* __MURPHY_LUA_INCLUDE_H__ */
diff --git a/src/core/lua-utils/lua-utils.c b/src/core/lua-utils/lua-utils.c
new file mode 100644 (file)
index 0000000..0fbc83d
--- /dev/null
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+
+#include "murphy/common/debug.h"
+#include "murphy/common/log.h"
+#include "murphy/core/lua-utils/lua-utils.h"
+
+
+void mrp_lua_setglobal(lua_State *L, const char *name)
+{
+#if LUA_VERSION_NUM >= 502
+    lua_setglobal(L, name);
+#else
+    lua_pushvalue(L, LUA_GLOBALSINDEX);
+    lua_insert(L, -2);
+    lua_setfield(L, -2, name);
+    lua_pop(L, 1);
+#endif
+}
+
+
+void mrp_lua_setglobal_idx(lua_State *L, int idx)
+{
+    if (lua_isstring(L, idx)) {
+        lua_pushvalue(L, idx);
+        mrp_lua_setglobal(L, lua_tostring(L, -1));
+    }
+}
+
+
+void mrp_lua_getglobal(lua_State *L, const char *name)
+{
+#if LUA_VERSION_NUM >= 502
+    lua_getglobal(L, name);
+#else
+    lua_pushvalue(L, LUA_GLOBALSINDEX);
+    lua_getfield(L, -1, name);
+    lua_remove(L, -2);
+#endif
+}
+
+
+void mrp_lua_getglobal_idx(lua_State *L, int idx)
+{
+    if (lua_isstring(L, idx)) {
+        lua_pushvalue(L, idx);
+        mrp_lua_getglobal(L, lua_tostring(L, -1));
+        lua_remove(L, -2);
+    }
+}
+
+
+const char *mrp_lua_findtable(lua_State *L, int t, const char *field, int size)
+{
+    const char *p, *n;
+    size_t      l;
+
+    if (t != MRP_LUA_GLOBALTABLE) {      /* t == 0 indicates a global */
+        if (!lua_istable(L, t))
+            return field;
+        else
+            lua_pushvalue(L, t);
+    }
+
+    for (p = field; p != NULL; p = n && *n ? n + 1: NULL) {
+        if ((n = strchr(p, '.')) != NULL)
+            l = n - p;
+        else
+            l = strlen(p);
+
+        lua_pushlstring(L, p, l);
+
+        if (!(p == field && t == MRP_LUA_GLOBALTABLE))
+            lua_rawget(L, -2);
+        else
+            mrp_lua_getglobal_idx(L, -1);
+
+        switch (lua_type(L, -1)) {
+        case LUA_TTABLE:
+            lua_remove(L, -2);
+            break;
+
+        case LUA_TNIL:
+            lua_pop(L, 1);
+            lua_createtable(L, 0, n && *n ? 1 : size);
+            lua_pushlstring(L, p, l);
+            lua_pushvalue(L, -2);
+            lua_settable(L, -4);
+            lua_remove(L, -2);
+            break;
+
+        default:
+            lua_pop(L, 2);
+            return p;
+        }
+    }
+
+    lua_remove(L, -2);
+    return NULL;
+}
+
+
+void mrp_lua_checkstack(lua_State *L, int extra)
+{
+    /*
+     * Notes:
+     *
+     *   We have a systematic bug throughout our codebase. We never ever
+     *   grow the Lua stack according to our needs. We simply rely on the
+     *   available space to be enough. When we occasionally do run out of
+     *   stack space, this causes severe memory corruption.
+     *
+     *   This is relatively easy to trigger with Lua 5.1.x but much harder
+     *   with Lua 5.2.x (I could not reproduce this with 5.2.x at all).
+     *
+     *   This function is merely a desperate kludgish attemp to try and
+     *   hide the damage caused by the bug. In a couple of commonly used
+     *   functions we call this to make sure there's plenty of space in the
+     *   stack and hope that it will be enough also for those who do not
+     *   ensure this themselves.
+     *
+     * XXX TODO: Eventually we'll need to fix this properly.
+     */
+
+    lua_checkstack(L, extra > 0 ? extra : 40);
+}
+
+
+const char *mrp_lua_callstack(lua_State *L, char *buf, size_t size, int depth)
+{
+    int         top;
+    int         i, b, e;
+    const char *w;
+    char       *p;
+    int         n, l;
+
+    if (depth <= 0)
+        depth = 16;
+
+    top = lua_gettop(L);
+
+    p = buf;
+    l = (int)size;
+
+    *p = '\0';
+
+    b = e = -1;
+    for (i = 0; i < depth; i++) {
+        luaL_where(L, i);
+
+        if (lua_isnil(L, -1))
+            break;
+
+        w = lua_tostring(L, -1);
+
+        if (!(w && *w)) {
+            if (b < 0)
+                b = e = i;
+            else
+                e = i;
+
+            continue;
+        }
+
+        if (b >= 0  && e == i - 1) {
+            if (b != e)
+                n = snprintf(p, l, "\n    [#%d-%d] ?", b, e);
+            else
+                n = snprintf(p, l, "\n    [#%d] ?", b);
+            b = e = -1;
+
+            p += n;
+            l -= n;
+
+            if (l <= 0)
+                goto out;
+        }
+
+        n = snprintf(p, l, "\n    [#%d] @%s", i, w);
+
+        p += n;
+        l -= n;
+
+        if (l <= 0)
+            goto out;
+    }
+
+ out:
+    lua_settop(L, top);
+
+    return buf;
+}
+
+
+void mrp_lua_calltrace(lua_State *L, int depth, bool debug)
+{
+    char buf[1024];
+
+    if (debug)
+        mrp_debug("\n%s", mrp_lua_callstack(L, buf, sizeof(buf), depth));
+    else
+        mrp_log_info("%s", mrp_lua_callstack(L, buf, sizeof(buf), depth));
+}
diff --git a/src/core/lua-utils/lua-utils.h b/src/core/lua-utils/lua-utils.h
new file mode 100644 (file)
index 0000000..33f36de
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2012-2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_UTILS_H__
+#define __MURPHY_LUA_UTILS_H__
+
+#include <stdbool.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#if LUA_VERSION_NUM >= 502
+/* this backward-compatibility macro is no longer pre-defined in Lua 5.2 */
+#    define  luaL_reg luaL_Reg
+
+/* the undocumented luaL_getn is no longer available in 5.2 */
+#    define luaL_getn luaL_len
+
+/* this has been removed from Lua 5.2 */
+static inline int luaL_typerror (lua_State *L, int arg, const char *type) {
+    return luaL_argerror(L, arg, lua_pushfstring(L, "%s expected, got %s",
+                                                 type, luaL_typename(L, arg)));
+}
+
+#    ifndef lua_objlen
+#        define lua_objlen(L, i) lua_rawlen(L, (i))
+#    endif
+#endif
+
+/** Convert the given stack index to an absolute one. */
+static inline int mrp_lua_absidx(lua_State *L, int idx) {
+    return (idx >= 0 ? idx :  (1 + lua_gettop(L) + idx));
+}
+
+/** Convert the given stack index to a relative one. */
+static inline int mrp_lua_relidx(lua_State *L, int idx) {
+    return (idx <= 0 ? idx : -(1 + lua_gettop(L) - idx));
+}
+
+/** Set @name to the value at the top, pops the stack. */
+void mrp_lua_setglobal(lua_State *L, const char *name);
+
+/** Set the value at the top to the name at @idx. */
+void mrp_lua_setglobal_idx(lua_State *L, int idx);
+
+/** Get the value of the global variable @name. Push nil if not found. */
+void mrp_lua_getglobal(lua_State *L, const char *name);
+
+/** Get the value of the name at @idx. Push nil if not found. */
+void mrp_lua_getglobal_idx(lua_State *L, int idx);
+
+/** Traverse table @t to find/create member @field. */
+#define MRP_LUA_GLOBALTABLE 0            /* use as t for globals */
+const char *mrp_lua_findtable(lua_State *L, int t, const char *field, int size);
+
+/** Make sure there's space for at least extra values in the stack. */
+void mrp_lua_checkstack(lua_State *L, int extra);
+
+/** Produce a Lua call stack trace of the given depth. */
+const char *mrp_lua_callstack(lua_State *L, char *buf, size_t size, int depth);
+
+/** Print a Lua call stack trace of the given depth. */
+void mrp_lua_calltrace(lua_State *L, int depth, bool debug);
+
+#endif /* __MURPHY_LUA_UTILS_H__ */
diff --git a/src/core/lua-utils/murphy-lua-utils.pc.in b/src/core/lua-utils/murphy-lua-utils.pc.in
new file mode 100644 (file)
index 0000000..aed5208
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-lua-utils
+Description: Murphy policy framework, LUA support library
+Requires:
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-lua-utils @LUA_LIBS@
+Cflags: -I${includedir}
diff --git a/src/core/lua-utils/object.c b/src/core/lua-utils/object.c
new file mode 100644 (file)
index 0000000..c0bc029
--- /dev/null
@@ -0,0 +1,2877 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/env.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/refcnt.h>
+
+#include <murphy/core/lua-bindings/murphy.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/error.h>
+
+
+#undef  __MURPHY_MANGLE_CLASS_SELF__     /* extra self-mangling if defined */
+#define CHECK    true                    /* do type/self-checking */
+#define NOCHECK (!CHECK)                 /* omit type/self-checking */
+
+/**
+ * Metadata we use to administer objects allocated via us.
+ */
+typedef struct {
+    void *selfish;                       /* verification pointer(ish) to us */
+    mrp_lua_classdef_t *def;             /* class definition for this object */
+    struct {
+        int self;                        /* self reference for static objects */
+        int ext;                         /* object extensions */
+        int priv;                        /* private references */
+    } refs;
+    mrp_refcnt_t refcnt;                 /* object reference count */
+    int  dead : 1;                       /* being cleaned up */
+    int  initializing : 1;               /* being initialized */
+    mrp_list_hook_t hook[0];             /* to object list if we're tracking */
+} userdata_t;
+
+
+static bool valid_id(const char *);
+static int  userdata_destructor(lua_State *);
+
+static void object_create_reftbl(userdata_t *u, lua_State *L);
+static void object_delete_reftbl(userdata_t *u, lua_State *L);
+static void object_create_exttbl(userdata_t *u, lua_State *L);
+static void object_delete_exttbl(userdata_t *u, lua_State *L);
+static int  override_setfield(lua_State *L);
+static int  override_getfield(lua_State *L);
+static int override_tostring(lua_State *L);
+static int  object_setup_bridges(userdata_t *u, lua_State *L);
+
+static void invalid_destructor(void *data);
+static inline int is_native(userdata_t *u, const char *name);
+
+/**
+ * A static non-NULL class definition we return for failed lookups.
+ */
+static mrp_lua_classdef_t invalid_classdef = {
+    .class_name    = "<invalid class>",
+    .class_id      = "<invalid class-id>",
+    .constructor   = "<invalid constructor>",
+    .destructor    = invalid_destructor,
+    .type_name     = "<invalid class type>",
+    .type_id       = MRP_LUA_NONE,
+    .userdata_id   = "<invalid userdata>",
+    .userdata_size = 0,
+    .methods       = NULL,
+    .overrides     = NULL,
+    .members       = NULL,
+    .nmember       = 0,
+    .natives       = NULL,
+    .nnative       = 0,
+    .notify        = NULL,
+    .flags         = 0,
+}, *invalid_class = &invalid_classdef;
+
+
+/**
+ * object infra configurable settings
+ */
+static struct {
+    bool track;                          /* track objects per classdef */
+    bool set;                            /* whether already set */
+    bool busy;                           /* whether taken into use */
+} cfg;
+
+
+/**
+ * indirect table to look up classdefs by type_ids
+ */
+static mrp_lua_classdef_t **classdefs;
+static int                  nclassdef;
+
+/**
+ * Macros to convert between userdata and user-visible data addresses.
+ */
+
+#define USERDATA_SIZE                                                   \
+    (cfg.track?MRP_OFFSET(userdata_t, hook[1]):MRP_OFFSET(userdata_t, hook[0]))
+
+#define USER_TO_DATA(u) user_to_data(u)
+#define DATA_TO_USER(d) data_to_user(d)
+
+static inline void *user_to_data(userdata_t *u)
+{
+    if (u != NULL) {
+        if (cfg.track)
+            return &((userdata_t *)(u))->hook[1];
+        else
+            return &((userdata_t *)(u))->hook[0];
+    }
+    else
+        return NULL;
+}
+
+static inline userdata_t *data_to_user(void *d)
+{
+    if (d != NULL)
+        return ((userdata_t *)(((void *)d) - USERDATA_SIZE));
+    else
+        return NULL;
+}
+
+
+/** Check our configuration from the environment. */
+static void check_config(void)
+{
+    char *config = getenv(MRP_LUA_CONFIG_ENVVAR);
+
+    if (mrp_env_config_bool(config, "track", false))
+        mrp_lua_track_objects(true);
+}
+
+
+/** Encode our self(ish) pointer. */
+static inline void userdata_setself(userdata_t *u)
+{
+#ifdef __MURPHY_MANGLE_CLASS_SELF__
+    void *data = USER_TO_DATA(u);
+
+    u->selfish = (void *)(((ptrdiff_t)u) ^ ((ptrdiff_t)data));
+#else
+    u->selfish = u;
+#endif
+}
+
+/** Decode our self(ish) pointer, return NULL if the most basic check fails. */
+static inline void *userdata_getself(userdata_t *u)
+{
+#ifdef __MURPHY_MANGLE_CLASS_SELF__
+    void *data = USER_TO_DATA(u);
+    void *self = u ? (void *)(((ptrdiff_t)u->selfish) ^ ((ptrdiff_t)data)):NULL;
+#else
+    void *self = u ? u->selfish : NULL;
+#endif
+
+    if (u == self)
+        return self;
+    else
+        return NULL;
+}
+
+/** Check if the give pointer appears to point to a valid userdata. */
+static inline bool valid_userdata(userdata_t *u)
+{
+    return (userdata_getself(u) == u);
+}
+
+/** Obtain userdata for a data pointer, optionally checking basic validity. */
+static inline userdata_t *userdata_get(void *data, bool check)
+{
+    userdata_t *u;
+
+    if (data != NULL) {
+        u = DATA_TO_USER(data);
+
+        if (!check || userdata_getself(u) == u)
+            return u;
+    }
+
+    return NULL;
+}
+
+/** Obtain data for a userdata pointer, optionally checking for validity. */
+static inline void *object_get(userdata_t *u, bool check)
+{
+    if (u != NULL) {
+        if (!check || (userdata_getself(u) == u))
+            return USER_TO_DATA(u);
+    }
+
+    return NULL;
+}
+
+
+/** Create and register a new object class definition. */
+int mrp_lua_create_object_class(lua_State *L, mrp_lua_classdef_t *def)
+{
+    static bool chkconfig = true;
+
+    mrp_debug("registering Lua object class '%s'", def->class_name);
+
+    if (def->constructor == NULL) {
+        mrp_log_error("Classes with NULL constructor not allowed.");
+        mrp_log_error("Please define a constructor for class %s (type %s).",
+                      def->class_name, def->type_name);
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (chkconfig) {
+        check_config();
+        chkconfig = false;
+    }
+
+    /* make a metatatable for userdata, ie for 'c' part of object instances*/
+    luaL_newmetatable(L, def->userdata_id);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    lua_pushcfunction(L, userdata_destructor);
+    lua_setfield(L, -2, "__gc");
+
+    lua_pushcfunction(L, override_tostring);
+    lua_setfield(L, -2, "__tostring");
+
+    lua_pop(L, 1);
+
+    /* define pre-declared members */
+    {
+        mrp_lua_class_member_t  *members  = def->members;
+        int                      nmember  = def->nmember;
+        char                   **natives  = def->natives;
+        int                      nnative  = def->nnative;
+        mrp_lua_class_notify_t   notify   = def->notify;
+        int                      flags    = def->flags;
+
+        def->members = NULL;
+        def->nmember = 0;
+        def->natives = NULL;
+        def->nnative = 0;
+        def->notify  = NULL;
+        def->flags   = 0;
+        def->brmeta  = LUA_NOREF;
+
+        if (mrp_lua_declare_members(def, flags, members, nmember,
+                                    natives, nnative, notify) != 0) {
+            luaL_error(L, "failed to create object class '%s'",
+                       def->class_name);
+        }
+    }
+
+    mrp_list_init(&def->objects);
+
+    /* make the class table */
+    luaL_openlib(L, def->constructor, def->methods, 0);
+
+    /* make a metatable for class, ie. for LUA part of object instances */
+    luaL_newmetatable(L, def->class_id);
+
+    if (mrp_reallocz(classdefs, nclassdef, nclassdef + 1) != NULL) {
+        def->type_id = MRP_LUA_OBJECT + nclassdef;
+        classdefs[nclassdef++] = def;
+    }
+    else {
+        mrp_log_error("Failed to store class %s in lookup table.",
+                      def->class_name);
+        def->type_id = MRP_LUA_NONE;
+    }
+
+    /* XXX TODO we could/should do better identification */
+    def->type_meta = lua_topointer(L, -1);
+
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+
+    luaL_openlib(L, NULL, def->overrides, 0);
+    lua_setmetatable(L, -2);
+
+    lua_pop(L, 1);
+
+    return 0;
+}
+
+
+/** Traverse a dott global name and push the table it resolves to, or nil. */
+void mrp_lua_get_class_table(lua_State *L, mrp_lua_classdef_t *def)
+{
+#if 0
+    const char *p;
+    char *q;
+    char tag[256];
+
+    lua_pushvalue(L, LUA_GLOBALSINDEX);
+
+    for (p = def->constructor, q = tag; *p;  p++) {
+        if ((*q++ = *p) == '.') {
+            q[-1] = '\0';
+            lua_getfield(L, -1, tag);
+            if (lua_type(L, -1) != LUA_TTABLE) {
+                lua_pop(L, 2);
+                lua_pushnil(L);
+                return;
+            }
+            lua_remove(L, -2);
+            q = tag;
+        }
+    } /* for */
+
+    *q = '\0';
+
+    lua_getfield(L, -1, tag);
+    lua_remove(L, -2);
+#else
+    const char *p;
+    char *q;
+    char tag[256];
+    int  idx;
+
+    for (p = def->constructor, q = tag, idx = 0; *p;  p++) {
+        if ((*q++ = *p) == '.') {
+            q[-1] = '\0';
+            if (idx++ == 0) {
+                lua_pushnil(L);
+                mrp_lua_getglobal(L, tag);
+            }
+            else
+                lua_getfield(L, -1, tag);
+            if (lua_type(L, -1) != LUA_TTABLE) {
+                lua_pop(L, 2);
+                lua_pushnil(L);
+                return;
+            }
+            lua_remove(L, -2);
+            q = tag;
+        }
+    } /* for */
+
+    *q = '\0';
+
+    if (idx++ == 0) {
+        lua_pushnil(L);
+        mrp_lua_getglobal(L, tag);
+    }
+    else
+        lua_getfield(L, -1, tag);
+
+    lua_remove(L, -2);
+#endif
+}
+
+
+static void invalid_destructor(void *data)
+{
+    MRP_UNUSED(data);
+    mrp_log_error("<invalid-destructor> called");
+}
+
+
+static mrp_lua_classdef_t *class_by_type(int type_id)
+{
+    int idx = type_id - MRP_LUA_OBJECT;
+
+    if (0 <= idx && idx < nclassdef)
+        return classdefs[idx];
+    else
+        return invalid_class;
+}
+
+
+static mrp_lua_classdef_t *class_by_type_name(const char *type_name)
+{
+    mrp_lua_classdef_t *def;
+    int                 i;
+
+    for (i = 0; i < nclassdef; i++) {
+        def = classdefs[i];
+
+        if (def->type_name[0] != type_name[0])
+            continue;
+
+        if (!strcmp(def->type_name + 1, type_name + 1))
+            return def;
+    }
+
+    return invalid_class;
+}
+
+
+static mrp_lua_classdef_t *class_by_class_name(const char *class_name)
+{
+    mrp_lua_classdef_t *def;
+    int                 i;
+
+    for (i = 0; i < nclassdef; i++) {
+        def = classdefs[i];
+
+        if (def->class_name[0] != class_name[0])
+            continue;
+
+        if (!strcmp(def->class_name + 1, class_name + 1))
+            return def;
+    }
+
+    return invalid_class;
+}
+
+
+static mrp_lua_classdef_t *class_by_class_id(const char *class_id)
+{
+    mrp_lua_classdef_t *def;
+    int                 i;
+
+    for (i = 0; i < nclassdef; i++) {
+        def = classdefs[i];
+
+        if (def->class_id[0] != class_id[0])
+            continue;
+
+        if (!strcmp(def->class_id + 1, class_id + 1))
+            return def;
+    }
+
+    return invalid_class;
+}
+
+
+static mrp_lua_classdef_t *class_by_userdata_id(const char *userdata_id)
+{
+    mrp_lua_classdef_t *def;
+    int                 i;
+
+    for (i = 0; i < nclassdef; i++) {
+        def = classdefs[i];
+
+        if (def->userdata_id[0] != userdata_id[0])
+            continue;
+
+        if (!strcmp(def->userdata_id + 1, userdata_id + 1))
+            return def;
+    }
+
+    return invalid_class;
+}
+
+/** Get the type_id for the given class name. */
+mrp_lua_type_t mrp_lua_class_name_type(const char *class_name)
+{
+    return class_by_class_name(class_name)->type_id;
+}
+
+/** Get the type_id for the given class id. */
+mrp_lua_type_t mrp_lua_class_id_type(const char *class_id)
+{
+    return class_by_class_id(class_id)->type_id;
+}
+
+/** Get the type_id for the given class type name. */
+mrp_lua_type_t mrp_lua_class_type(const char *type_name)
+{
+    return class_by_type_name(type_name)->type_id;
+}
+
+/** Dump the given oject instance for debugging. */
+static const char *__instance(userdata_t **uptr, const char *fmt)
+{
+    static char buf[16][256];
+    static int  idx = 0;
+
+    userdata_t *u = uptr ? *uptr : NULL;
+    char       *p = buf[idx++];
+    char       *r = p;
+    const char *s;
+    int         l, n;
+
+    /*
+     * Notes: The currently implemeted format specifiers are:
+     *     'D': dynamic flag, avaluates to 'S' or 'D'
+     *     't': object type name
+     *     'i': object instancem indirect userdata pointer
+     *     'u': object userdata_t
+     *     'd': object user-visible data
+     *     'S': USERDATA_SIZE
+     *     'R': reference count
+     */
+
+    if (!fmt || !*fmt || (fmt[0] == '*' && fmt[1] == '\0'))
+        fmt = "<%D:%t>%i:(%u+%S)>";
+
+    l = (int)sizeof(buf[0]);
+    s = fmt;
+    while (*s && l > 0) {
+        if (*s != '%') {
+            *p++ = *s++;
+            l--;
+            continue;
+        }
+
+        if (!u) {
+            *p++ = '?';
+            s++;
+            l--;
+            continue;
+        }
+
+        s++;
+
+#define P(fmt, arg) n = snprintf(p, l, fmt, arg);
+        switch (*s) {
+        case 'D': P("%s", u->def->flags & MRP_LUA_CLASS_DYNAMIC?"D":"S"); break;
+        case 't': P("%s", u->def->type_name);                             break;
+        case 'i': P("%p", uptr);                                          break;
+        case 'u': P("%p", u);                                             break;
+        case 'd': P("%p", USER_TO_DATA(u));                               break;
+        case 'S': P("%d", (int)USERDATA_SIZE);                            break;
+        case 'R': P("%d", (int)u->refcnt);                                break;
+        default : P("%s", "?");                                           break;
+        }
+#undef P
+
+        p += n;
+        l -= n;
+
+        if (*s)
+            s++;
+    }
+
+    if (l <= 0)
+        buf[idx-1][sizeof(buf[0])-1] = '\0';
+
+    if (idx >= (int)MRP_ARRAY_SIZE(buf))
+        idx = 0;
+
+    return r;
+}
+
+/** Dump the given object for debugging. */
+static const char *__object(userdata_t *u, const char *fmt)
+{
+    static char buf[16][256];
+    static int  idx = 0;
+
+    char       *p = buf[idx++];
+    char       *r = p;
+    const char *s;
+    int         l, n;
+
+    /*
+     * Notes: The currently implemeted format specifiers are:
+     *     'D': dynamic flag, avaluates to 'S' or 'D'
+     *     't': object type name
+     *     'u': object userdata_t
+     *     'd': object user-visible data
+     *     'S': USERDATA_SIZE
+     *     'R': reference count
+     */
+
+    if (!fmt || !*fmt || (fmt[0] == '*' && fmt[1] == '\0'))
+        fmt = "<%D:%t>%i:(%u+%S)>";
+
+    l = (int)sizeof(buf[0]);
+    s = fmt;
+    while (*s && l > 0) {
+        if (*s != '%') {
+            *p++ = *s++;
+            l--;
+            continue;
+        }
+
+        if (!u) {
+            *p++ = '?';
+            s++;
+            l--;
+            continue;
+        }
+
+        s++;
+
+#define P(fmt, arg) n = snprintf(p, l, fmt, arg);
+        switch (*s) {
+        case 'D': P("%s", u->def->flags & MRP_LUA_CLASS_DYNAMIC?"D":"S"); break;
+        case 't': P("%s", u->def->type_name);                             break;
+        case 'i': P("%s", "?");                                           break;
+        case 'u': P("%p", u);                                             break;
+        case 'd': P("%p", USER_TO_DATA(u));                               break;
+        case 'S': P("%d", (int)USERDATA_SIZE);                            break;
+        case 'R': P("%d", (int)u->refcnt);                                break;
+        default : P("%s", "?");                                           break;
+        }
+#undef P
+
+        p += n;
+        l -= n;
+
+        if (*s)
+            s++;
+    }
+
+    if (l <= 0)
+        buf[idx-1][sizeof(buf[0])-1] = '\0';
+
+    if (idx >= (int)MRP_ARRAY_SIZE(buf))
+        idx = 0;
+
+    return r;
+}
+
+
+/** Create a new object, optionally assign it to a class table name or index. */
+void *mrp_lua_create_object(lua_State *L, mrp_lua_classdef_t *def,
+                            const char *name, int idx)
+{
+    int class = 0;
+    size_t size;
+    userdata_t **userdatap, *userdata;
+    int dynamic;
+
+    MRP_UNUSED(class_by_userdata_id);
+
+    mrp_lua_checkstack(L, -1);
+
+    if (name || idx) {
+        if (name && !valid_id(name))
+            return NULL;
+
+        mrp_lua_get_class_table(L, def);
+        luaL_checktype(L, -1, LUA_TTABLE);
+        class = lua_gettop(L);
+    }
+
+    lua_createtable(L, 1, 1);
+
+    luaL_openlib(L, NULL, def->methods, 0);
+
+    luaL_getmetatable(L, def->class_id);
+    lua_setmetatable(L, -2);
+
+    lua_pushliteral(L, "userdata");
+
+    size = USERDATA_SIZE + def->userdata_size;
+    userdata = (userdata_t *)mrp_allocz(size);
+
+    if (userdata == NULL) {
+        mrp_log_error("Failed to allocate object of type %s <%s>.",
+                      def->class_name, def->type_name);
+        return NULL;
+    }
+
+    userdatap = (userdata_t **)lua_newuserdata(L, sizeof(userdata));
+    *userdatap = userdata;
+    mrp_refcnt_init(&userdata->refcnt);
+
+    if (cfg.track)
+        mrp_list_init(&userdata->hook[0]);
+
+    userdata->refs.priv = LUA_NOREF;
+    userdata->refs.ext  = LUA_NOREF;
+
+    luaL_getmetatable(L, def->userdata_id);
+    lua_setmetatable(L, -2);
+
+    lua_rawset(L, -3);              /* userdata["userdata"]=lib<def->methods> */
+
+    userdata_setself(userdata);
+    userdata->def    = def;
+
+    if (!(dynamic = def->flags & MRP_LUA_CLASS_DYNAMIC)) {
+        lua_pushvalue(L, -1);       /* userdata->refs.self = lib<def->methods> */
+        userdata->refs.self = luaL_ref(L, LUA_REGISTRYINDEX);
+    }
+    else
+        userdata->refs.self = LUA_NOREF;
+
+    if (name) {
+        lua_pushstring(L, name);
+        lua_pushvalue(L, -2);
+        lua_rawset(L, class);
+    }
+
+    if (idx) {
+        lua_pushvalue(L, -1);
+        lua_rawseti(L, class, idx);
+    }
+
+    if (class)
+        lua_remove(L, class);
+
+    object_create_reftbl(userdata, L);
+    if (def->flags & MRP_LUA_CLASS_EXTENSIBLE)
+        object_create_exttbl(userdata, L);
+
+    if (object_setup_bridges(userdata, L) < 0) {
+        luaL_error(L, "Failed to set up bridged methods.");
+        return NULL;                     /* not reached */
+    }
+
+    if (cfg.track)
+        mrp_list_append(&def->objects, &userdata->hook[0]);
+
+    def->nactive++;
+    def->ncreated++;
+
+    mrp_debug("created %s", __instance(userdatap, "*"));
+
+    return USER_TO_DATA(userdata);
+}
+
+
+/** Set the name of the object @-1 to the given name in the class table. */
+void mrp_lua_set_object_name(lua_State *L, mrp_lua_classdef_t *def,
+                             const char *name)
+{
+    if (valid_id(name)) {
+        mrp_lua_get_class_table(L, def);
+        luaL_checktype(L, -1, LUA_TTABLE);
+
+        lua_pushstring(L, name);
+        lua_pushvalue(L, -3);
+
+        lua_rawset(L, -3);
+        lua_pop(L, 1);
+    }
+}
+
+/** Assign the object @-1 to the given index in the class table. */
+void mrp_lua_set_object_index(lua_State *L, mrp_lua_classdef_t *def, int idx)
+{
+    mrp_lua_get_class_table(L, def);
+    luaL_checktype(L, -1, LUA_TTABLE);
+
+    lua_pushvalue(L, -2);
+
+    lua_rawseti(L, -2, idx);
+
+    lua_pop(L, 1);
+}
+
+/** Trigger (potential) destruction of the given object. */
+void mrp_lua_destroy_object(lua_State *L, const char *name, int idx, void *data)
+{
+    userdata_t *userdata = userdata_get(data, CHECK);
+    mrp_lua_classdef_t *def;
+
+    if (userdata) {
+        if (userdata->dead)
+            return;
+
+        userdata->dead = true;
+        def = userdata->def;
+
+        if (!(def->flags & MRP_LUA_CLASS_DYNAMIC)) {
+            mrp_debug("destroying %s (name: '%s', idx: %d)",
+                      __object(userdata, "*"), name ? name : "", idx);
+
+            def->nactive--;
+            def->ndead++;
+
+            object_delete_reftbl(userdata, L);
+            object_delete_exttbl(userdata, L);
+
+            if (userdata->refs.self != LUA_NOREF) {
+                lua_rawgeti(L, LUA_REGISTRYINDEX, userdata->refs.self);
+                lua_pushstring(L, "userdata");
+                lua_pushnil(L);
+                lua_rawset(L, -3);
+                lua_pop(L, -1);
+
+                luaL_unref(L, LUA_REGISTRYINDEX, userdata->refs.self);
+                userdata->refs.self = LUA_NOREF;
+            }
+        }
+        else {
+            mrp_log_error("ERROR: %s should be called for static object",
+                          __FUNCTION__);
+            mrp_log_error("ERROR: but was called for %s",
+                          __object(userdata, "*"));
+        }
+
+        if (name || idx) {
+            mrp_lua_get_class_table(L, def);
+            luaL_checktype(L, -1, LUA_TTABLE);
+
+            if (name) {
+                lua_pushstring(L, name);
+                lua_pushnil(L);
+                lua_rawset(L, -3);
+            }
+
+            if (idx) {
+                lua_pushnil(L);
+                lua_rawseti(L, -2, idx);
+            }
+
+            lua_pop(L, 1);
+        }
+    }
+}
+
+
+/** Find the object corresponding to the given name in the class table. */
+int mrp_lua_find_object(lua_State *L, mrp_lua_classdef_t *def, const char *name)
+{
+    if (!name)
+        lua_pushnil(L);
+    else {
+        mrp_lua_get_class_table(L, def);
+        luaL_checktype(L, -1, LUA_TTABLE);
+
+        lua_pushstring(L, name);
+        lua_rawget(L, -2);
+
+        lua_remove(L, -2);
+    }
+
+    return 1;
+}
+
+/** Check if the object @idx is ours and optionally of the given type. */
+void *mrp_lua_check_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
+{
+    userdata_t *userdata, **userdatap;
+    char errmsg[256];
+
+    luaL_checktype(L, idx, LUA_TTABLE);
+
+    lua_pushvalue(L, idx);
+    lua_pushliteral(L, "userdata");
+    lua_rawget(L, -2);
+
+    if (!def) {
+        userdatap = (userdata_t **)lua_touserdata(L, -1);
+
+        if (!userdatap) {
+            luaL_argerror(L, idx, "couldn't find expected userdata");
+            userdata = NULL;
+        }
+        else
+            userdata = *userdatap;
+    }
+    else {
+        userdatap = (userdata_t **)luaL_checkudata(L, -1, def->userdata_id);
+
+        if (!userdatap || def != (userdata = *userdatap)->def) {
+            snprintf(errmsg, sizeof(errmsg), "'%s' expected", def->class_name);
+            luaL_argerror(L, idx, errmsg);
+            userdata = NULL;
+        }
+        else
+            userdata = *userdatap;
+    }
+
+    if (userdata_getself(userdata) != userdata) {
+        luaL_error(L, "invalid userdata");
+        userdata = NULL;
+    }
+
+    lua_pop(L, 2);
+
+    return userdata ? USER_TO_DATA(userdata) : NULL;
+}
+
+/** Check if the object @idx is of the given virtual type. */
+int mrp_lua_object_of_type(lua_State *L, int idx, mrp_lua_type_t type)
+{
+    mrp_lua_type_t      ltype = (mrp_lua_type_t)lua_type(L, idx);
+    mrp_lua_classdef_t *def;
+    int                 match;
+
+    switch (type) {
+    case MRP_LUA_NULL:
+    case MRP_LUA_BOOLEAN:
+    case MRP_LUA_STRING:
+    case MRP_LUA_DOUBLE:
+    case MRP_LUA_FUNC:
+        return (type == ltype);
+
+    case MRP_LUA_INTEGER:
+        return ((int)lua_tointeger(L, idx) == (double)lua_tonumber(L, idx));
+
+    case MRP_LUA_LFUNC:
+        return (ltype == LUA_TFUNCTION && !lua_iscfunction(L, idx));
+    case MRP_LUA_CFUNC:
+        return (ltype == LUA_TFUNCTION &&  lua_iscfunction(L, idx));
+    case MRP_LUA_BFUNC:
+        /* XXX TODO */ mrp_log_error("Can't handle funcbridge yet.");
+        return false;
+
+    case MRP_LUA_BOOLEAN_ARRAY:
+    case MRP_LUA_STRING_ARRAY:
+    case MRP_LUA_INTEGER_ARRAY:
+    case MRP_LUA_DOUBLE_ARRAY:
+        return (ltype == LUA_TTABLE); /* XXX could do be better */
+
+    case MRP_LUA_NONE:
+        return false;
+    case MRP_LUA_ANY:
+        return true;
+
+    case MRP_LUA_OBJECT:
+        return (ltype == LUA_TTABLE); /* XXX could do much be better */
+
+    default:
+        if (type > MRP_LUA_MAX)
+            return false;
+
+        if ((def = class_by_type(type)) == invalid_class)
+            return false;
+
+        if (lua_getmetatable(L, idx)) {
+             /* XXX TODO we could/should do better identification */
+            match = (lua_topointer(L, idx) == def->type_meta);
+            lua_pop(L, 1);
+        }
+        else
+            match = false;
+
+        return match;
+    }
+
+    return false;
+}
+
+/** Check if the given data is of the given virtual type. */
+int mrp_lua_pointer_of_type(void *data, mrp_lua_type_t type)
+{
+    userdata_t *u;
+
+    if (type < MRP_LUA_OBJECT) {
+        mrp_log_error("Can't do pointer-based type-equality for "
+                      "non-object types.");
+        return 0;
+    }
+
+    /*
+     * We consider NULL to be a valid instance. Might need to be changed.
+     */
+
+    if ((u = userdata_get(data, CHECK)) != NULL)
+        return type == u->def->type_id;
+    else
+        return true;
+}
+
+/** Obtain the user-visible data for the object @idx. */
+void *mrp_lua_to_object(lua_State *L, mrp_lua_classdef_t *def, int idx)
+{
+    userdata_t *userdata, **userdatap;
+    int top = lua_gettop(L);
+
+    idx = (idx < 0) ? lua_gettop(L) + idx + 1 : idx;
+
+    if (!lua_istable(L, idx))
+        return NULL;
+
+    lua_pushliteral(L, "userdata");
+    lua_rawget(L, idx);
+
+    userdatap = (userdata_t **)lua_touserdata(L, -1);
+
+    if (!userdatap || !lua_getmetatable(L, -1)) {
+        lua_settop(L, top);
+        return NULL;
+    }
+
+    userdata = *userdatap;
+
+    lua_getfield(L, LUA_REGISTRYINDEX, def->userdata_id);
+
+    if (!lua_rawequal(L, -1, -2) || userdata != userdata_getself(userdata))
+        userdata = NULL;
+
+    lua_settop(L, top);
+
+    return userdata ? USER_TO_DATA(userdata) : NULL;
+}
+
+
+/** Push the given data on the stack. */
+int mrp_lua_push_object(lua_State *L, void *data)
+{
+    userdata_t *userdata = userdata_get(data, CHECK);
+    userdata_t **userdatap;
+    mrp_lua_classdef_t *def = userdata ? userdata->def : NULL;
+
+    /*
+     * Notes:
+     *
+     *    This is essentially mrp_lua_create_object with a few differences:
+     *      1) No need for name or idx handling.
+     *      2) No need for adding global reference, already done during
+     *         initial object creation if necessary.
+     *      3) Instead of creating a Lua userdata pointer and a new userdata,
+     *         we only create a Lua userdata pointer, make it point to the
+     *         existing userdata and increate the userdata reference count.
+     *
+     *   userdata_destructor has been similarly modified to decrese the
+     *   reference count of userdata and destroy the object only when the
+     *   last reference is dropped.
+     */
+
+    mrp_lua_checkstack(L, -1);
+
+    if (!userdata || !def || userdata->dead) {
+        lua_pushnil(L);
+        return 1;
+    }
+
+    if (!(def->flags & MRP_LUA_CLASS_DYNAMIC)) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, userdata->refs.self);
+
+        mrp_debug("pushed %s", __object(userdata, "*"));
+    }
+    else {
+        lua_createtable(L, 1, 1);
+
+        luaL_openlib(L, NULL, def->methods, 0);
+
+        luaL_getmetatable(L, def->class_id);
+        lua_setmetatable(L, -2);
+
+        lua_pushliteral(L, "userdata");
+
+        userdatap = (userdata_t **)lua_newuserdata(L, sizeof(userdata));
+        *userdatap = userdata;
+        mrp_ref_obj(userdata, refcnt);
+
+        luaL_getmetatable(L, def->userdata_id);
+        lua_setmetatable(L, -2);
+
+        lua_rawset(L, -3);
+
+        mrp_debug("pushed %s", __instance(userdatap, "*"));
+    }
+
+    return 1;
+}
+
+/** Obtain the class definition for the given object. */
+mrp_lua_classdef_t *mrp_lua_get_object_classdef(void *data)
+{
+    userdata_t *userdata = userdata_get(data, CHECK);
+    mrp_lua_classdef_t *def;
+
+    if (!userdata || userdata->dead)
+        def = NULL;
+    else
+        def = userdata->def;
+
+    return def;
+}
+
+
+static bool valid_id(const char *id)
+{
+    const char *p;
+    char c;
+
+    if (!(p = id) || !isalpha(*p))
+        return false;
+
+    while ((c = *p++)) {
+        if (!isalnum(c) && (c != '_'))
+            return false;
+    }
+
+    return true;
+}
+
+static int userdata_destructor(lua_State *L)
+{
+    userdata_t *userdata, **userdatap;
+    mrp_lua_classdef_t *def;
+
+    if (!(userdatap = lua_touserdata(L, -1)) || !lua_getmetatable(L, -1))
+        luaL_error(L, "attempt to destroy unknown type of userdata");
+    else {
+        userdata = *userdatap;
+        def = userdata->def;
+
+        if (mrp_unref_obj(userdata, refcnt)) {
+            mrp_debug("freeing %s", __instance(userdatap, "*"));
+
+            if (cfg.track)
+                mrp_list_delete(&userdata->hook[0]);
+
+            lua_getfield(L, LUA_REGISTRYINDEX, def->userdata_id);
+            if (!lua_rawequal(L, -1, -2))
+                luaL_typerror(L, -2, def->userdata_id);
+            else
+                def->destructor(USER_TO_DATA(userdata));
+
+            if (def->flags & MRP_LUA_CLASS_DYNAMIC) {
+                def->nactive--;
+
+                object_delete_reftbl(userdata, L);
+                object_delete_exttbl(userdata, L);
+            }
+            else {
+                def->ndead--;
+            }
+
+            def->ndestroyed++;
+
+            *userdatap = NULL;
+            mrp_free(userdata);
+        }
+        else {
+            mrp_debug("unreffed %s", __instance(userdatap, "*"));
+
+            if (!(def->flags & MRP_LUA_CLASS_DYNAMIC))
+                mrp_log_error("Hmm, more refs for a static object ?");
+
+            *userdatap = NULL;
+        }
+    }
+
+    return 0;
+}
+
+
+static int default_setter(void *data, lua_State *L, int member,
+                          mrp_lua_value_t *v)
+{
+    userdata_t             *u = DATA_TO_USER(data);
+    mrp_lua_class_member_t *m;
+    mrp_lua_value_t        *vptr;
+    void                  **itemsp;
+    union {
+        void     *ptr;
+        size_t   *size;
+        uint32_t *u32;
+    } nitemp;
+    size_t                   nitem;
+
+    mrp_lua_checkstack(L, -1);
+
+    m    = u->def->members + member;
+    vptr = data + m->offs;
+
+    if (L == NULL) {
+        switch (m->type) {
+        case MRP_LUA_STRING:
+            vptr->str = NULL;
+            goto ok;
+        case MRP_LUA_FUNC:
+        case MRP_LUA_LFUNC:
+        case MRP_LUA_CFUNC:
+            vptr->lfn = LUA_NOREF;
+            goto ok;
+        case MRP_LUA_BFUNC:
+            vptr->bfn = NULL;
+            goto ok;
+        case MRP_LUA_ANY:
+            vptr->any = LUA_NOREF;
+            goto ok;
+        case MRP_LUA_STRING_ARRAY:
+        case MRP_LUA_BOOLEAN_ARRAY:
+        case MRP_LUA_INTEGER_ARRAY:
+        case MRP_LUA_DOUBLE_ARRAY:
+            itemsp     = data + m->offs;
+            nitemp.ptr = data + m->size;
+            *itemsp    = NULL;
+            if (m->sizew == 8)
+                *nitemp.size = 0;
+            else
+                *nitemp.u32  = 0;
+            goto ok;
+        case MRP_LUA_OBJECT:
+            if (m->type_id == MRP_LUA_NONE)
+                m->type_id = class_by_type_name(m->type_name)->type_id;
+            *((void **)(data + m->offs)) = NULL;
+            *((int   *)(data + m->size)) = LUA_NOREF;
+        default:
+            goto error;
+        }
+    }
+
+    switch (m->type) {
+    case MRP_LUA_STRING:
+        mrp_free((void *)vptr->str);
+        vptr->str = v->str ? mrp_strdup(v->str) : NULL;
+
+        if (vptr->str == NULL && v->str != NULL)
+            goto error;
+        else
+            goto ok;
+
+    case MRP_LUA_BOOLEAN:
+        vptr->bln = v->bln;
+        goto ok;
+
+    case MRP_LUA_INTEGER:
+        vptr->s32 = v->s32;
+        goto ok;
+
+    case MRP_LUA_DOUBLE:
+        vptr->dbl = v->dbl;
+        goto ok;
+
+    case MRP_LUA_FUNC:
+        mrp_lua_object_unref_value(data, L, vptr->lfn);
+        vptr->lfn = v->lfn;
+        goto ok;
+
+    case MRP_LUA_LFUNC:
+        mrp_lua_object_unref_value(data, L, vptr->lfn);
+        vptr->lfn = v->lfn;
+        goto ok;
+
+    case MRP_LUA_CFUNC:
+        mrp_lua_object_unref_value(data, L, vptr->lfn);
+        vptr->lfn = v->lfn;
+        goto ok;
+
+    case MRP_LUA_BFUNC:
+        goto error;
+
+    case MRP_LUA_ANY:
+        mrp_lua_object_unref_value(data, L, vptr->any);
+        vptr->any = v->any;
+        goto ok;
+
+    case MRP_LUA_STRING_ARRAY:
+    case MRP_LUA_BOOLEAN_ARRAY:
+    case MRP_LUA_INTEGER_ARRAY:
+    case MRP_LUA_DOUBLE_ARRAY:
+        itemsp     = data + m->offs;
+        nitemp.ptr = data + m->size;
+        if (m->sizew == 8)
+            nitem = *nitemp.size;
+        else
+            nitem = *nitemp.u32;
+        mrp_lua_object_free_array(itemsp, &nitem, m->type);
+        *itemsp = *v->array.items;
+        if (m->sizew == 8)
+            *nitemp.size = *v->array.nitem64;
+        else
+            *nitemp.u32 = (uint32_t)*v->array.nitem64;
+        goto ok;
+
+    case MRP_LUA_OBJECT:
+        mrp_lua_object_unref_value(data, L, *((int *)(data + m->size)));
+        *((void **)(data + m->offs)) = v->obj.ptr;
+        *((int   *)(data + m->size)) = v->obj.ref;
+        goto ok;
+
+    default:
+        goto error;
+    }
+
+ ok:
+    return 1;
+
+ error:
+    return -1;
+}
+
+
+static int default_getter(void *data, lua_State *L, int member,
+                          mrp_lua_value_t *v)
+{
+    userdata_t             *u = DATA_TO_USER(data);
+    mrp_lua_class_member_t *m;
+    mrp_lua_value_t        *vptr;
+
+    MRP_UNUSED(L);
+
+    mrp_lua_checkstack(L, -1);
+
+    m    = u->def->members + member;
+    vptr = data + m->offs;
+
+    switch (m->type) {
+    case MRP_LUA_STRING:
+        v->str = vptr->str;
+        goto ok;
+
+    case MRP_LUA_BOOLEAN:
+        v->bln = vptr->bln;
+        goto ok;
+
+    case MRP_LUA_INTEGER:
+        v->s32 = vptr->s32;
+        goto ok;
+
+    case MRP_LUA_DOUBLE:
+        v->dbl = vptr->dbl;
+        goto ok;
+
+    case MRP_LUA_FUNC:
+        v->lfn = vptr->lfn;
+        goto ok;
+
+    case MRP_LUA_LFUNC:
+        v->lfn = vptr->lfn;
+        goto ok;
+
+    case MRP_LUA_CFUNC:
+        v->lfn = vptr->lfn;
+        goto ok;
+
+    case MRP_LUA_BFUNC:
+        goto error;
+
+    case MRP_LUA_ANY:
+        v->any = vptr->any;
+        goto ok;
+
+    case MRP_LUA_STRING_ARRAY:
+    case MRP_LUA_BOOLEAN_ARRAY:
+    case MRP_LUA_INTEGER_ARRAY:
+    case MRP_LUA_DOUBLE_ARRAY:
+        v->array = vptr->array;
+        goto ok;
+
+    case MRP_LUA_OBJECT:
+        v->obj.ptr = *((void **)(data + m->offs));
+        v->obj.ref = *((int   *)(data + m->size));
+        goto ok;
+
+    default:
+        goto error;
+    }
+
+ ok:
+    return 1;
+
+ error:
+    return -1;
+}
+
+
+static int patch_overrides(mrp_lua_classdef_t *def)
+{
+    luaL_reg set = { NULL, NULL }, get = { NULL, NULL }, *r, *overrides;
+    int      i, n, extra, tostring;
+
+    tostring = 0;
+    for (n = 0, r = def->overrides; r->name != NULL; r++, n++) {
+        if (!strcmp(r->name, "__newindex")) {
+            if (set.name != NULL) {
+                mrp_log_error("Class with multiple SETFIELD overrides.");
+                exit(1);
+            }
+
+            if (set.func == override_setfield) {
+                mrp_log_error("SETFIELD already overridden to setfield!");
+                exit(1);
+            }
+
+            set = *r;
+            r->func = override_setfield;
+            continue;
+        }
+
+        if (!strcmp(r->name, "__index")) {
+            if (get.name != NULL) {
+                mrp_log_info("Class with multiple GETFIELD overrides.");
+                exit(1);
+            }
+
+            if (get.func == override_getfield) {
+                mrp_log_error("GETFIELD already overridden to getfield!");
+                exit(1);
+            }
+
+            get = *r;
+            r->func = override_getfield;
+            continue;
+        }
+
+        if (!strcmp(r->name, "__tostring")) {
+            tostring = 1;
+            continue;
+        }
+    }
+
+    if (set.func && get.func && tostring) {
+        def->setfield = set.func;
+        def->getfield = get.func;
+
+        return 0;
+    }
+
+    extra = (set.func ? 0 : 1) + (get.func ? 0 : 1) + (tostring ? 0 : 1);
+
+    /* XXX TODO: currently this is leaked if/when a classdef is destroyed */
+    if ((overrides = mrp_allocz_array(typeof(*overrides), n+1 + extra)) == NULL)
+        return -1;
+
+    for (i = 0, r = def->overrides; r->name != NULL; i++, r++) {
+        overrides[i].name = r->name;
+        overrides[i].func = r->func;
+    }
+
+    if (set.func == NULL) {
+        mrp_debug("overriding __newindex for class %s", def->class_name);
+        overrides[i].name = "__newindex";
+        overrides[i].func = override_setfield;
+        i++;
+    }
+    else
+        def->setfield = set.func;
+
+    if (get.func == NULL) {
+        mrp_debug("overriding __index for class %s", def->class_name);
+        overrides[i].name = "__index";
+        overrides[i].func = override_getfield;
+        i++;
+    }
+    else
+        def->getfield = get.func;
+
+    if (!tostring) {
+        mrp_debug("overriding __tostring for class %s", def->class_name);
+        overrides[i].name = "__tostring";
+        overrides[i].func = override_tostring;
+        i++;
+    }
+
+    def->overrides = overrides;
+
+    return 0;
+}
+
+
+/** Declare automatically handled class members for the given class. */
+int mrp_lua_declare_members(mrp_lua_classdef_t *def, mrp_lua_class_flag_t flags,
+                            mrp_lua_class_member_t *members, int nmember,
+                            char **natives, int nnative,
+                            mrp_lua_class_notify_t notify)
+{
+#define F(flag)          MRP_LUA_CLASS_##flag
+#define INHERITED_FLAGS (F(READONLY)|F(RAWGETTER)|F(RAWSETTER))
+
+    mrp_lua_class_member_t *m;
+    int                     i;
+
+    def->flags = flags;
+
+    if (members == NULL || nmember <= 0) {
+        if (def->flags & MRP_LUA_CLASS_EXTENSIBLE)
+            goto update_overrides;
+        else
+            return 0;
+    }
+
+    def->members = mrp_allocz_array(typeof(*def->members), nmember);
+
+    if (def->members == NULL)
+        return -1;
+
+    for (i = 0, m = def->members; i < nmember; i++, m++) {
+        if (members[i].flags & MRP_LUA_CLASS_NOTIFY) {
+            if (notify == NULL) {
+                mrp_log_error("member '%s' needs a non-NULL notifier",
+                              members[i].name);
+                goto fail;
+            }
+        }
+
+        if (MRP_LUA_BOOLEAN_ARRAY <= members[i].type &&
+            members[i].type <= MRP_LUA_DOUBLE_ARRAY) {
+            if (members[i].sizew != 8 && members[i].sizew != 4) {
+                mrp_log_error("array member '%s': size must be 32- or 64-bit",
+                              members[i].name);
+                goto fail;
+            }
+        }
+
+        if ((m->name = mrp_strdup(members[i].name)) == NULL)
+            goto fail;
+
+        *m = members[i];
+
+        if (m->setter == NULL)
+            m->setter = default_setter;
+        if (m->getter == NULL)
+            m->getter = default_getter;
+
+        m->flags |= (flags & INHERITED_FLAGS); /* inherit flags we can */
+
+        /* clear flags the default setter and getter don't do */
+        if (m->setter == default_setter)
+            m->flags &= ~MRP_LUA_CLASS_RAWSETTER;
+
+        if (m->getter == default_getter)
+            m->flags &= ~MRP_LUA_CLASS_RAWGETTER;
+
+        def->nmember++;
+    }
+
+    def->flags  = flags;
+    def->notify = notify;
+
+    if (natives == NULL || nnative == 0)
+        goto update_overrides;
+
+    def->natives = mrp_allocz_array(typeof(*def->natives), nnative);
+
+    if (def->natives == NULL)
+        goto fail;
+
+    for (i = 0; i < nnative; i++) {
+        if ((def->natives[i] = mrp_strdup(natives[i])) == NULL)
+            goto fail;
+
+        def->nnative++;
+    }
+
+ update_overrides:
+    if (!(def->flags & MRP_LUA_CLASS_NOOVERRIDE))
+        patch_overrides(def);
+
+    return 0;
+
+ fail:
+    for (i = 0, m = def->members; i < def->nmember; i++, m++)
+        mrp_free(m->name);
+    mrp_free(def->members);
+
+    def->members = NULL;
+    def->nmember = 0;
+
+    for (i = 0; i < def->nnative; i++)
+        mrp_free(def->natives[i]);
+    mrp_free(def->natives);
+
+    def->natives = NULL;
+    def->nnative = 0;
+
+    return -1;
+}
+
+
+static int object_setup_bridges(userdata_t *u, lua_State *L)
+{
+    mrp_lua_classdef_t     *def = u->def;
+    mrp_lua_class_bridge_t *b;
+    int                     i, class_usestack;
+
+    class_usestack = (def->flags & MRP_LUA_CLASS_USESTACK) ? true : false;
+    for (i = 0, b = def->bridges; i < def->nbridge; i++, b++) {
+        b->fb = mrp_funcbridge_create_cfunc(L, b->name, b->signature, b->fc,
+                                            USER_TO_DATA(u));
+
+        if (b->fb == NULL)
+            return -1;
+
+        b->fb->autobridge = true;
+        b->fb->usestack   = (b->flags & MRP_LUA_CLASS_USESTACK) ? true : false;
+        b->fb->usestack  |= class_usestack;
+    }
+
+    return 0;
+}
+
+
+static int class_member(userdata_t *u, lua_State *L, int index)
+{
+    mrp_lua_class_member_t *members = u->def->members;
+    int                     nmember = u->def->nmember;
+    mrp_lua_class_member_t *m;
+    int                     i;
+    const char              *name;
+
+    if (lua_type(L, index) != LUA_TSTRING)
+        return -1;
+
+    name = lua_tostring(L, index);
+
+    /*
+     * XXX TODO, check how to speed this up. For instance if Lua
+     * strings or references to string happened to be always
+     * represented by the same value as long as they are interned
+     * (ie. not collected) we could simply check for numeric
+     * equality of the stack item or a reference to thereof to one
+     * store in the object classdef...
+     *
+     * Alternatively if all else fails, at least pass in the length
+     * here and store it alongside all native names, to speed up
+     * negtive tests.
+     */
+
+    for (i = 0, m = members; i < nmember; i++, m++)
+        if (!strcmp(m->name, name))
+            return i;
+
+    return -1;
+}
+
+
+static int class_bridge(userdata_t *u, lua_State *L, int index)
+{
+    mrp_lua_class_bridge_t *bridges, *b;
+    int                     nbridge;
+    const char             *name;
+    int                     bidx;
+
+    if ((bridges = u->def->bridges) == NULL || (nbridge = u->def->nbridge) == 0)
+        return -1;
+
+    if (lua_type(L, index) != LUA_TSTRING)
+        return -1;
+
+    name = lua_tostring(L, index);
+
+    /*
+     * XXX TODO, ditto as for class_member()
+     */
+
+    for (bidx = 0, b = bridges; bidx < nbridge; bidx++, b++)
+        if (!strcmp(b->name, name))
+            return bidx;
+
+    return -1;
+}
+
+
+static int seterr(lua_State *L, char *e, size_t size, const char *format, ...)
+{
+    va_list ap;
+    char    msg[256];
+
+    va_start(ap, format);
+    vsnprintf(e ? e : msg, e ? size : sizeof(msg), format, ap);
+    va_end(ap);
+
+    if (!e && L) {
+        lua_pushstring(L, msg);
+        lua_error(L);
+    }
+
+    return -1;
+}
+
+
+static void object_create_reftbl(userdata_t *u, lua_State *L)
+{
+    lua_newtable(L);
+    u->refs.priv = luaL_ref(L, LUA_REGISTRYINDEX);
+}
+
+
+static void object_delete_reftbl(userdata_t *u, lua_State *L)
+{
+    luaL_unref(L, LUA_REGISTRYINDEX, u->refs.priv);
+    u->refs.priv = LUA_NOREF;
+}
+
+
+/** Refcount the object @idx for or within the given object. */
+int mrp_lua_object_ref_value(void *data, lua_State *L, int idx)
+{
+    userdata_t *u = userdata_get(data, CHECK);
+    int ref;
+
+    if (u->refs.priv != LUA_NOREF) {
+        lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
+        lua_pushvalue(L, idx > 0 ? idx : idx - 1);
+        ref = luaL_ref(L, -2);
+        lua_pop(L, 1);
+    }
+    else
+        ref = LUA_NOREF;
+
+    return ref;
+}
+
+/** Release the given reference for/from within the given object. */
+void mrp_lua_object_unref_value(void *data, lua_State *L, int ref)
+{
+    userdata_t *u = userdata_get(data, CHECK);
+
+    if (ref != LUA_NOREF && ref != LUA_REFNIL) {
+        if (u->refs.priv != LUA_NOREF) {
+            lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
+            luaL_unref(L, -1, ref);
+            lua_pop(L, 1);
+        }
+    }
+}
+
+/** Obtain and push the object for the given reference on the stack. */
+int mrp_lua_object_deref_value(void *data, lua_State *L, int ref, int pushnil)
+{
+    userdata_t *u = userdata_get(data, CHECK);
+
+    if (ref == LUA_REFNIL) {
+    nilref:
+        lua_pushnil(L);
+        return 1;
+    }
+
+    if (ref == LUA_NOREF) {
+        if (pushnil)
+            goto nilref;
+        else
+            return 0;
+    }
+
+    if (u->refs.priv == LUA_NOREF) {
+        if (pushnil)
+            goto nilref;
+        else
+            return 0;
+    }
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.priv);
+    lua_rawgeti(L, -1, ref);
+    lua_remove(L, -2);
+
+    return 1;
+}
+
+/** Obtain a new reference based on the reference owned by owner. */
+int mrp_lua_object_getref(void *owner, void *data, lua_State *L, int ref)
+{
+    userdata_t *uo = userdata_get(owner, CHECK);
+    userdata_t *ud = userdata_get(data , CHECK);
+
+    if (ref == LUA_NOREF || ref == LUA_REFNIL)
+        return ref;
+
+    if (uo->refs.priv == LUA_NOREF || ud->refs.priv == LUA_NOREF)
+        return LUA_NOREF;
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, uo->refs.priv);
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ud->refs.priv);
+
+    lua_rawgeti(L, -2, ref);
+    ref = luaL_ref(L, -2);
+
+    lua_pop(L, 2);
+
+    return ref;
+}
+
+
+static void object_create_exttbl(userdata_t *u, lua_State *L)
+{
+    lua_newtable(L);
+    u->refs.ext = luaL_ref(L, LUA_REGISTRYINDEX);
+}
+
+
+static void object_delete_exttbl(userdata_t *u, lua_State *L)
+{
+    int extidx;
+
+    if (u->refs.ext == LUA_NOREF)
+        return;
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
+    extidx = lua_gettop(L);
+
+    /*
+     * Notes:
+     *     I'm not sure whether explicitly unreffing all references
+     *     is necessary... In principle this should not be necessary.
+     *     We should have the only reference to our exttbl and we are
+     *     about to remove that making exttb garbage-collectable.
+     */
+
+    lua_pushnil(L);
+    while (lua_next(L, extidx) != 0) {
+        lua_pop(L, 1);
+        lua_pushvalue(L, -1);
+        lua_pushnil(L);
+
+        mrp_debug("freeing extended member [%s] of %s", lua_tostring(L, -2),
+                  __object(u, "*"));
+
+        lua_rawset(L, extidx);
+    }
+
+    luaL_unref(L, LUA_REGISTRYINDEX, u->refs.ext);
+    u->refs.ext = LUA_NOREF;
+
+    lua_settop(L, extidx);
+    lua_pop(L, 1);
+}
+
+
+static int object_setext(void *data, lua_State *L, const char *name,
+                         int vidx, char *err, size_t esize)
+{
+    userdata_t *u = DATA_TO_USER(data);
+
+    if (u->refs.ext == LUA_NOREF) {
+        if (err)
+            return seterr(L, err, esize, "trying to set user-defined field %s "
+                          "for non-extensible object %s", name,
+                          u->def->class_name);
+        else
+            return luaL_error(L, "trying to set user-defined field %s "
+                              "for non-extensible object %s", name,
+                              u->def->class_name);
+    }
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
+    lua_pushvalue(L, vidx > 0 ? vidx : vidx - 1);
+    lua_setfield(L, -2, name);
+    lua_pop(L, 1);
+
+    return 1;
+}
+
+
+static int object_getext(void *data, lua_State *L, const char *name)
+{
+    userdata_t *u = DATA_TO_USER(data);
+
+    if (u->refs.ext == LUA_NOREF) {
+        lua_pushnil(L);
+        return 1;
+    }
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
+    lua_getfield(L, -1, name);
+    lua_remove(L, -2);
+
+    return 1;
+}
+
+
+static int object_setiext(void *data, lua_State *L, int idx, int val)
+{
+    userdata_t *u = DATA_TO_USER(data);
+
+    if (u->refs.ext == LUA_NOREF) {
+        return luaL_error(L, "trying to set user-defined index %d "
+                          "for non-extensible object %s", idx,
+                          u->def->class_name);
+    }
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
+    lua_pushvalue(L, val > 0 ? val : val - 1);
+    lua_rawseti(L, -2, idx);
+    lua_pop(L, 1);
+
+    return 1;
+}
+
+
+static int object_getiext(void *data, lua_State *L, int idx)
+{
+    userdata_t *u = DATA_TO_USER(data);
+
+    if (u->refs.ext == LUA_NOREF) {
+        lua_pushnil(L);
+        return 1;
+    }
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, u->refs.ext);
+    lua_rawgeti(L, -1, idx);
+    lua_remove(L, -2);
+
+    return 1;
+}
+
+
+static inline int array_lua_type(int type)
+{
+    switch (type) {
+    case MRP_LUA_STRING_ARRAY:  return LUA_TSTRING;
+    case MRP_LUA_BOOLEAN_ARRAY: return LUA_TBOOLEAN;
+    case MRP_LUA_INTEGER_ARRAY: return LUA_TNUMBER;
+    case MRP_LUA_DOUBLE_ARRAY:  return LUA_TNUMBER;
+    default:                    return LUA_TNONE;
+    }
+}
+
+
+static inline int array_murphy_type(int type)
+{
+    switch (type) {
+    case LUA_TSTRING:  return MRP_LUA_STRING_ARRAY;
+    case LUA_TBOOLEAN: return MRP_LUA_BOOLEAN_ARRAY;
+    case LUA_TNUMBER:  return MRP_LUA_INTEGER_ARRAY;
+    default:           return MRP_LUA_NONE;
+    }
+}
+
+
+static inline int array_item_size(int type)
+{
+    switch (type) {
+    case MRP_LUA_STRING_ARRAY:  return sizeof(char *);
+    case MRP_LUA_BOOLEAN_ARRAY: return sizeof(bool);
+    case MRP_LUA_INTEGER_ARRAY: return sizeof(int32_t);
+    case MRP_LUA_DOUBLE_ARRAY:  return sizeof(double);
+    default:                    return 0;
+    }
+}
+
+
+static inline const char *array_type_name(int type)
+{
+    switch (type) {
+    case MRP_LUA_STRING_ARRAY:  return "string";
+    case MRP_LUA_BOOLEAN_ARRAY: return "boolean";
+    case MRP_LUA_INTEGER_ARRAY: return "integer";
+    case MRP_LUA_DOUBLE_ARRAY:  return "double";
+    case MRP_LUA_ANY:           return "any";
+    default:                    return "<invalid array type>";
+    }
+}
+
+
+/** Collect, optionally dupping, all items from an assumed array @tidx. */
+int mrp_lua_object_collect_array(lua_State *L, int tidx, void **itemsp,
+                                 size_t *nitemp, int *expectedp, int dup,
+                                 char *e, size_t esize)
+{
+    const char *name, *str;
+    int         ktype, vtype, ltype, i, expected, popnil;
+    size_t      max, idx, isize;
+    void       *items;
+
+    max   = *nitemp;
+    tidx  = mrp_lua_absidx(L, tidx);
+    items = *itemsp;
+    ltype = LUA_TNONE;
+    isize = 0;
+
+    expected = *expectedp;
+    popnil   = false;
+
+    if (expected != MRP_LUA_ANY) {
+        ltype = array_lua_type(expected);
+        isize = array_item_size(expected);
+
+        if (ltype == LUA_TNONE || !isize)
+            goto type_error;
+    }
+
+    lua_pushnil(L);
+    popnil = true;
+    MRP_LUA_FOREACH_ALL(L, i, tidx, ktype, name, idx) {
+        vtype = lua_type(L, -1);
+
+        mrp_debug("collecting <%s>:<%s> element for %s array",
+                  lua_typename(L, ktype), lua_typename(L, vtype),
+                  array_type_name(expected));
+
+        if (ktype != LUA_TNUMBER)
+            goto not_pure;
+
+        if (expected == MRP_LUA_ANY) {
+            expected = array_murphy_type(vtype);
+
+            if (!expected)
+                goto type_error;
+
+            if (expected == MRP_LUA_INTEGER_ARRAY)
+                expected = MRP_LUA_DOUBLE_ARRAY;        /* safer for ANY */
+
+            ltype = array_lua_type(expected);
+            isize = array_item_size(expected);
+        }
+        else
+            /* bail out for type mismatch (null is a valid string array) */
+            if (vtype != ltype &&
+                !(expected == MRP_LUA_STRING_ARRAY && vtype == LUA_TNIL))
+                goto type_error;
+
+        if (max != (size_t)-1 && i >= (int)max)
+            goto overflow;
+
+        if (dup && mrp_realloc(items, (i + 1) * isize) == NULL)
+            goto nomem;
+
+        switch (expected) {
+        case MRP_LUA_STRING_ARRAY:
+            str = (vtype != LUA_TNIL ? lua_tostring(L, -1) : NULL);
+            if (dup) {
+                ((char **)items)[i] = str ? mrp_strdup(str) : NULL;
+                if (!((char **)items)[i] && str)
+                    goto nomem;
+            }
+            else
+                ((char **)items)[i] = (char *)str;
+            break;
+        case MRP_LUA_BOOLEAN_ARRAY:
+            ((bool *)items)[i] = lua_toboolean(L, -1);
+            break;
+        case MRP_LUA_INTEGER_ARRAY:
+            ((int32_t *)items)[i] = lua_tointeger(L, -1);
+            break;
+        case MRP_LUA_DOUBLE_ARRAY:
+            ((double *)items)[i] = lua_tonumber(L, -1);
+            break;
+        default:
+            goto type_error;
+        }
+    }
+    lua_pop(L, 1);
+
+    *itemsp    = items;
+    *nitemp    = i;
+    *expectedp = expected;
+
+    return 0;
+
+
+#define CLEANUP() do {                                                  \
+        mrp_lua_object_free_array(itemsp, nitemp, expected);            \
+        if (popnil) lua_pop(L, 1);                                      \
+    } while (0)
+
+ type_error:
+    CLEANUP(); return seterr(L, e, esize, "array or element of wrong type");
+ not_pure:
+    CLEANUP(); return seterr(L, e, esize, "not a pure array");
+ nomem:
+    CLEANUP(); return seterr(L, e, esize, "could not allocate array");
+ overflow:
+    CLEANUP(); return seterr(L, e, esize, "array too large");
+#undef CLEANUP
+}
+
+/** Free an array collected and duplicated by the collector above. */
+void mrp_lua_object_free_array(void **itemsp, size_t *nitemp, int type)
+{
+    size_t   nitem = *nitemp;
+    char   **saptr;
+    size_t   i;
+
+    switch (type) {
+    case MRP_LUA_STRING_ARRAY:
+        saptr = *itemsp;
+        for (i = 0; i < nitem; i++)
+            mrp_free(saptr[i]);
+    case MRP_LUA_BOOLEAN_ARRAY:
+    case MRP_LUA_INTEGER_ARRAY:
+    case MRP_LUA_DOUBLE_ARRAY:
+        mrp_free(*itemsp);
+        *itemsp = NULL;
+        *nitemp = 0;
+        break;
+    default:
+        break;
+    }
+}
+
+/** Push the given array of simple native C type on the stack. */
+int mrp_lua_object_push_array(lua_State *L, int type, void *items, size_t nitem)
+{
+    int i;
+
+    lua_createtable(L, nitem, 0);
+
+    for (i = 0; i < (int)nitem; i++) {
+        switch (type) {
+        case MRP_LUA_STRING_ARRAY:
+            lua_pushstring(L, ((char **)items)[i]);
+            break;
+        case MRP_LUA_BOOLEAN_ARRAY:
+            lua_pushboolean(L, ((bool *)items)[i]);
+            break;
+        case MRP_LUA_INTEGER_ARRAY:
+            lua_pushinteger(L, ((int32_t *)items)[i]);
+            break;
+        case MRP_LUA_DOUBLE_ARRAY:
+            lua_pushnumber(L, ((double *)items)[i]);
+            break;
+        default:
+            lua_pop(L, 1);
+            return -1;
+        }
+
+        lua_rawseti(L, -2, i + 1);
+    }
+
+    return 1;
+}
+
+/** Perform a setfield-like member assignment on the given object. */
+int mrp_lua_set_member(void *data, lua_State *L, char *err, size_t esize)
+{
+    userdata_t             *u = DATA_TO_USER(data);
+    int                     midx = class_member(u, L, -2);
+    mrp_lua_class_member_t *m;
+    mrp_lua_value_t         v;
+    int                     vtype, etype;
+    void                   *items;
+    size_t                  nitem;
+
+    if (midx < 0)
+        goto notfound;
+
+    mrp_lua_checkstack(L, -1);
+
+    m     = u->def->members + midx;
+    vtype = lua_type(L, -1);
+
+    mrp_debug("setting %s.%s of Lua object %p(%p)", u->def->class_name,
+              m->name, u, data);
+
+    if (!u->initializing && (m->flags & MRP_LUA_CLASS_READONLY))
+        return seterr(L, err, esize, "%s.%s of Lua object is readonly",
+                      u->def->class_name, m->name);
+
+    if (u->initializing && (m->flags & MRP_LUA_CLASS_NOINIT))
+        goto ok_noinit;
+
+    if (m->flags & MRP_LUA_CLASS_RAWSETTER) {
+        if (m->setter(data, L, midx, NULL) == 1)
+            goto ok;
+        else
+            goto error;
+    }
+
+    switch (m->type) {
+    case MRP_LUA_STRING:
+        if (vtype != LUA_TSTRING && vtype != LUA_TNIL)
+            return seterr(L, err, esize, "%s.%s expects string or nil, got %s",
+                          u->def->class_name, m->name,
+                          lua_typename(L, vtype), m->name);
+
+        v.str = lua_tostring(L, -1);
+
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+
+    case MRP_LUA_BOOLEAN:
+        v.bln = lua_toboolean(L, -1);
+
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+
+    case MRP_LUA_INTEGER:
+        if (vtype != LUA_TNUMBER)
+            return seterr(L, err, esize, "%s.%s expects number, got %s",
+                          u->def->class_name, m->name, lua_typename(L, vtype));
+
+        v.s32 = lua_tointeger(L, -1);
+
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+
+    case MRP_LUA_DOUBLE:
+        if (vtype != LUA_TNUMBER)
+            return seterr(L, err, esize, "%s.%s expects number, got %s",
+                          u->def->class_name, m->name, lua_typename(L, vtype));
+
+        v.dbl = lua_tonumber(L, -1);
+
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+
+    case MRP_LUA_CFUNC:
+        if (vtype != LUA_TFUNCTION && vtype != LUA_TNIL)
+            return seterr(L, err, esize, "%s.%s expects function, got %s",
+                          u->def->class_name, m->name, lua_typename(L, vtype));
+        if (!lua_iscfunction(L, -1))
+            return seterr(L, err, esize, "%s.%s expects Lua C-function.",
+                          u->def->class_name, m->name);
+        goto setfn;
+
+    case MRP_LUA_LFUNC:
+        if (vtype != LUA_TFUNCTION && vtype != LUA_TNIL)
+            return seterr(L, err, esize, "%s.%s expects function, got %s",
+                          u->def->class_name, m->name, lua_typename(L, vtype));
+        if (lua_iscfunction(L, -1))
+            return seterr(L, err, esize, "%s.%s expects pure Lua function.",
+                          u->def->class_name, m->name);
+        goto setfn;
+
+    case MRP_LUA_FUNC:
+        if (vtype != LUA_TFUNCTION && vtype != LUA_TNIL)
+            return seterr(L, err, esize, "%s.%s expects function, got %s",
+                          u->def->class_name, m->name, lua_typename(L, vtype));
+
+    setfn:
+        v.lfn = mrp_lua_object_ref_value(data, L, -1);
+
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+
+    case MRP_LUA_BFUNC:
+        seterr(L, err, esize, "BFUNC is not implemented");
+        goto error;
+
+    case MRP_LUA_NULL:
+        seterr(L, err, esize, "setting member of invalid type NULL");
+        goto error;
+
+    case MRP_LUA_NONE:
+        seterr(L, err, esize, "setting member of invalid type NONE");
+        goto error;
+
+    case MRP_LUA_ANY:
+        v.any = mrp_lua_object_ref_value(data, L, -1);
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+
+    case MRP_LUA_STRING_ARRAY:
+    case MRP_LUA_BOOLEAN_ARRAY:
+    case MRP_LUA_INTEGER_ARRAY:
+    case MRP_LUA_DOUBLE_ARRAY:
+        items = NULL;
+        nitem = (size_t)-1;
+        etype = m->type;
+        if (mrp_lua_object_collect_array(L, -1, &items, &nitem, &etype, true,
+                                         err, esize) < 0)
+            return -1;
+        else {
+            v.array.items   = data + m->offs;
+            v.array.nitem64 = data + m->size;
+
+            *v.array.items = items;
+            if (m->sizew == 8)
+                *v.array.nitem64 = nitem;
+            else
+                *v.array.nitem32 = (uint32_t)nitem;
+        }
+        goto ok;
+
+    case MRP_LUA_OBJECT:
+        if (m->type_id == MRP_LUA_NONE)
+            m->type_id = class_by_type_name(m->type_name)->type_id;
+
+        if (m->type_id == MRP_LUA_NONE) {
+            seterr(L, err, esize, "can't set member of unknown type %s",
+                   m->type_name);
+            goto error;
+        }
+
+        if (!mrp_lua_object_of_type(L, -1, m->type_id)) {
+            seterr(L, err, esize, "object type mismatch, expecting '%s'",
+                   class_by_type(m->type_id)->type_name);
+            goto error;
+        }
+
+        v.obj.ref = mrp_lua_object_ref_value(data, L, -1);
+
+        lua_pushliteral(L, "userdata");
+        lua_rawget(L, -2);
+        if ((v.obj.ptr = lua_touserdata(L, -1)) != NULL) {
+            v.obj.ptr = *(void **)v.obj.ptr;
+            v.obj.ptr = USER_TO_DATA(v.obj.ptr);
+        }
+        lua_pop(L, 1);
+
+        if (m->setter(data, L, midx, &v) == 1)
+            goto ok;
+        else
+            goto error;
+        break;
+
+    default:
+        seterr(L, err, esize, "type %d not implemented");
+        break;
+    }
+
+ ok:
+    if ((m->flags & MRP_LUA_CLASS_NOTIFY) && u->def->notify)
+        u->def->notify(data, L, midx);
+ ok_noinit:
+    return 1;
+
+ notfound:
+    return 0;
+
+ error:
+    return -1;
+}
+
+/** Perform a getfield-like member-lookup on the given object. */
+int mrp_lua_get_member(void *data, lua_State *L, char *err, size_t esize)
+{
+    userdata_t             *u = DATA_TO_USER(data);
+    mrp_lua_class_member_t *m;
+    mrp_lua_class_bridge_t *b;
+    int                     midx, bidx;
+    mrp_lua_value_t         v;
+    void                  **items;
+    size_t                 *nitem;
+
+    mrp_lua_checkstack(L, -1);
+
+    if ((midx = class_member(u, L, -1)) >= 0)
+        m = u->def->members + midx;
+    else {
+        if ((bidx = class_bridge(u, L, -1)) >= 0) {
+            b = u->def->bridges + bidx;
+
+            return mrp_funcbridge_push(L, b->fb);
+        }
+
+        goto notfound;
+    }
+
+    if (m->getter(data, L, midx, &v) != 1)
+        goto error;
+
+    if (m->flags & MRP_LUA_CLASS_RAWGETTER)
+        goto ok;
+
+    switch (m->type) {
+    case MRP_LUA_STRING:
+        if (v.str != NULL)
+            lua_pushstring(L, v.str);
+        else
+            lua_pushnil(L);
+        goto ok;
+
+    case MRP_LUA_BOOLEAN:
+        lua_pushboolean(L, v.bln);
+        goto ok;
+
+    case MRP_LUA_INTEGER:
+        lua_pushinteger(L, v.s32);
+        goto ok;
+
+    case MRP_LUA_DOUBLE:
+        lua_pushnumber(L, v.dbl);
+        goto ok;
+
+    case MRP_LUA_FUNC:
+        mrp_lua_object_deref_value(data, L, v.lfn, true);
+        goto ok;
+
+    case MRP_LUA_LFUNC:
+        mrp_lua_object_deref_value(data, L, v.lfn, true);
+        goto ok;
+
+    case MRP_LUA_CFUNC:
+        mrp_lua_object_deref_value(data, L, v.lfn, true);
+        goto ok;
+
+    case MRP_LUA_BFUNC:
+        seterr(L, err, esize, "BFUNC is not implemented");
+        goto error;
+
+    case MRP_LUA_NULL:
+        lua_pushnil(L);
+        goto ok;
+
+    case MRP_LUA_NONE:
+        seterr(L, err, esize, "invalid type");
+        goto error;
+
+    case MRP_LUA_ANY:
+        mrp_lua_object_deref_value(data, L, v.any, true);
+        goto ok;
+
+    case MRP_LUA_STRING_ARRAY:
+    case MRP_LUA_BOOLEAN_ARRAY:
+    case MRP_LUA_INTEGER_ARRAY:
+    case MRP_LUA_DOUBLE_ARRAY:
+        items = data + m->offs;
+        nitem = data + m->size;
+        if (mrp_lua_object_push_array(L, m->type, *items, *nitem) > 0)
+            goto ok;
+        else {
+            seterr(L, err, esize, "failed to push array");
+            goto error;
+        }
+
+    case MRP_LUA_OBJECT:
+        mrp_lua_object_deref_value(data, L, v.obj.ref, true);
+        break;
+
+    default:
+        goto error;
+    }
+
+ ok:
+    return 1;
+
+ notfound:
+    return 0;
+
+ error:
+    return -1;
+}
+
+/** Perform a table-based member initialization for the given object. */
+int mrp_lua_init_members(void *data, lua_State *L, int idx,
+                         char *err, size_t esize)
+{
+    userdata_t *u = userdata_get(data, CHECK);
+    const char *n;
+    size_t      l;
+    int         top, set;
+
+    if (err != NULL)
+        *err = '\0';
+
+    if (idx < 0)
+        idx = lua_gettop(L) + idx + 1;
+
+    if (lua_type(L, idx) != LUA_TTABLE)
+        return 0;
+
+    if (u->def->flags & MRP_LUA_CLASS_NOINIT) {
+        mrp_log_warning("Explicit table-based member initializer called for");
+        mrp_log_warning("object %s marked for NOINIT.", u->def->class_name);
+    }
+
+    top = lua_gettop(L);
+    u->initializing = true;
+    MRP_LUA_FOREACH_FIELD(L, idx, n, l) {
+        mrp_debug("initializing %s.%s", u->def->class_name, n);
+
+        lua_pushvalue(L, -2);
+        lua_pushvalue(L, -2);
+
+        switch (mrp_lua_set_member(data, L, err, esize)) {
+        case -1:
+            goto error;
+        case 0:
+            /*
+             * Okay, this was not an ordinary predefined member, so
+             *     - pass this to setfield if we have one and this field is
+             *       declared native, or no native fields have been declared
+             *     - if there is no setfield or setfield said it does not
+             *       handle this field, pass this to setext if the class is
+             *       extensible
+             *
+             * Note that, in principle, we probably should treat it an error
+             * if setfield does not handle a field that has been declared
+             * native but currently we don't.
+             */
+
+            set = 0;
+
+            if (u->def->setfield && (!u->def->natives || is_native(u, n))) {
+                mrp_lua_push_object(L, data);
+                lua_insert(L, -3);
+                set = u->def->setfield(L);
+                lua_remove(L, -3);
+            }
+
+            if (set == 0 && (u->def->flags & MRP_LUA_CLASS_EXTENSIBLE))
+                set = object_setext(data, L, n, -1, NULL, 0);
+
+            if (set <= 0)
+                goto error;
+            break;
+        case 1:
+            break;
+        }
+    }
+    u->initializing = false;
+    lua_settop(L, top);
+    return 1;
+
+ error:
+    u->initializing = false;
+    lua_settop(L, top);
+    return -1;
+
+}
+
+
+static inline int is_native(userdata_t *u, const char *name)
+{
+    int i;
+
+    /*
+     * XXX TODO, ditto as for class_member() and class_bridge()
+     */
+
+    for (i = 0; i < u->def->nnative; i++)
+        if (u->def->natives[i][0] == name[0] &&
+            !strcmp(u->def->natives[i] + 1, name + 1))
+            return 1;
+
+    return 0;
+}
+
+
+static int override_setfield(lua_State *L)
+{
+    void       *data = mrp_lua_check_object(L, NULL, 1);
+    userdata_t *u    = DATA_TO_USER(data);
+    char        err[128] = "";
+    const char *name;
+    int         status;
+
+    if (data == NULL)
+        return luaL_error(L, "failed to find class userdata");
+
+    mrp_debug("setting field for object of type '%s'", u->def->class_name);
+
+    switch (mrp_lua_set_member(data, L, err, sizeof(err))) {
+    case 1:  /* ok */
+        status = 0;
+        goto out;
+    case 0:  /* field not found */
+        break;
+    default: /* error */
+        return luaL_error(L, "failed to set member (%s)", err);
+    }
+
+    switch (lua_type(L, 2)) {
+    case LUA_TSTRING:
+        name = lua_tostring(L, 2);
+        break;
+    case LUA_TNUMBER:
+        name = NULL;
+        break;
+    default:
+        return luaL_argerror(L, 2, "expecting string or integer");
+    }
+
+    luaL_checkany(L, 3);
+
+    /*
+     * Okay, this was not an ordinary predefined member, so
+     *     - if this is a field (named vs. indexed member)
+     *         * pass this to setfield if we have one and this field is
+     *           declared native, or no native fields have been declared
+     *         * if there is no setfield or setfield said it does not handle
+     *           this field, pass this to setext if the class is extensible
+     *     - otherwise (IOW an indexed member) pass this to setixet if the
+     *       class is extensible
+     *
+     * Note that, in principle, we probably should treat it an error if
+     * setfield does not handle a field that has been declared native but
+     * currently we don't.
+     */
+
+    status = 0;
+
+    if (name != NULL) {
+        if (u->def->setfield && (!u->def->natives || is_native(u, name)))
+            status = u->def->setfield(L);
+
+        if (status == 0 && u->def->flags & MRP_LUA_CLASS_EXTENSIBLE)
+            status = object_setext(data, L, name, 3, NULL, 0);
+    }
+    else
+        status = object_setiext(data, L, lua_tointeger(L, 2), 3);
+
+ out:
+    return status;
+}
+
+
+static int override_getfield(lua_State *L)
+{
+    void       *data = mrp_lua_check_object(L, NULL, 1);
+    userdata_t *u    = DATA_TO_USER(data);
+    char        err[128] = "";
+    const char *name;
+    int         status;
+
+    mrp_debug("getting field for object of type '%s'", u->def->class_name);
+
+    switch (mrp_lua_get_member(data, L, err, sizeof(err))) {
+    case 1:  /* ok */
+        status = 1;
+        goto out;
+    case 0:  /* field not found */
+        break;
+    default: /* error */
+        return luaL_error(L, "failed to set member (%s)", err);
+    }
+
+    switch (lua_type(L, 2)) {
+    case LUA_TSTRING:
+        name = lua_tostring(L, 2);
+        break;
+    case LUA_TNUMBER:
+        name = NULL;
+        break;
+    default:
+        return luaL_argerror(L, 2, "expecting string or integer");
+    }
+
+    /*
+     * Okay, this was not an ordinary predefined member, so
+     *     - if this is a field (named vs. indexed member)
+     *         * pass this to setfield if we have one and this field is
+     *           declared native, or no native fields have been declared
+     *         * if there is no setfield or setfield said it does not handle
+     *           this field, pass this to setext if the class is extensible
+     *     - otherwise (IOW an indexed member) pass this to setixet if the
+     *       class is extensible
+     *
+     * Note that, in principle, we probably should treat it an error if
+     * setfield does not handle a field that has been declared native but
+     * currently we don't.
+     */
+
+    status = 0;
+
+    if (name != NULL) {
+        if (u->def->getfield && (!u->def->natives || is_native(u, name)))
+            status = u->def->getfield(L);
+
+        if (status == 0 && u->def->flags & MRP_LUA_CLASS_EXTENSIBLE)
+            status = object_getext(data, L, name);
+    }
+    else
+        status = object_getiext(data, L, 2);
+
+ out:
+    return status;
+}
+
+
+static int override_tostring(lua_State *L)
+{
+    void       *data;
+    userdata_t *u;
+    char        buf[1024];
+
+    data = mrp_lua_check_object(L, NULL, 1);
+    u    = DATA_TO_USER(data);
+
+    if (u != NULL) {
+        if (u->def->tostring == NULL ||
+            u->def->tostring(MRP_LUA_TOSTR_LUA, buf, sizeof(buf),
+                             L, data) <= 0) {
+            snprintf(buf, sizeof(buf), "<%s)", __object(u, "*"));
+        }
+
+        lua_pushstring(L, buf);
+
+        return 1;
+    }
+    else {
+        snprintf(buf, sizeof(buf),
+                 "<tostring called for invalid Murphy Lua object %p>", data);
+        return luaL_error(L, buf);
+    }
+
+}
+
+
+static ssize_t userdata_minimal_tostr(char *buf, size_t size, userdata_t *u)
+{
+    return snprintf(buf, size, "<%d:%p>", u->def->type_id, u);
+}
+
+
+static ssize_t userdata_compact_tostr(char *buf, size_t size, userdata_t *u)
+{
+    return snprintf(buf, size, "<%c:%s(%p)>",
+                    (u->initializing ? 'I' : (u->dead ? 'D' : 'a')),
+                    u->def->type_name, u);
+}
+
+
+static ssize_t userdata_oneline_tostr(char *buf, size_t size, userdata_t *u)
+{
+    void *data = USER_TO_DATA(u);
+
+    return snprintf(buf, size, "<%c:%s(%p/%p)>",
+                    (u->initializing ? 'I' : (u->dead ? 'D' : 'a')),
+                    u->def->type_name, u, data);
+}
+
+
+static ssize_t userdata_short_tostr(char *buf, size_t size, userdata_t *u)
+{
+    return userdata_oneline_tostr(buf, size, u);
+}
+
+
+static ssize_t userdata_medium_tostr(char *buf, size_t size, userdata_t *u)
+{
+    return userdata_oneline_tostr(buf, size, u);
+}
+
+
+static ssize_t userdata_full_tostr(char *buf, size_t size, userdata_t *u)
+{
+    return userdata_oneline_tostr(buf, size, u);
+}
+
+
+static ssize_t userdata_verbose_tostr(char *buf, size_t size, userdata_t *u)
+{
+    return userdata_oneline_tostr(buf, size, u);
+}
+
+
+static ssize_t userdata_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
+                              userdata_t *u)
+{
+    switch (mode & MRP_LUA_TOSTR_MODEMASK) {
+    case MRP_LUA_TOSTR_MINIMAL: return userdata_minimal_tostr(buf, size, u);
+    default:
+    case MRP_LUA_TOSTR_COMPACT: return userdata_compact_tostr(buf, size, u);
+    case MRP_LUA_TOSTR_ONELINE: return userdata_oneline_tostr(buf, size, u);
+    case MRP_LUA_TOSTR_SHORT:   return userdata_short_tostr  (buf, size, u);
+    case MRP_LUA_TOSTR_MEDIUM:  return userdata_medium_tostr (buf, size, u);
+    case MRP_LUA_TOSTR_FULL:    return userdata_full_tostr   (buf, size, u);
+    case MRP_LUA_TOSTR_VERBOSE: return userdata_verbose_tostr(buf, size, u);
+    }
+
+    return 0;
+}
+
+
+/** Dump the given object to the provided buffer. */
+ssize_t mrp_lua_object_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
+                             lua_State *L, void *data)
+{
+    userdata_t *u = userdata_get(data, CHECK);
+    char       *p = buf;
+    ssize_t     n = 0;;
+
+    if (u == NULL)
+        return snprintf(p, size, "<non-object %p>", u);
+
+    if (mode & MRP_LUA_TOSTR_META) {
+        n = userdata_tostr(mode, p, size, u);
+
+        if (n <= 0)
+            goto error;
+        if ((size_t)n >= size)
+            goto overflow;
+    }
+
+    if (mode & MRP_LUA_TOSTR_DATA) {
+        p    += (size_t)n;
+        size -= (size_t)n;
+
+        if (u->def->tostring != NULL) {
+            n = u->def->tostring(mode, p, size, L, data);
+
+            if (n < 0)
+                goto error;
+            if ((size_t)n >= size)
+                goto overflow;
+        }
+    }
+
+    return (ssize_t)(p + n - buf);
+
+ overflow:
+    return (ssize_t)(p + n - buf);
+
+ error:
+    return n;
+}
+
+
+/** Dump the object at the given stack location to the provided buffer. */
+ssize_t mrp_lua_index_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
+                            lua_State *L, int index)
+{
+    userdata_t **userdatap;
+
+    userdatap = (userdata_t **)lua_touserdata(L, index);
+
+    if (userdatap != NULL)
+        return mrp_lua_object_tostr(mode, buf, size, L, *userdatap);
+    else
+        return snprintf(buf, size, "<invalid object, no userdata>");;
+}
+
+
+/** Dump all live or zombie Lua objects. */
+void mrp_lua_dump_objects(mrp_lua_tostr_mode_t mode, lua_State *L, FILE *fp)
+{
+    mrp_lua_classdef_t *def;
+    mrp_list_hook_t    *p, *n;
+    userdata_t         *u;
+    void               *d;
+    int                 i, cnt;
+    char                active[64], dead[64], created[64], destroyed[64];
+    char                obj[4096];
+
+    fprintf(fp, "Lua memory usage: %d k, %.2f M\n",
+            lua_gc(L, LUA_GCCOUNT, 0), lua_gc(L, LUA_GCCOUNT, 0) / 1024.0);
+
+    fprintf(fp, "Objects by class/type: A=active, D=dead, "
+            "c=created, d=destroyed\n");
+    for (i = 0; i < nclassdef; i++) {
+        def = classdefs[i];
+
+        snprintf(active, sizeof(active), "%u", def->nactive);
+        snprintf(dead, sizeof(dead), "%u", def->ndead);
+        snprintf(created, sizeof(created), "%u", def->ncreated);
+        snprintf(destroyed, sizeof(destroyed), "%u", def->ndestroyed);
+
+        fprintf(fp, "<%s/%s>: A:%s, D:%s, c:%s, d:%s\n",
+                def->class_name, def->type_name,
+                active, dead, created, destroyed);
+
+        cnt = 0;
+        mrp_list_foreach(&def->objects, p, n) {
+            u = mrp_list_entry(p, typeof(*u), hook[0]);
+            d = USER_TO_DATA(u);
+
+            if (mrp_lua_object_tostr(mode, obj, sizeof(obj), L, d) > 0)
+                fprintf(fp, "    #%d: %s\n", cnt, obj);
+            else
+                fprintf(fp, "    failed to dump object %p(%p)\n", u, d);
+            cnt++;
+        }
+    }
+}
+
+
+void mrp_lua_track_objects(bool enable)
+{
+    if (cfg.busy) {
+        if (cfg.track != enable)
+            mrp_log_warning("Can't %s Murphy Lua object tracking, "
+                            "already in use.", enable ? "enable" : "disable");
+        return;
+    }
+
+    if (!cfg.set) {
+        cfg.track = enable;
+        cfg.set   = true;
+
+        mrp_log_info("Murphy Lua object tracking is now %s.",
+                     enable ? "enabled" : "disabled");
+    }
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-utils/object.h b/src/core/lua-utils/object.h
new file mode 100644 (file)
index 0000000..3838e1b
--- /dev/null
@@ -0,0 +1,770 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_OBJECT_H__
+#define __MURPHY_LUA_OBJECT_H__
+
+#include <stdbool.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "murphy/common/list.h"
+#include "murphy/core/lua-utils/lua-utils.h"
+
+#define MRP_LUA_CONFIG_ENVVAR "__MURPHY_LUA_CONFIG"
+
+#define MRP_LUA_ENTER                           \
+    mrp_debug("enter")
+
+#define MRP_LUA_LEAVE(_v)                       \
+    do {                                        \
+        mrp_debug("leave (%d)", (_v));          \
+        return (_v);                            \
+    } while (0)
+
+#define MRP_LUA_LEAVE_NOARG                     \
+    mrp_debug("leave")
+
+#define MRP_LUA_LEAVE_ERROR(L, fmt, args...)    \
+    luaL_error(L, fmt , ## args)
+
+#define MRP_LUA_CLASSID_ROOT              "LuaBook."
+
+#define MRP_LUA_CLASS(_name, _constr)     & _name ## _ ## _constr ## _class_def
+#define MRP_LUA_CLASS_SIMPLE(_name)       & _name ## _class_def
+
+#define MRP_LUA_METHOD_LIST(...)          { __VA_ARGS__  {NULL, NULL}}
+
+#define MRP_LUA_METHOD(_name, _func)      { # _name, _func } ,
+#define MRP_LUA_METHOD_CONSTRUCTOR(_func) { "new", _func } ,
+
+#define MRP_LUA_OVERRIDE_GETFIELD(_func)  { "__index", _func } ,
+#define MRP_LUA_OVERRIDE_SETFIELD(_func)  { "__newindex", _func } ,
+#define MRP_LUA_OVERRIDE_CALL(_func)      { "__call", _func } ,
+#define MRP_LUA_OVERRIDE_STRINGIFY(_func) { "__tostring", _func } ,
+#define MRP_LUA_OVERRIDE_GETLENGTH(_func) { "__len", _func } ,
+
+#define MRP_LUA_METHOD_LIST_TABLE(_name, ... ) \
+    static luaL_reg _name[] = {                \
+        __VA_ARGS__                            \
+        { NULL, NULL }                         \
+    }
+
+#define MRP_LUA_CLASS_NAME(_name)        #_name
+#define MRP_LUA_CLASS_ID(_name, _constr) MRP_LUA_CLASSID_ROOT#_name"_"#_constr
+#define MRP_LUA_UDATA_ID(_name, _constr)                \
+    MRP_LUA_CLASSID_ROOT#_name"."#_constr".userdata"
+
+#define MRP_LUA_TYPE_ID(_class_def)   ((_class_def)->type_id)
+#define MRP_LUA_TYPE_NAME(_class_def) ((_class_def)->type_name)
+
+#define MRP_LUA_CLASS_DEF(_name, _constr, _type, _destr, _methods, _overrides)\
+    static mrp_lua_classdef_t _name ## _ ## _constr ## _class_def = {         \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                           \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),                    \
+        .constructor   = # _name "." # _constr,                               \
+        .destructor    = _destr,                                              \
+        .type_name     = #_type,                                              \
+        .type_id       = -1,                                                  \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),                    \
+        .userdata_size = sizeof(_type),                                       \
+        .methods       = _methods,                                            \
+        .overrides     = _overrides,                                          \
+        .members       = NULL,                                                \
+        .nmember       = 0,                                                   \
+        .natives       = NULL,                                                \
+        .nnative       = 0,                                                   \
+        .notify        = NULL,                                                \
+        .flags         = 0,                                                   \
+    }
+
+#define MRP_LUA_CLASS_DEF_SIMPLE(_name, _type, _destr, _methods, _overrides) \
+    static luaL_reg _name ## _class_methods[]   = _methods;             \
+    static luaL_reg _name ## _class_overrides[] = _overrides;           \
+                                                                        \
+    static mrp_lua_classdef_t _name ## _class_def = {                   \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                     \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),              \
+        .constructor   = # _name,                                       \
+        .destructor    = _destr,                                        \
+        .type_name     = #_type,                                        \
+        .type_id       = -1,                                            \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),              \
+        .userdata_size = sizeof(_type),                                 \
+        .methods       = _name ## _class_methods,                       \
+        .overrides     = _name ## _class_overrides,                     \
+        .members       = NULL,                                          \
+        .nmember       = 0,                                             \
+        .natives       = NULL,                                          \
+        .nnative       = 0,                                             \
+        .notify        = NULL,                                          \
+        .flags         = 0,                                             \
+    }
+
+#define MRP_LUA_CLASS_DEF_FLAGS(_name, _constr, _type, _destr, _methods,      \
+                                _overrides, _class_flags)                     \
+    static mrp_lua_classdef_t _name ## _ ## _constr ## _class_def = {         \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                           \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),                    \
+        .constructor   = # _name "." # _constr,                               \
+        .destructor    = _destr,                                              \
+        .type_name     = #_type,                                              \
+        .type_id       = -1,                                                  \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),                    \
+        .userdata_size = sizeof(_type),                                       \
+        .methods       = _methods,                                            \
+        .overrides     = _overrides,                                          \
+        .members       = NULL,                                                \
+        .nmember       = 0,                                                   \
+        .natives       = NULL,                                                \
+        .nnative       = 0,                                                   \
+        .notify        = NULL,                                                \
+        .flags         = _class_flags,                                        \
+    }
+
+
+#define MRP_LUA_CLASS_DEF_SIMPLE_FLAGS(_name, _type, _destr, _methods,  \
+                                       _overrides, _class_flags)        \
+    static luaL_reg _name ## _class_methods[]   = _methods;             \
+    static luaL_reg _name ## _class_overrides[] = _overrides;           \
+                                                                        \
+    static mrp_lua_classdef_t _name ## _class_def = {                   \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                     \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),              \
+        .constructor   = # _name,                                       \
+        .destructor    = _destr,                                        \
+        .type_name     = #_type,                                        \
+        .type_id       = -1,                                            \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),              \
+        .userdata_size = sizeof(_type),                                 \
+        .methods       = _name ## _class_methods,                       \
+        .overrides     = _name ## _class_overrides,                     \
+        .members       = NULL,                                          \
+        .nmember       = 0,                                             \
+        .natives       = NULL,                                          \
+        .nnative       = 0,                                             \
+        .notify        = NULL,                                          \
+        .flags         = _class_flags,                                  \
+    }
+
+
+#define MRP_LUA_FOREACH_FIELD(_L, _i, _n, _l)                           \
+    for (lua_pushnil(_L);                                               \
+                                                                        \
+         !(_l = 0) && lua_next(_L, _i) &&                               \
+         (_n = (lua_type(_L, -2) == LUA_TSTRING) ?                      \
+          lua_tolstring(_L, -2, &_l) : ""       );                      \
+                                                                        \
+         lua_pop(_L, 1))
+
+
+/** _i=loopcount, _t=table idx, _type=key type, _n=field, _l=len or idx */
+#define MRP_LUA_FOREACH_ALL(_L, _i, _t, _type, _n, _l)          \
+    for (lua_pushnil(_L), _i = 0;                               \
+                                                                \
+    (lua_next(_L, _t) &&                                        \
+    (((_type = lua_type(_L, -2)) == LUA_TSTRING ?               \
+      (_n = lua_tolstring(_L, -2, &_l)) : (_n = NULL)) ||       \
+     ((_type                     == LUA_TNIL    ?               \
+        !(_n = NULL, _l = 0) :                                  \
+       (_type                    == LUA_TNUMBER ?               \
+        (_n = NULL, _l = lua_tointeger(_L, -2)) :               \
+        !(_n = NULL, _l = 0))                           ||      \
+       _type != LUA_TSTRING))));                                \
+                                                                \
+         lua_pop(_L, 1), _i++)
+
+
+enum mrp_lua_event_type_e {
+    MRP_LUA_OBJECT_DESTRUCTION = 1,
+};
+
+
+/*
+ * stringification (debugging and Lua __tostring)
+ */
+
+/**
+ * tostring/stringification mode
+ */
+typedef enum {
+    /* verbosity modifiers */
+    MRP_LUA_TOSTR_LUA      = 0x000,      /* native Lua tostr */
+    MRP_LUA_TOSTR_MINIMAL  = 0x010,      /* minimal useful output */
+    MRP_LUA_TOSTR_COMPACT  = 0x020,      /* compact (sub-oneline) output */
+    MRP_LUA_TOSTR_ONELINE  = 0x040,      /* all output that fits into a line */
+    MRP_LUA_TOSTR_SHORT    = 0x080,      /* shortest multiline output */
+    MRP_LUA_TOSTR_MEDIUM   = 0x100,      /* medium multiline output */
+    MRP_LUA_TOSTR_FULL     = 0x200,      /* full object output */
+    MRP_LUA_TOSTR_VERBOSE  = 0x400,      /* dump all data imaginable */
+    MRP_LUA_TOSTR_MODEMASK = 0xff0,      /* dump mode mask */
+
+    /* content modifiers */
+    MRP_LUA_TOSTR_META    = 0x001,       /* show metadata (userdata_t) part */
+    MRP_LUA_TOSTR_DATA    = 0x002,       /* show user-visible data */
+    MRP_LUA_TOSTR_BOTH    = 0x003,       /* both meta- and user-visible data */
+} mrp_lua_tostr_mode_t;
+
+/** Default configuration. */
+#define MRP_LUA_TOSTR_DEFAULT   (MRP_LUA_TOSTR_COMPACT | MRP_LUA_TOSTR_META)
+
+/** Stack-dump configuration. */
+#define MRP_LUA_TOSTR_STACKDUMP (MRP_LUA_TOSTR_MINIMAL | MRP_LUA_TOSTR_META)
+
+/** Stack-dump configuration for errors. */
+#define MRP_LUA_TOSTR_ERRORDUMP (MRP_LUA_TOSTR_ONELINE | MRP_LUA_TOSTR_BOTH)
+
+/** Stack-dump confguration for object-tracking check dump. */
+#define MRP_LUA_TOSTR_CHECKDUMP (MRP_LUA_TOSTR_ONELINE | MRP_LUA_TOSTR_BOTH)
+
+/** Type of an object stringification handler. */
+typedef ssize_t (*mrp_lua_tostr_t)(mrp_lua_tostr_mode_t mode, char *buf,
+                                   size_t size, lua_State *L, void *data);
+
+/** Dump the given object to the provided buffer. */
+ssize_t mrp_lua_object_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
+                             lua_State *L, void *data);
+
+/** Dump the object at the given stack location to the provided buffer. */
+ssize_t mrp_lua_index_tostr(mrp_lua_tostr_mode_t mode, char *buf, size_t size,
+                            lua_State *L, int index);
+
+/*
+ * pre-declared class members
+ */
+
+typedef struct mrp_lua_class_member_s mrp_lua_class_member_t;
+typedef union  mrp_lua_value_u        mrp_lua_value_t;
+typedef int  (*mrp_lua_setter_t)(void *data, lua_State *L, int member,
+                                 mrp_lua_value_t *v);
+typedef int  (*mrp_lua_getter_t)(void *data, lua_State *L, int member,
+                                 mrp_lua_value_t *v);
+
+/*
+ * class member/extension flags
+ */
+
+typedef enum {
+    MRP_LUA_CLASS_NOFLAGS    = 0x000,    /* empty flags */
+    MRP_LUA_CLASS_EXTENSIBLE = 0x001,    /* class is user-extensible from Lua */
+    MRP_LUA_CLASS_READONLY   = 0x002,    /* class or member is readonly */
+    MRP_LUA_CLASS_NOTIFY     = 0x004,    /* notify when member is changed */
+    MRP_LUA_CLASS_NOINIT     = 0x008,    /* don't initialize member */
+    MRP_LUA_CLASS_NOOVERRIDE = 0x010,    /* don't override setters, getters */
+    MRP_LUA_CLASS_RAWGETTER  = 0x020,    /* getter pushes to the stack */
+    MRP_LUA_CLASS_RAWSETTER  = 0x040,    /* setter takes args from the stack */
+    MRP_LUA_CLASS_USESTACK   = 0x080,    /* autobridged method uses the stack */
+    MRP_LUA_CLASS_DYNAMIC    = 0x100,    /* allow dynamic GC for this class */
+} mrp_lua_class_flag_t;
+
+/*
+ * getter/setter statuses and macros
+ */
+
+#define MRP_LUA_OK_       1              /* successfully get/set */
+#define MRP_LUA_NOTFOUND  0              /* member not found */
+#define MRP_LUA_FAIL     -1              /* failed to get/set member */
+
+/*
+ * supported class member types
+ */
+
+#define MRP_LUA_VBASE      (LUA_TTHREAD + 1)
+#define MRP_LUA_VMAX       8192
+#define MRP_LUA_VTYPE(idx) (MRP_LUA_VBASE + (idx))
+
+typedef enum {
+    MRP_LUA_NONE    = LUA_TNONE,         /* not a valid type */
+    MRP_LUA_NULL    = LUA_TNIL,          /* don't use, nil */
+
+    MRP_LUA_BOOLEAN = LUA_TBOOLEAN,      /* boolean member */
+    MRP_LUA_STRING  = LUA_TSTRING,       /* string member */
+    MRP_LUA_DOUBLE  = LUA_TNUMBER,       /* double member */
+    MRP_LUA_FUNC    = LUA_TFUNCTION,     /* any Lua function member */
+    MRP_LUA_INTEGER = MRP_LUA_VTYPE(0),  /* integer member */
+    MRP_LUA_LFUNC   = MRP_LUA_VTYPE(1),  /* pure Lua function member */
+    MRP_LUA_CFUNC   = MRP_LUA_VTYPE(2),  /* C function member */
+    MRP_LUA_BFUNC   = MRP_LUA_VTYPE(3),  /* bridged function member */
+
+    MRP_LUA_BOOLEAN_ARRAY = MRP_LUA_VTYPE(4),
+    MRP_LUA_STRING_ARRAY  = MRP_LUA_VTYPE(5),
+    MRP_LUA_INTEGER_ARRAY = MRP_LUA_VTYPE(6),
+    MRP_LUA_DOUBLE_ARRAY  = MRP_LUA_VTYPE(7),
+
+    MRP_LUA_ANY     = MRP_LUA_VTYPE(8),  /* member of any type */
+    MRP_LUA_OBJECT  = MRP_LUA_VTYPE(9),  /* object member */
+    /* dynamically registered types */
+    MRP_LUA_MAX     = MRP_LUA_VTYPE(MRP_LUA_VMAX)
+} mrp_lua_type_t;
+
+
+/*
+ * a generic class member value
+ */
+
+union mrp_lua_value_u {
+    const char *str;                     /* string value */
+    bool        bln;                     /* boolean value */
+    int32_t     s32;                     /* integer value */
+    double      dbl;                     /* double value */
+    int         lfn;                     /* Lua function reference value */
+    void       *bfn;                     /* bridged function value */
+    int         any;                     /* Lua reference */
+    struct {                             /* array value */
+        void    **items;                 /* array items */
+        union {
+            size_t   *nitem64;           /* number of items */
+            int      *nitem32;           /* number of items */
+        };
+    } array;
+    struct {
+        int       ref;                   /* object reference */
+        void     *ptr;                   /* object pointer */
+    } obj;
+};
+
+
+/*
+ * a class member descriptor
+ */
+
+struct mrp_lua_class_member_s {
+    char             *name;              /* member name */
+    mrp_lua_type_t    type;              /* member type */
+    size_t            offs;              /* offset within type buffer */
+    size_t            size;              /* offset to size within type buffer */
+    size_t            sizew;             /* width of size within type buffer */
+    const char       *type_name;         /* object type name */
+    mrp_lua_type_t    type_id;           /* object type id */
+    mrp_lua_setter_t  setter;            /* setter if any */
+    mrp_lua_getter_t  getter;            /* getter if any */
+    int               flags;             /* member flags */
+};
+
+
+
+/**
+ * Murphy Lua Object Infrastructure
+ *
+ * The Murphy Lua object infrastructure allow you to declare and define
+ * the C implementation of a Lua class, along with a set of explicitly
+ * and or implicitly defined class members and class methods.
+ *
+ * Implicitly-defined members and methods - direct full control
+ *
+ * With implicitly defined members and methods you have almost absolute
+ * control over what members and methods, when and how your C object
+ * backend exposes to the Lua runtime, with minimal intervention from
+ * the Murphy object infrastructure.
+ *
+ * You can almost freely override any method of your Lua object. Ignoring
+ * some Lua-intrinsic details about raw versus ordinary member lookup, in
+ * practice overriding a method causes your corresponding C handler to be
+ * invoked whenever the Lua runtime makes a call to a member you have
+ * overridden in the Murphy Lua class definition of the object.
+ *
+ * A basic technique to gain almost full control of the behavior of your
+ * objects exposed behavior is simply to override the getfield (Lua
+ * __index) and setfield (Lua __newindex) meta-methods in the objects
+ * class definition. This way whenever someone tries to fetch, or set
+ * a specific member in your object you will be called backed to either
+ * provide the associated data, for getfield, or take the provided data
+ * and associate it with the supplied field name or index, for setfield.
+ *
+ * In your overridden C callbacks you take care of all the necessary
+ * actions of pushing the values corresponfing to fetched fields/indices
+ * and storing values associated with set fields or indices. You can do
+ * value, object state, accessibility (read-write) checks or any other
+ * check you see necessary and reject the operation by returning an error,
+ * or throwing an exception to the Lua runtime.
+ *
+ * Explicitly-defined members and methods - less control, but less code
+ *
+ * With explicitly defined members and/or methods, you specify the
+ * members and/or methods for your object class along with metainformation
+ * such as read-write/readonly status, write/update notifications,
+ * dedicated member-specific getter/setter callbacks, etc. and let the
+ * infrastructure take care of the details of passing information between
+ * your objects and the Lua interpreter. In the case of explicitly-
+ * defined members/methods, the Murphy Lua object infrastructure still
+ * offers a certain degree of flexibility about the details of how you
+ * want to handle specific members/methods. You can configure these by
+ * setting flags, each representing a certain option, on the member or
+ * for some flags on the full class.
+ *
+ * To provide a good balance between maximum flexibility and maximum
+ * control, the infrastructure allows you to mix and match pretty
+ * explicit and implicit member/method control pretty freely. You can
+ * declare parts of your members (typically the regularly behaving easy
+ * ones) explicitly, while handle the hairy ones (or those that are
+ * difficult or impossible to map to C by the metadata) with your own
+ * overridden getfield and setfield handlers. Which approach you take is
+ * fully up to you and you are very much encouraged to experiment with
+ * both to understand the limitations vs. conveniences presented by
+ * each in practical terms.
+ *
+ * As stated earlier, in pratice, you can select how much assistance you
+ * want from the infra on a per-member basis. You can relatively freely
+ * choose between fully automatic handling, where you do not need to do
+ * much apart from providing the corret metadata describing your object,
+ * and fully manual handling where you need to take care of almost all
+ * details of setting and retrieval (setfield/getfield).
+ *
+ * Occasionally the infra has technical limitations and if you hit any of
+ * these you have no choice but take care of the details yourself. Usually
+ * such limitations should not exist, however, and the chosen level of
+ * detail is mostly dictated by how complex rules or constrains a class
+ * member needs to obey. For instance, scalar members (strings, numbers,
+ * booleans, etc.) and functions without much restrictions you can let be
+ * handled automatically. Members with semantic restrictions, such as a
+ * restricted range of acceptable values, or with contextual dependencies,
+ * for instance dependencies on the values of other class members, you need
+ * to handle with a varying level of detail yourself.
+ *
+ * Even in the more complex cases, there are a few facilities offered by
+ * the infrastructure which were designed to let you get along in as many
+ * of the cases as possibly without having to write much extra code.
+ *
+ * Automatic Members (perhaps should be called automatic explicit members):
+ *
+ * 1. Automatic (maybe we should call them explicit) class members
+ *    You can declare a member with its name, type, storage offset,
+ *    and let the infra take care of setting and retrieving it.
+ *
+ * 2. Read-only Members
+ *    You can mark members (or a full class) read-only. Read-only members
+ *    can only be set from C. Trying to set a read-only member from Lua
+ *    will raise a runtime exception.
+ *
+ * 3. Change Notifications
+ *    You can request change notifications on a per-member basis. Whenever
+ *    a member with notifications on is changed from Lua, a class-wide
+ *    notification callback is invoked. You can check the newly set value
+ *    of the member and take any actions necessary to reflect the updated
+ *    value of the member. For instance, when setting a 'disabled' member
+ *    to true, you might disable the normal actions you class instance is
+ *    performing. Note that currently the notification is called back only
+ *    after the member has been updated and the callback has no return
+ *    value. IOW, there is no straightforward way to reject a change from
+ *    the notification callback (although, you can restore the old value
+ *    from a saved copy if the new one is not kosher and then raise a Lua
+ *    exception). However, this is still subject to change and probably
+ *    we'll change notification callbacks to receive both the old and new
+ *    values and to return a accepted/rejected/I-ve-taken-care-of-it type
+ *    of verdict.
+ *
+ * 4. Optional Getters and Setters
+ *    You can set on a per-members basis a getter, a setter, or both. If
+ *    a member has a getter it will be invoked instead of the common default
+ *    getter when the member is read/retrieved. Similary, when a member has
+ *    a setter this will be called instead of the default setter when the
+ *    value of the member is set.
+ *
+ * 5. Native (Fully Manual) Members
+ *    You can declare a set of members native (perhaps manual members would
+ *    be a bit more descriptive term). These will always be handed to your
+ *    getfield/setfield class methods so you need to take care of every
+ *    detail of setting and retrieving these variables.
+ *
+ * 6. Extended Members
+ *    You can mark your class extensible at will. If you do so, your users
+ *    will be able to extend your class from Lua by simply setting other
+ *    members than the ones you have declared for the class. This allows
+ *    easy duck-typing and extensions to your class be implemented in a
+ *    straightforward manner usually without complex layers of wrappers.
+ *
+ * Classes with Mixed Members
+ *
+ * You can quite freely mix and match automatic (both with and without
+ * getters and/or setters), fully manual, and extended class members,
+ * although the dominant usage tends to be to mix only automatic and
+ * extended members and keep manual classes strictly as such.
+ */
+
+/** Macro to define a Murphy Lua class. */
+#define MRP_LUA_DEFINE_CLASS(_name, _constr, _type, _destr,               \
+                             _methods, _overrides, _members,              \
+                             _blacklist, _notify, _tostring,              \
+                             _bridges, _class_flags)                      \
+    static mrp_lua_classdef_t _name ## _ ## _constr ## _class_def = {     \
+        .class_name    = MRP_LUA_CLASS_NAME(_name),                       \
+        .class_id      = MRP_LUA_CLASS_ID(_name, _constr),                \
+        .constructor   = # _name "." # _constr,                           \
+        .destructor    = _destr,                                          \
+        .type_name     = #_type,                                          \
+        .type_id       = -1,                                              \
+        .userdata_id   = MRP_LUA_UDATA_ID(_name, _constr),                \
+        .userdata_size = sizeof(_type),                                   \
+        .methods       = _methods,                                        \
+        .overrides     = _overrides,                                      \
+        .members       = _members,                                        \
+        .nmember       = _members == NULL ? 0 : MRP_ARRAY_SIZE(_members), \
+        .natives       = _blacklist,                                      \
+        .nnative       = _blacklist==NULL ? 0:MRP_ARRAY_SIZE(_blacklist), \
+        .bridges       = _bridges,                                        \
+        .nbridge       = _bridges == NULL ? 0 : MRP_ARRAY_SIZE(_bridges), \
+        .notify        = _notify,                                         \
+        .tostring      = _tostring,                                       \
+        .flags         = _class_flags,                                    \
+    }
+
+/** Macro to declare the list of class members. */
+#define MRP_LUA_MEMBER_LIST_TABLE(_name, ...) \
+    static mrp_lua_class_member_t _name[] = { __VA_ARGS__ }
+
+/**
+ * Generic generic macro to declare an automatic member for a class.
+ *
+ * @param _type    member type
+ * @param _name    member name in Lua
+ * @param _offs    member offset with visible part of userdata (if relevant)
+ * @param _set     optional member-specific setter
+ * @param _get     optional member-specific getter
+ * @param _flags   member flags, bitwise or of MRP_LUA_CLASS_READONLY,
+ *                 MRP_LUA_CLASS_NOTIFY, and MRP_LUA_CLASS_NOINIT. Also
+ *                 MRP_LUA_CLASS_NOFLAGS is available to denote the empty
+ *                 set of flags.
+ */
+#define MRP_LUA_CLASS_MEMBER(_type, _name, _offs, _set, _get, _flags)   \
+                                                                        \
+        .name = _name,                                                  \
+        .type = _type,                                                  \
+        .offs = _offs,                                                  \
+        .setter = _set,                                                 \
+        .getter = _get,                                                 \
+        .flags  = _flags,                                               \
+
+/*
+ * type-specific convenience macros
+ */
+
+/** Declare an automatic string member. */
+#define MRP_LUA_CLASS_STRING(_name, _offs, _set, _get, _flags)        \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_STRING, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic (signed 32-bit) integer member. */
+#define MRP_LUA_CLASS_INTEGER(_name, _offs, _set, _get, _flags)       \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_INTEGER, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic double-precision floating point member. */
+#define MRP_LUA_CLASS_DOUBLE(_name, _offs, _set, _get, _flags)        \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_DOUBLE, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic boolean member. */
+#define MRP_LUA_CLASS_BOOLEAN(_name, _offs, _set, _get, _flags)       \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_BOOLEAN, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic Lua function member. */
+#define MRP_LUA_CLASS_LFUNC(_name, _offs, _set, _get, _flags)         \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_LFUNC, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic C function member. */
+#define MRP_LUA_CLASS_CFUNC(_name, _offs, _set, _get, _flags)         \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_CFUNC, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic member with a value of any acceptable type. */
+#define MRP_LUA_CLASS_ANY(_name, _offs, _set, _get, _flags)           \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_ANY, _name, _offs, _set, _get, _flags)},
+
+/** Declare an automatic array and size member of the given type. */
+#define MRP_LUA_CLASS_ARRAY(_name, _type, _ctype, _p, _n, _set, _get, _flags) \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_##_type##_ARRAY, _name,                     \
+                          MRP_OFFSET(_ctype, _p), _set, _set, _flags)         \
+            .size  = MRP_OFFSET(_ctype, _n),                                  \
+            .sizew = sizeof(((_ctype *)NULL)->_n)    },
+
+/** Declare an automatic object and reference member of the given type. */
+#define MRP_LUA_CLASS_OBJECT(_name, _type, _poffs, _roffs, _set, _get, _flags) \
+    {MRP_LUA_CLASS_MEMBER(MRP_LUA_OBJECT, _name, _poffs,  _set, _get,   \
+                          _flags)                                       \
+            .size = _roffs,                                             \
+            .type_name = #_type,                                        \
+            .type_id = -1        },
+
+
+#include "murphy/core/lua-utils/funcbridge.h"
+
+/*
+ * a bridged class method descriptor
+ */
+
+typedef struct {
+    char                   *name;        /* function name */
+    const char             *signature;   /* function signature */
+    union {
+        mrp_funcbridge_cfunc_t  fc;      /* C function to bridge to */
+        mrp_funcbridge_t       *fb;      /* function bridge */
+    };
+    int                     flags;       /* method flags */
+} mrp_lua_class_bridge_t;
+
+
+/** Macro to declare the list of bridged class methods. */
+#define MRP_LUA_BRIDGE_LIST_TABLE(_name, ...) \
+    static mrp_lua_class_bridge_t _name[] = { __VA_ARGS__ }
+
+/**
+ * Generic generic macro to declare a bridged method for a class.
+ *
+ * @param _name      member name in Lua for this method
+ * @param _signature signature of this method (see funcbridge.h)
+ * @param _fn        bridged function to be invoked for this method
+ * @param _flags     member flags, currently either MRP_LUA_CLASS_NOFLAGS,
+ *                   or MRP_LUA_CLASS_USESTACK.
+ */
+
+#define MRP_LUA_CLASS_BRIDGE(_method, _signature, _fn, _flags)          \
+    {                                                                   \
+        .name      = _method,                                           \
+        .signature = _signature,                                        \
+        .fc        = _fn,                                               \
+        .flags     = _flags,                                            \
+    }
+
+
+#define MRP_LUA_CLASS_CHECKER(_type, _prefix, _class)                  \
+    static _type *_prefix##_check(lua_State *L, int idx)               \
+    {                                                                  \
+        return (_type *)mrp_lua_check_object(L, _class, idx);          \
+    } struct _prefix##_kludge_for_trailing_semicolon
+
+
+typedef struct mrp_lua_classdef_s     mrp_lua_classdef_t;
+typedef enum   mrp_lua_event_type_e   mrp_lua_event_type_t;
+
+typedef void (*mrp_lua_class_notify_t)(void *data, lua_State *L, int member);
+
+
+struct mrp_lua_classdef_s {
+    const char   *class_name;
+    const char   *class_id;
+    const char   *constructor;
+    void        (*destructor)(void *);
+    const char   *type_name;
+    int           type_id;
+    const void   *type_meta;
+    const char   *userdata_id;
+    size_t        userdata_size;
+    luaL_reg     *methods;
+    luaL_reg     *overrides;
+
+    mrp_lua_tostr_t tostring;            /* stringification handler */
+    mrp_list_hook_t objects;             /* instances of this class */
+    uint32_t        ncreated;            /* number of objects created */
+    uint32_t        ndestroyed;          /* number of objects destroyed */
+    int             nactive;             /* number of active objects */
+    int             ndead;               /* nuber of dead objects */
+
+    /* pre-declared members */
+    mrp_lua_class_member_t  *members;    /* pre-declared members */
+    int                      nmember;    /* number of pre-declared members */
+    char                   **natives;    /* 'native' member names */
+    int                      nnative;    /* number of native member names */
+    mrp_lua_class_bridge_t  *bridges;    /* bridged methods */
+    int                      nbridge;    /* number of bridged methods */
+    int                      brmeta;     /* reference to bridging metatable */
+    mrp_lua_class_flag_t     flags;      /* class member flags */
+    mrp_lua_class_notify_t   notify;     /* member change notify callback */
+    lua_CFunction            setfield;   /* overridden setfield, if any */
+    lua_CFunction            getfield;   /* overridden getfield, if any */
+};
+
+int   mrp_lua_create_object_class(lua_State *L, mrp_lua_classdef_t *def);
+void  mrp_lua_get_class_table(lua_State *L, mrp_lua_classdef_t *def);
+void *mrp_lua_create_object(lua_State *L, mrp_lua_classdef_t *def,
+                            const char *name, int);
+void  mrp_lua_set_object_name(lua_State  *L, mrp_lua_classdef_t *def,
+                              const char *name);
+void mrp_lua_set_object_index(lua_State *L, mrp_lua_classdef_t *def, int idx);
+
+void  mrp_lua_destroy_object(lua_State *L, const char *name,int, void *object);
+
+int   mrp_lua_find_object(lua_State *L, mrp_lua_classdef_t *def,
+                          const char *name);
+
+void *mrp_lua_check_object(lua_State *L, mrp_lua_classdef_t *def, int argnum);
+void *mrp_lua_to_object(lua_State *L, mrp_lua_classdef_t *def, int idx);
+int   mrp_lua_push_object(lua_State *L, void *object);
+
+mrp_lua_classdef_t *mrp_lua_get_object_classdef(void *);
+
+/** Specify pre-declared object members. */
+int mrp_lua_declare_members(mrp_lua_classdef_t *def, mrp_lua_class_flag_t flags,
+                            mrp_lua_class_member_t *members, int nmember,
+                            char **natives, int nnative,
+                            mrp_lua_class_notify_t notify);
+/** Store and return a reference the value at the given stack location. */
+int mrp_lua_object_ref_value(void *object, lua_State *L, int idx);
+/** Remove the given stored reference. */
+void mrp_lua_object_unref_value(void *object, lua_State *L, int ref);
+/** Retrieve and push to the stack the value for the fiven reference. */
+int mrp_lua_object_deref_value(void *object, lua_State *L, int ref,
+                               int pushnil);
+/** Get a private reference for the given reference owned by the given object. */
+int mrp_lua_object_getref(void *owner, void *object, lua_State *L, int ref);
+/** Decreate the reference count of the given object reference. */
+#define mrp_lua_object_putref(o, L, ref) mrp_lua_object_unref_value(o, L, ref)
+/** Set a pre-declared object member. */
+int mrp_lua_set_member(void *data, lua_State *L, char *err, size_t esize);
+/** Get and push a pre-declared object member. */
+int mrp_lua_get_member(void *data, lua_State *L, char *err, size_t esize);
+/** Initialize pre-declared object members from table at the given index. */
+int mrp_lua_init_members(void *data, lua_State *L, int idx,
+                         char *err, size_t esize);
+/** Attempt to an array at tidx of expected type, with *nitemp max items. */
+int mrp_lua_object_collect_array(lua_State *L, int tidx, void **itemsp,
+                                 size_t *nitemp, int *expected, int dup,
+                                 char *e, size_t esize);
+/** Free an array duplicated by mrp_lua_object_collect_array. */
+void mrp_lua_object_free_array(void **itemsp, size_t *nitemp, int type);
+/** Get the class type id for the given class name. */
+mrp_lua_type_t mrp_lua_class_name_type(const char *class_name);
+/** Get the class type id for the given class id. */
+mrp_lua_type_t mrp_lua_class_id_type(const char *class_id);
+/** Get the class type id for the given type name. */
+mrp_lua_type_t mrp_lua_class_type(const char *type_name);
+/** Check if the object at the given stack index is of the given type. */
+int mrp_lua_object_of_type(lua_State *L, int idx, mrp_lua_type_t type);
+/** Check if the given (known to be murphy Lua) object is of given type. */
+int mrp_lua_pointer_of_type(void *data, mrp_lua_type_t type);
+/** Enable/disable per-class Murphy Lua object tracking. */
+void mrp_lua_track_objects(bool enable);
+/** Dump all active murphy Lua objects. */
+void mrp_lua_dump_objects(mrp_lua_tostr_mode_t mode, lua_State *L, FILE *fp);
+
+#endif  /* __MURPHY_LUA_OBJECT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-utils/strarray.c b/src/core/lua-utils/strarray.c
new file mode 100644 (file)
index 0000000..6f0b2d1
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/core/lua-utils/strarray.h>
+#include <murphy/core/lua-utils/lua-utils.h>
+
+
+mrp_lua_strarray_t *mrp_lua_check_strarray(lua_State *L, int t)
+{
+    size_t len;
+    size_t size;
+    mrp_lua_strarray_t *arr;
+    size_t i;
+
+    luaL_checktype(L, t, LUA_TTABLE);
+    len  = luaL_getn(L, t);
+    size = sizeof(mrp_lua_strarray_t) + sizeof(const char *) * (len + 1);
+
+    if (!(arr = malloc(size)))
+        luaL_error(L, "can't allocate %d byte long memory", size);
+
+    arr->nstring = len;
+
+    lua_pushvalue(L, t);
+
+    for (i = 0;  i < len;  i++) {
+        lua_pushnumber(L, (int)(i+1));
+        lua_gettable(L, -2);
+
+        arr->strings[i] = strdup(luaL_checklstring(L, -1, NULL));
+
+        lua_pop(L, 1);
+    }
+
+    arr->strings[i] = NULL;
+
+    lua_pop(L, 1);
+
+    return arr;
+}
+
+int mrp_lua_push_strarray(lua_State *L, mrp_lua_strarray_t *arr)
+{
+    size_t i;
+
+    if (!arr)
+        lua_pushnil(L);
+    else {
+        lua_createtable(L, arr->nstring, 0);
+
+        for (i = 0;  i < arr->nstring; i++) {
+            lua_pushinteger(L, (int)(i+1));
+            lua_pushstring(L, arr->strings[i]);
+            lua_settable(L, -3);
+        }
+    }
+
+    return 1;
+}
+
+void mrp_lua_free_strarray(mrp_lua_strarray_t *arr)
+{
+    size_t i;
+
+    if (arr) {
+        for (i = 0;  i < arr->nstring;  i++)
+            free((void *)arr->strings[i]);
+        free(arr);
+    }
+}
+
+
+char *mrp_lua_print_strarray(mrp_lua_strarray_t *arr, char *buf, int len)
+{
+    char *p, *e, *s;
+    size_t i;
+
+    e = (p = buf) + len;
+
+    if (len > 0) {
+        if (!arr->nstring)
+            p += snprintf(p, e-p, "<empty>");
+        else {
+            for (i = 0, s = "";    i < arr->nstring && p < e;    i++, s = ", ")
+                p += snprintf(p, e-p, "%s%s", s, arr->strings[i]);
+        }
+    }
+
+    return buf;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/lua-utils/strarray.h b/src/core/lua-utils/strarray.h
new file mode 100644 (file)
index 0000000..b989626
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_LUA_STRARRAY_H__
+#define __MURPHY_LUA_STRARRAY_H__
+
+typedef struct mrp_lua_strarray_s   mrp_lua_strarray_t;
+
+struct mrp_lua_strarray_s {
+    size_t      nstring;
+    const char *strings[];
+};
+
+mrp_lua_strarray_t *mrp_lua_check_strarray(lua_State *, int);
+int   mrp_lua_push_strarray(lua_State *L, mrp_lua_strarray_t *);
+void  mrp_lua_free_strarray(mrp_lua_strarray_t *);
+char *mrp_lua_print_strarray(mrp_lua_strarray_t *, char *, int);
+
+
+#endif  /* __MURPHY_LUA_STRARRAY_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/core/method.c b/src/core/method.c
new file mode 100644 (file)
index 0000000..664af2c
--- /dev/null
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/method.h>
+
+
+typedef struct {
+    char            *name;               /* name of methods in this chain */
+    mrp_list_hook_t  methods;            /* list of exported methods */
+} method_list_t;
+
+
+typedef struct {
+    __MRP_METHOD_FIELDS();               /* method fields */
+    mrp_list_hook_t hook;                /* to method table */
+} method_t;
+
+
+static void purge_method_list(void *key, void *object);
+
+
+static mrp_htbl_t *methods = NULL;       /* hash table of method lists */
+
+
+static int create_method_table(void)
+{
+    mrp_htbl_config_t hcfg;
+
+    mrp_clear(&hcfg);
+    hcfg.comp = mrp_string_comp;
+    hcfg.hash = mrp_string_hash;
+    hcfg.free = purge_method_list;
+
+    methods = mrp_htbl_create(&hcfg);
+
+    if (methods != NULL)
+        return 0;
+    else
+        return -1;
+}
+
+
+MRP_EXIT static void destroy_method_table(void)
+{
+    mrp_htbl_destroy(methods, TRUE);
+    methods = NULL;
+}
+
+
+static void free_method(method_t *m)
+{
+    if (m != NULL) {
+        mrp_free(m->name);
+        mrp_free(m->signature);
+        mrp_free(m);
+    }
+}
+
+
+static method_t *alloc_method(mrp_method_descr_t *method)
+{
+    method_t *m;
+
+    m = mrp_allocz(sizeof(*m));
+
+    if (m != NULL) {
+        mrp_list_init(&m->hook);
+        m->name      = mrp_strdup(method->name);
+        m->signature = mrp_strdup(method->signature);
+
+        if (m->name != NULL &&
+            (m->signature != NULL || method->signature == NULL)) {
+            m->native_ptr = method->native_ptr;
+            m->script_ptr = method->script_ptr;
+            m->plugin     = method->plugin;
+        }
+        else {
+            free_method(m);
+            m = NULL;
+        }
+    }
+
+    return m;
+}
+
+
+static method_list_t *create_method_list(const char *name)
+{
+    method_list_t *l;
+
+    if (MRP_UNLIKELY(methods == NULL)) {
+        if (create_method_table() < 0)
+            return NULL;
+    }
+
+    l = mrp_allocz(sizeof(*l));
+
+    if (l != NULL) {
+        mrp_list_init(&l->methods);
+        l->name = mrp_strdup(name);
+
+        if (l->name != NULL) {
+            if (mrp_htbl_insert(methods, (void *)l->name, (void *)l))
+                return l;
+        }
+
+        mrp_free(l->name);
+        mrp_free(l);
+    }
+
+    return NULL;
+}
+
+
+static void free_method_list(method_list_t *l)
+{
+    mrp_list_hook_t *p, *n;
+    method_t        *m;
+
+    if (l != NULL) {
+        mrp_list_foreach(&l->methods, p, n) {
+            m = mrp_list_entry(p, typeof(*m), hook);
+
+            mrp_list_delete(&m->hook);
+            free_method(m);
+        }
+
+        mrp_free(l->name);
+        mrp_free(l);
+    }
+}
+
+
+static void purge_method_list(void *key, void *object)
+{
+    MRP_UNUSED(key);
+
+    free_method_list(object);
+}
+
+
+static method_list_t *lookup_method_list(const char *name)
+{
+    method_list_t *l;
+
+    if (methods != NULL)
+        l = mrp_htbl_lookup(methods, (void *)name);
+    else
+        l = NULL;
+
+    return l;
+}
+
+
+static inline int check_signatures(const char *sig1, const char *sig2)
+{
+    static int warned = FALSE;
+
+    MRP_UNUSED(sig1);
+    MRP_UNUSED(sig2);
+
+    if (!warned) {
+        mrp_log_warning("XXX TODO: implement signature checking (%s@%s:%d)",
+                        __FUNCTION__, __FILE__, __LINE__);
+        warned = TRUE;
+    }
+
+    return TRUE;
+}
+
+
+static method_t *lookup_method(const char *name, const char *signature,
+                               void *native_ptr,
+                               int (*script_ptr)(mrp_plugin_t *plugin,
+                                                 const char *name,
+                                                 mrp_script_env_t *env),
+                               mrp_plugin_t *plugin)
+{
+    method_list_t   *l;
+    method_t        *m;
+    mrp_list_hook_t *p, *n;
+
+    l = lookup_method_list(name);
+
+    if (l != NULL) {
+        mrp_list_foreach(&l->methods, p, n) {
+            m = mrp_list_entry(p, typeof(*m), hook);
+
+            if (((m->signature == NULL && signature == NULL) ||
+                 (m->signature != NULL && signature != NULL &&
+                  !strcmp(m->signature, signature))) &&
+                m->native_ptr == native_ptr && m->script_ptr == script_ptr &&
+                m->plugin == plugin)
+                return m;
+        }
+    }
+
+    return NULL;
+}
+
+
+method_t *find_method(const char *name, const char *signature)
+{
+    method_list_t   *l;
+    method_t        *m;
+    mrp_list_hook_t *p, *n;
+    const char      *base;
+    int              plen;
+
+    base = strrchr(name, '.');
+    if (base != NULL) {
+        plen = base - name;
+        base = base + 1;
+    }
+    else {
+        base = name;
+        plen = 0;
+    }
+
+    l = lookup_method_list(base);
+
+    if (l != NULL) {
+        mrp_list_foreach(&l->methods, p, n) {
+            m = mrp_list_entry(p, typeof(*m), hook);
+
+            if (signature != NULL && m->signature != NULL)
+                if (!check_signatures(signature, m->signature))
+                    continue;
+
+            if (plen != 0) {
+                if (m->plugin == NULL)
+                    continue;
+                if (strncmp(name, m->plugin->instance, plen) ||
+                    m->plugin->instance[plen] != '\0')
+                    continue;
+            }
+
+            return m;
+        }
+    }
+
+    errno = ENOENT;
+    return NULL;
+}
+
+
+static int export_method(method_t *method)
+{
+    method_list_t *l;
+
+    l = lookup_method_list(method->name);
+
+    if (l == NULL) {
+        l = create_method_list(method->name);
+
+        if (l == NULL)
+            return -1;
+    }
+
+    mrp_ref_plugin(method->plugin);
+    mrp_list_append(&l->methods, &method->hook);
+
+    return 0;
+}
+
+
+static int remove_method(const char *name, const char *signature,
+                         void *native_ptr,
+                         int (*script_ptr)(mrp_plugin_t *plugin,
+                                           const char *name,
+                                           mrp_script_env_t *env),
+                         mrp_plugin_t *plugin)
+{
+    method_t *m;
+
+    m = lookup_method(name, signature, native_ptr, script_ptr, plugin);
+
+    if (m != NULL) {
+        mrp_list_delete(&m->hook);
+        mrp_unref_plugin(m->plugin);
+        free_method(m);
+
+        return 0;
+    }
+    else {
+        errno = ENOENT;
+        return -1;
+    }
+}
+
+
+int mrp_export_method(mrp_method_descr_t *method)
+{
+    method_t *m;
+
+    if (lookup_method(method->name, method->signature,
+                      method->native_ptr, method->script_ptr,
+                      method->plugin) != NULL)
+        errno = EEXIST;
+    else {
+        m = alloc_method(method);
+
+        if (m != NULL) {
+            if (export_method(m) == 0) {
+                mrp_log_info("exported method %s (%s) %s%s.",
+                             method->name,
+                             method->signature ? method->signature : "-",
+                             method->plugin ? "from plugin " : "",
+                             method->plugin ? method->plugin->instance : "");
+                return 0;
+            }
+            else
+                free_method(m);
+        }
+    }
+
+    mrp_log_error("Failed to export method %s (%s) %s%s.",
+                  method->name, method->signature,
+                  method->plugin ? "from plugin " : "",
+                  method->plugin ? method->plugin->instance : "");
+
+    return -1;
+}
+
+
+int mrp_remove_method(mrp_method_descr_t *method)
+{
+    return remove_method(method->name, method->signature,
+                         method->native_ptr, method->script_ptr,
+                         method->plugin);
+}
+
+
+int mrp_import_method(const char *name, const char *signature,
+                      void **native_ptr,
+                      int (**script_ptr)(mrp_plugin_t *plugin, const char *name,
+                                         mrp_script_env_t *env),
+                      mrp_plugin_t **plugin)
+{
+    method_t *m;
+
+    if ((script_ptr != NULL && plugin == NULL) ||
+        (script_ptr == NULL && plugin != NULL)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    m = find_method(name, signature);
+
+    if (m == NULL) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    if ((native_ptr != NULL && m->native_ptr == NULL) ||
+        (script_ptr != NULL && m->script_ptr == NULL)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    mrp_ref_plugin(m->plugin);
+
+    if (native_ptr != NULL)
+        *native_ptr = m->native_ptr;
+    if (script_ptr != NULL) {
+        *script_ptr = m->script_ptr;
+        *plugin     = m->plugin;
+    }
+
+    return 0;
+}
+
+
+int mrp_release_method(const char *name, const char *signature,
+                       void **native_ptr,
+                       int (**script_ptr)(mrp_plugin_t *plugin,
+                                          const char *name,
+                                          mrp_script_env_t *env))
+{
+    method_t *m;
+
+    m = find_method(name, signature);
+
+    if (m == NULL) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    if ((native_ptr != NULL && (*native_ptr != m->native_ptr)) ||
+        (script_ptr != NULL && (*script_ptr != m->script_ptr))) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    mrp_unref_plugin(m->plugin);
+
+    if (native_ptr != NULL)
+        *native_ptr = NULL;
+    if (script_ptr != NULL)
+        *script_ptr = NULL;
+
+    return 0;
+}
diff --git a/src/core/method.h b/src/core/method.h
new file mode 100644 (file)
index 0000000..504cc4f
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CORE_METHOD_H__
+#define __MURPHY_CORE_METHOD_H__
+
+typedef struct mrp_method_descr_s mrp_method_descr_t;
+
+#include <murphy/core/plugin.h>
+#include <murphy/core/scripting.h>
+
+
+/*
+ * exported methods
+ */
+
+#define __MRP_METHOD_FIELDS(...)                                             \
+    __VA_ARGS__ char *name;              /* method name */                   \
+    __VA_ARGS__ char *signature;         /* method signature */              \
+    /* pointers to exported function, native and boilerplate for scripts */  \
+    void         *native_ptr;                                                \
+    int         (*script_ptr)(mrp_plugin_t *plugin, const char *name,        \
+                              mrp_script_env_t *env);                        \
+    mrp_plugin_t *plugin                 /* exporting plugin (or NULL) */    \
+
+struct mrp_method_descr_s {
+    __MRP_METHOD_FIELDS(const);
+};
+
+
+/*
+ * convenience macros for declaring exported methods
+ */
+
+#define MRP_EXPORTABLE(_return_type, _func, _arglist)     \
+    static const char _func##_method_signature[] =        \
+        #_return_type" __ "#_arglist;                     \
+                                                          \
+    static _return_type _func _arglist
+
+/** Declare a method along with a boilerplate to call from scripts. */
+#define MRP_GENERIC_METHOD(_name, _func, _boilerplate) {  \
+            .name       = _name,                          \
+            .signature  = _func##_method_signature,       \
+            .native_ptr = _func,                          \
+            .script_ptr = _boilerplate,                   \
+            .plugin     = NULL,                           \
+        }
+
+/** Declare a method that cannot be called from scripts. */
+#define MRP_NATIVE_METHOD(_name, _func) {                \
+            .name       = _name,                         \
+            .signature  = _func##_method_signature,      \
+            .native_ptr = _func,                         \
+            .script_ptr = NULL,                          \
+            .plugin     = NULL,                          \
+        }
+
+/** Declare a method that can only be called via a boilerplate for scripts. */
+#define MRP_SCRIPT_METHOD(_name, _boilerplate) {         \
+            .name       = _name,                         \
+            .signature  = NULL,                          \
+            .native_ptr = NULL,                          \
+            .script_ptr = _boilerplate,                  \
+            .plugin     = NULL,                          \
+        }
+
+
+/*
+ * convenience macros for declaring imported methods
+ */
+
+#define MRP_IMPORTABLE(_return_type, _funcptr, _arglist) \
+    static const char _funcptr##_method_signature[] =    \
+        #_return_type" __ "#_arglist;                    \
+                                                         \
+    static _return_type (*_funcptr) _arglist
+
+#define MRP_IMPORT_METHOD(_name, _funcptr) {             \
+        .name       = _name,                             \
+        .signature  = _funcptr##_method_signature,       \
+        .native_ptr = (void *)&_funcptr,                 \
+    }
+
+/** Export a method for plugins and/or scripts. */
+int mrp_export_method(mrp_method_descr_t *method);
+
+/** Remove an exported method. */
+int mrp_remove_method(mrp_method_descr_t *method);
+
+/** Import an exported method. */
+int mrp_import_method(const char *name, const char *signature,
+                      void **native_ptr,
+                      int (**script_ptr)(mrp_plugin_t *plugin, const char *name,
+                                         mrp_script_env_t *env),
+                      mrp_plugin_t **plugin);
+
+/** Release an imported method. */
+int mrp_release_method(const char *name, const char *signature,
+                      void **native_ptr,
+                      int (**script_ptr)(mrp_plugin_t *plugin, const char *name,
+                                         mrp_script_env_t *env));
+
+
+
+#endif /* __MURPHY_CORE_METHOD_H__ */
diff --git a/src/core/murphy-core.pc.in b/src/core/murphy-core.pc.in
new file mode 100644 (file)
index 0000000..cbb1926
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-core
+Description: Murphy policy framework, core library.
+Requires: murphy-common lua
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-common -lmurphy-lua-decision -lmurphy-lua-utils @LUA_LIBS@ -lmurphy-core
+Cflags: -I${includedir}
diff --git a/src/core/plugin.c b/src/core/plugin.c
new file mode 100644 (file)
index 0000000..3606216
--- /dev/null
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <murphy/common/list.h>
+#include <murphy/common/file-utils.h>
+#include <murphy/core/plugin.h>
+
+#define PLUGIN_PREFIX "plugin-"
+#define BUILTIN TRUE
+#define DYNAMIC FALSE
+
+#define __PARANOID_BLACKLIST_CHECK__
+
+static mrp_plugin_descr_t *open_builtin(mrp_context_t *ctx, const char *name);
+static mrp_plugin_descr_t *open_dynamic(mrp_context_t *ctx, const char *name,
+                                        void **handle);
+static mrp_plugin_t *find_plugin(mrp_context_t *ctx, char *name);
+static mrp_plugin_t *find_plugin_instance(mrp_context_t *ctx,
+                                          const char *instance);
+static int parse_plugin_args(mrp_plugin_t *plugin,
+                             mrp_plugin_arg_t *argv, int argc);
+
+static int export_plugin_methods(mrp_plugin_t *plugin);
+static int remove_plugin_methods(mrp_plugin_t *plugin);
+static int import_plugin_methods(mrp_plugin_t *plugin);
+static int release_plugin_methods(mrp_plugin_t *plugin);
+
+
+/*
+ * list of built-in in plugins
+ *
+ * Descriptors of built-in plugins, ie. plugins that are linked to
+ * the daemon, get collected to this list during startup.
+ */
+
+static MRP_LIST_HOOK(builtin_plugins);
+
+
+/*
+ * plugin-related events
+ */
+
+enum {
+    PLUGIN_EVENT_LOADED = 0,             /* plugin has been loaded */
+    PLUGIN_EVENT_STARTED,                /* plugin has been started */
+    PLUGIN_EVENT_FAILED,                 /* plugin failed to start */
+    PLUGIN_EVENT_STOPPING,               /* plugin being stopped */
+    PLUGIN_EVENT_STOPPED,                /* plugin has been stopped */
+    PLUGIN_EVENT_UNLOADED,               /* plugin has been unloaded */
+    PLUGIN_EVENT_MAX,
+};
+
+
+MRP_REGISTER_EVENTS(plugin_events,
+                    MRP_EVENT(MRP_PLUGIN_EVENT_LOADED  , PLUGIN_EVENT_LOADED  ),
+                    MRP_EVENT(MRP_PLUGIN_EVENT_STARTED , PLUGIN_EVENT_STARTED ),
+                    MRP_EVENT(MRP_PLUGIN_EVENT_FAILED  , PLUGIN_EVENT_FAILED  ),
+                    MRP_EVENT(MRP_PLUGIN_EVENT_STOPPING, PLUGIN_EVENT_STOPPING),
+                    MRP_EVENT(MRP_PLUGIN_EVENT_STOPPED , PLUGIN_EVENT_STOPPED ),
+                    MRP_EVENT(MRP_PLUGIN_EVENT_UNLOADED, PLUGIN_EVENT_UNLOADED));
+
+
+static inline int is_listed(const char *list,  const char *name)
+{
+    const char *b, *n, *e;
+
+    if (list == NULL || name == NULL)
+        return FALSE;
+
+    if ((list[0] == '*' && list[1] == '\0') || (!strcmp(list, "all")))
+        return TRUE;
+
+    b = list;
+    while (b && *b) {
+        while (*b == ' ' || *b == '\t' || *b == ',')
+            b++;
+
+        if ((e = n = strchr(b, ',')) != NULL) {
+            while (e > b && (*e == ' ' || *e == '\t' || *e == ','))
+                e--;
+        }
+
+        if ((e != NULL && e > b && !strncmp(b, name, e - b)) ||
+            (e == NULL &&          !strcmp (b, name)))
+            return TRUE;
+
+        if ((b[0] == '*' && (b[1] == ',' ||
+                             b[1] == ' ' || b[1] == '\t' ||
+                             b[1] == '\0')) ||
+            (b[0] == 'a' && b[1] == 'l' && b[2] == 'l' &&
+             (b[1] == ',' ||
+              b[1] == ' ' || b[1] == '\t' ||
+              b[1] == '\0'))) {
+            mrp_log_warning("Wildcard entry in blacklist/whitelist '%s'", list);
+            return TRUE;
+        }
+
+        b = n ? n + 1 : NULL;
+    }
+
+    return FALSE;
+}
+
+
+static inline int is_blacklisted(mrp_context_t *ctx, const char *name,
+                                 int builtin)
+{
+#define IS_WILDCARD(l) \
+    (((l) != NULL && (l)[0] == '*' && (l)[1] == '\0') ||        \
+     ((l) != NULL && !strcmp((l), "all")))
+
+    const char *bl, *wl, *type_bl, *type_wl;
+    int         b, w, tb, tw, blacklist;
+
+    mrp_debug("checking if %s plugin %s is blacklisted",
+              builtin ? "builtin" : "dynamic", name);
+
+    bl = ctx->blacklist_plugins;
+    wl = ctx->whitelist_plugins;
+    b  = is_listed(bl, name);
+    w  = is_listed(wl, name);
+
+    if (builtin) {
+        type_bl = ctx->blacklist_builtin;
+        type_wl = ctx->whitelist_builtin;
+    }
+    else {
+        type_bl = ctx->blacklist_dynamic;
+        type_wl = ctx->whitelist_dynamic;
+    }
+
+    tb = is_listed(type_bl, name);
+    tw = is_listed(type_wl, name);
+
+    mrp_debug("%s: b:(%s,%s), w:(%s,%s)", name,
+              b ? "true" : "false", tb ? "true" : "false",
+              w ? "true" : "false", tw ? "true" : "false");
+
+    if (IS_WILDCARD(bl) || IS_WILDCARD(type_bl)) {
+        if (w || tw)
+            blacklist = FALSE;
+         else
+            blacklist = TRUE;
+
+        goto verdict;
+    }
+
+    if (IS_WILDCARD(wl) || IS_WILDCARD(type_wl)) {
+        if (b || tb)
+            blacklist = TRUE;
+        else
+            blacklist = FALSE;
+
+        goto verdict;
+    }
+
+    blacklist = (( is_listed(bl, name) || is_listed(type_bl, name)) &&
+                 !(is_listed(wl, name) || is_listed(type_wl, name)));
+
+
+ verdict:
+    mrp_debug("%s: %sblacklisted", name, blacklist ? "" : "not ");
+
+    return blacklist;
+}
+
+
+static int emit_plugin_event(int idx, mrp_plugin_t *plugin)
+{
+    mrp_context_t  *ctx   = plugin->ctx;
+    mrp_mainloop_t *ml    = ctx->ml;
+    uint16_t        name  = MRP_PLUGIN_TAG_PLUGIN;
+    uint16_t        inst  = MRP_PLUGIN_TAG_INSTANCE;
+    int             flags = MRP_EVENT_SYNCHRONOUS;
+
+    if (ctx->plugin_bus == NULL)
+        ctx->plugin_bus = mrp_event_bus_get(ml, MRP_PLUGIN_BUS);
+
+    if (mrp_event_emit_msg(ctx->plugin_bus, plugin_events[idx].id, flags,
+                           MRP_MSG_TAG_STRING(name, plugin->descriptor->name),
+                           MRP_MSG_TAG_STRING(inst, plugin->instance)) < 0)
+        return FALSE;
+    else
+        return TRUE;
+}
+
+
+int mrp_register_builtin_plugin(mrp_plugin_descr_t *descriptor)
+{
+    mrp_plugin_t *plugin;
+
+    if (descriptor->name && descriptor->init && descriptor->exit) {
+        if ((plugin = mrp_allocz(sizeof(*plugin))) != NULL) {
+            plugin->descriptor = descriptor;
+            mrp_list_append(&builtin_plugins, &plugin->hook);
+
+            return TRUE;
+        }
+        else
+            return FALSE;
+    }
+    else {
+        mrp_log_error("Ignoring static plugin '%s' with an invalid or "
+                      "incomplete plugin descriptor.", descriptor->path);
+        return FALSE;
+    }
+}
+
+
+void mrp_block_blacklisted_plugins(mrp_context_t *ctx)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *builtin;
+
+    mrp_list_foreach(&builtin_plugins, p, n) {
+        builtin = mrp_list_entry(p, typeof(*builtin), hook);
+
+        if (is_blacklisted(ctx, builtin->descriptor->name, BUILTIN)) {
+            mrp_log_info("Unlinking blacklisted builtin plugin %s",
+                         builtin->descriptor->name);
+            mrp_list_delete(&builtin->hook);
+        }
+    }
+}
+
+
+int mrp_plugin_exists(mrp_context_t *ctx, const char *name)
+{
+    struct stat st;
+    char        path[PATH_MAX];
+
+    if (open_builtin(ctx, name))
+        return TRUE;
+    else {
+        if (is_blacklisted(ctx, name, DYNAMIC))
+            return FALSE;
+
+        snprintf(path, sizeof(path), "%s/%s%s.so", ctx->plugin_dir,
+                 PLUGIN_PREFIX, name);
+        if (stat(path, &st) == 0)
+            return TRUE;
+        else
+            return FALSE;
+    }
+}
+
+
+int mrp_plugin_loaded(mrp_context_t *ctx, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *plugin;
+
+    mrp_list_foreach(&ctx->plugins, p, n) {
+        plugin = mrp_list_entry(p, typeof(*plugin), hook);
+
+        if (!strcmp(plugin->instance, name))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+int mrp_plugin_running(mrp_context_t *ctx, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *plugin;
+
+    mrp_list_foreach(&ctx->plugins, p, n) {
+        plugin = mrp_list_entry(p, typeof(*plugin), hook);
+
+        if (!strcmp(plugin->instance, name))
+            return (plugin->state == MRP_PLUGIN_RUNNING);
+    }
+
+    return FALSE;
+}
+
+
+static inline int check_plugin_version(mrp_plugin_descr_t *descr)
+{
+    int major, minor;
+
+    major = MRP_VERSION_MAJOR(descr->mrp_version);
+    minor = MRP_VERSION_MINOR(descr->mrp_version);
+
+    if (major != MRP_PLUGIN_API_MAJOR || minor > MRP_PLUGIN_API_MINOR) {
+        mrp_log_error("Plugin '%s' uses incompatible version (%d.%d vs. %d.%d)",
+                      descr->name, major, minor,
+                      MRP_PLUGIN_API_MAJOR, MRP_PLUGIN_API_MINOR);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static inline int check_plugin_singleton(mrp_plugin_descr_t *descr)
+{
+    if (descr->singleton && descr->ninstance > 1) {
+        mrp_log_error("Singleton plugin '%s' has already been instantiated.",
+                      descr->name);
+        return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+mrp_plugin_t *mrp_load_plugin(mrp_context_t *ctx, const char *name,
+                              const char *instance, mrp_plugin_arg_t *args,
+                              int narg)
+{
+    mrp_plugin_t        *plugin;
+    char                 path[PATH_MAX];
+    mrp_plugin_descr_t  *dynamic, *builtin, *descr;
+    void                *handle;
+    mrp_console_group_t *cmds;
+    char                 grpbuf[PATH_MAX], *cmdgrp;
+
+    if (name == NULL)
+        return NULL;
+
+    if (instance == NULL)
+        instance = name;
+
+    if (ctx->state == MRP_STATE_RUNNING && ctx->disable_runtime_load) {
+        mrp_log_error("Post-startup plugin loading has been disabled.");
+        return NULL;
+    }
+
+    if (find_plugin_instance(ctx, instance) != NULL) {
+        mrp_log_error("Plugin '%s' has already been loaded.", instance);
+        return NULL;
+    }
+
+    plugin = NULL;
+    handle = NULL;
+    snprintf(path, sizeof(path), "%s/%s%s.so", ctx->plugin_dir,
+             PLUGIN_PREFIX, name);
+
+    dynamic = open_dynamic(ctx, name, &handle);
+    builtin = open_builtin(ctx, name);
+
+    if (dynamic != NULL) {
+        if (builtin != NULL)
+            mrp_log_warning("Dynamic plugin '%s' shadows builtin plugin '%s'.",
+                            path, builtin->path);
+        descr = dynamic;
+    }
+    else {
+        if (builtin == NULL) {
+            mrp_log_error("Could not find plugin '%s' to load '%s'.", name,
+                          instance);
+            return NULL;
+        }
+        descr = builtin;
+    }
+
+    descr->ninstance++;
+
+    if (!check_plugin_version(descr) || !check_plugin_singleton(descr))
+        goto fail;
+
+    if ((plugin = mrp_allocz(sizeof(*plugin))) != NULL) {
+        mrp_list_init(&plugin->hook);
+
+        plugin->instance = mrp_strdup(instance);
+        plugin->path     = mrp_strdup(handle ? path : descr->path);
+
+        if (plugin->instance == NULL || plugin->path == NULL) {
+            mrp_log_error("Failed to allocate plugin '%s'.", name);
+            goto fail;
+        }
+
+        if (descr->cmds != NULL) {
+            plugin->cmds = cmds = mrp_allocz(sizeof(*plugin->cmds));
+
+            if (cmds != NULL) {
+                mrp_list_init(&cmds->hook);
+
+                if (instance != name) {
+                    snprintf(grpbuf, sizeof(grpbuf), "%s-%s", name, instance);
+                    cmdgrp = grpbuf;
+                }
+                else
+                    cmdgrp = (char *)name;
+
+                cmds->name = mrp_strdup(cmdgrp);
+
+                if (cmds->name == NULL) {
+                    mrp_log_error("Failed to allocate plugin commands.");
+                    goto fail;
+                }
+
+                cmds->commands = descr->cmds->commands;
+                cmds->ncommand = descr->cmds->ncommand;
+
+                if (MRP_UNLIKELY(descr->cmds->user_data != NULL))
+                    cmds->user_data = descr->cmds->user_data;
+                else
+                    cmds->user_data = plugin;
+            }
+            else {
+                mrp_log_error("Failed to allocate plugin commands.");
+                goto fail;
+            }
+        }
+
+
+        plugin->ctx        = ctx;
+        plugin->descriptor = descr;
+        plugin->handle     = handle;
+
+        mrp_refcnt_init(&plugin->refcnt);
+
+        if (!parse_plugin_args(plugin, args, narg))
+            goto fail;
+
+        if (plugin->cmds != NULL)
+            mrp_console_add_group(plugin->ctx, plugin->cmds);
+
+        if (!export_plugin_methods(plugin))
+            goto fail;
+
+        mrp_list_append(&ctx->plugins, &plugin->hook);
+
+        emit_plugin_event(PLUGIN_EVENT_LOADED, plugin);
+
+        if (ctx->state == MRP_STATE_STARTING || ctx->state == MRP_STATE_RUNNING)
+            mrp_start_plugin(plugin);
+
+        return plugin;
+    }
+    else {
+        mrp_log_error("Could not allocate plugin '%s'.", name);
+    fail:
+        mrp_unload_plugin(plugin);
+
+        return NULL;
+    }
+}
+
+
+static int load_plugin_cb(const char *file, mrp_dirent_type_t type, void *data)
+{
+    mrp_context_t *ctx = (mrp_context_t *)data;
+    char           name[PATH_MAX], *start, *end;
+    int            len;
+
+    MRP_UNUSED(type);
+
+    if ((start = strstr(file, PLUGIN_PREFIX)) != NULL) {
+        start += sizeof(PLUGIN_PREFIX) - 1;
+        if ((end = strstr(start, ".so")) != NULL) {
+            len = end - start;
+            strncpy(name, start, len);
+            name[len] = '\0';
+            mrp_load_plugin(ctx, name, NULL, NULL, 0);
+        }
+    }
+
+    return TRUE;
+}
+
+
+int mrp_load_all_plugins(mrp_context_t *ctx)
+{
+    mrp_dirent_type_t  type;
+    const char        *pattern;
+    mrp_list_hook_t   *p, *n;
+    mrp_plugin_t      *plugin;
+
+    type    = MRP_DIRENT_REG;
+    pattern = PLUGIN_PREFIX".*\\.so$";
+    mrp_scan_dir(ctx->plugin_dir, pattern, type, load_plugin_cb, ctx);
+
+    mrp_list_foreach(&builtin_plugins, p, n) {
+        plugin = mrp_list_entry(p, typeof(*plugin), hook);
+
+        mrp_load_plugin(ctx, plugin->descriptor->name, NULL, NULL, 0);
+    }
+
+    return TRUE;
+}
+
+
+int mrp_request_plugin(mrp_context_t *ctx, const char *name,
+                       const char *instance)
+{
+    mrp_plugin_t *plugin;
+
+    if (instance == NULL)
+        instance = name;
+
+    if ((plugin = find_plugin_instance(ctx, instance)) != NULL) {
+        if (instance == name || !strcmp(plugin->descriptor->name, name))
+            return TRUE;
+    }
+
+    return (mrp_load_plugin(ctx, name, instance, NULL, 0) != NULL);
+}
+
+
+int mrp_unload_plugin(mrp_plugin_t *plugin)
+{
+    mrp_plugin_arg_t *pa, *da, *ra;
+    int               i, j;
+
+    if (plugin != NULL) {
+        if (plugin->refcnt == 0) {
+            mrp_list_delete(&plugin->hook);
+
+            pa = plugin->args;
+            da = plugin->descriptor->args;
+
+            if (pa != da) {
+                for (i = 0; i < plugin->descriptor->narg; i++) {
+                    switch (pa->type) {
+                    case MRP_PLUGIN_ARG_TYPE_STRING:
+                        if (pa->str != da->str)
+                            mrp_free(pa->str);
+                        break;
+
+                    case MRP_PLUGIN_ARG_TYPE_OBJECT:
+                        mrp_json_unref(pa->obj.json);
+                        break;
+
+                    case MRP_PLUGIN_ARG_TYPE_UNDECL:
+                        ra = pa->rest.args;
+                        for (j = 0; j < pa->rest.narg; j++) {
+                            mrp_free(ra->key);
+                            switch (ra->type) {
+                            case MRP_PLUGIN_ARG_TYPE_STRING:
+                                mrp_free(ra->str);
+                                break;
+                            case MRP_PLUGIN_ARG_TYPE_OBJECT:
+                                mrp_json_unref(ra->obj.json);
+                                break;
+                            default:
+                                break;
+                            }
+                            ra++;
+                        }
+                        break;
+                    default:
+                        break;
+                    }
+
+                    pa++;
+                    da++;
+                }
+                mrp_free(plugin->args);
+            }
+
+            plugin->descriptor->ninstance--;
+
+            emit_plugin_event(PLUGIN_EVENT_UNLOADED, plugin);
+
+            if (plugin->handle != NULL)
+                dlclose(plugin->handle);
+
+            if (plugin->cmds != NULL) {
+                mrp_console_del_group(plugin->ctx, plugin->cmds);
+
+                mrp_free(plugin->cmds->name);
+                mrp_free(plugin->cmds);
+            }
+
+            release_plugin_methods(plugin);
+            remove_plugin_methods(plugin);
+
+            mrp_free(plugin->instance);
+            mrp_free(plugin->path);
+            mrp_free(plugin);
+
+            return TRUE;
+        }
+        else
+            return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+int mrp_start_plugins(mrp_context_t *ctx)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *plugin;
+
+    mrp_list_foreach(&ctx->plugins, p, n) {
+        plugin = mrp_list_entry(p, typeof(*plugin), hook);
+
+        if (!import_plugin_methods(plugin)) {
+            if (!plugin->may_fail)
+                return FALSE;
+            else
+                goto unload;
+        }
+
+        if (!mrp_start_plugin(plugin)) {
+            if (!plugin->may_fail)
+                return FALSE;
+            else {
+            unload:
+                mrp_unload_plugin(plugin);
+            }
+        }
+
+        /* XXX TODO: argh, ugly kludge for plugins loading plugins... */
+        if (plugin->hook.next != n)
+            n = plugin->hook.next;
+    }
+
+    return TRUE;
+}
+
+
+int mrp_start_plugin(mrp_plugin_t *plugin)
+{
+    if (plugin != NULL) {
+        if (plugin->state == MRP_PLUGIN_LOADED) {
+            if (!plugin->descriptor->init(plugin)) {
+                mrp_log_error("Failed to start plugin %s (%s).",
+                              plugin->instance, plugin->descriptor->name);
+
+                emit_plugin_event(PLUGIN_EVENT_FAILED, plugin);
+                return FALSE;
+            }
+            else {
+                plugin->state = MRP_PLUGIN_RUNNING;
+                emit_plugin_event(PLUGIN_EVENT_STARTED, plugin);
+            }
+        }
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_stop_plugin(mrp_plugin_t *plugin)
+{
+    if (plugin != NULL) {
+        if (plugin->refcnt <= 1) {
+            emit_plugin_event(PLUGIN_EVENT_STOPPING, plugin);
+            plugin->descriptor->exit(plugin);
+            plugin->refcnt = 0;
+            plugin->state = MRP_PLUGIN_STOPPED;
+            emit_plugin_event(PLUGIN_EVENT_STOPPED, plugin);
+
+            return TRUE;
+        }
+        else
+            return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+static mrp_plugin_t *find_plugin_instance(mrp_context_t *ctx,
+                                          const char *instance)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *plg;
+
+    MRP_UNUSED(find_plugin);
+
+    mrp_list_foreach(&ctx->plugins, p, n) {
+        plg = mrp_list_entry(p, typeof(*plg), hook);
+
+        if (!strcmp(plg->instance, instance))
+            return plg;
+    }
+
+    return NULL;
+}
+
+
+static mrp_plugin_t *find_plugin(mrp_context_t *ctx, char *name)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *plg;
+
+    mrp_list_foreach(&ctx->plugins, p, n) {
+        plg = mrp_list_entry(p, typeof(*plg), hook);
+
+        if (!strcmp(plg->descriptor->name, name))
+            return plg;
+    }
+
+    return NULL;
+}
+
+
+static mrp_plugin_descr_t *open_dynamic(mrp_context_t *ctx, const char *name,
+                                        void **handle)
+{
+    mrp_plugin_descr_t *(*describe)(void);
+    mrp_plugin_descr_t   *d;
+    void                 *h;
+    char                  path[PATH_MAX];
+
+    snprintf(path, sizeof(path), "%s/%s%s.so", ctx->plugin_dir,
+             PLUGIN_PREFIX, name);
+
+    if (is_blacklisted(ctx, name, DYNAMIC))
+        return NULL;
+
+    if ((h = dlopen(path, RTLD_LAZY | RTLD_LOCAL)) != NULL) {
+        if ((describe = dlsym(h, "mrp_get_plugin_descriptor")) != NULL) {
+            if ((d = describe()) != NULL) {
+                if (d->init != NULL && d->exit != NULL && d->name != NULL) {
+                    if (!d->core)
+                        *handle = h;
+                    else {
+                        *handle = dlopen(path,
+                                         RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD);
+                        dlclose(h);
+                    }
+
+                    return d;
+                }
+                else
+                    mrp_log_error("Ignoring plugin '%s' with invalid "
+                                  "plugin descriptor.", path);
+            }
+            else
+                mrp_log_error("Plugin '%s' provided NULL descriptor.", path);
+        }
+        else
+            mrp_log_error("Plugin '%s' does not provide a descriptor.", path);
+    }
+    else {
+        if (access(path, F_OK) == 0) {
+            char *err = dlerror();
+
+            mrp_log_error("Failed to dlopen plugin '%s' (%s).", path,
+                          err ? err : "unknown error");
+        }
+    }
+
+    if (h != NULL)
+        dlclose(h);
+
+    *handle = NULL;
+    return NULL;
+}
+
+
+static mrp_plugin_descr_t *open_builtin(mrp_context_t *ctx, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    mrp_plugin_t    *plugin;
+
+    mrp_list_foreach(&builtin_plugins, p, n) {
+        plugin = mrp_list_entry(p, typeof(*plugin), hook);
+
+        if (!strcmp(plugin->descriptor->name, name)) {
+#ifdef __PARANOID_BLACKLIST_CHECK__
+            if (is_blacklisted(ctx, name, BUILTIN)) {
+                mrp_log_warning("Hmm... blacklisted builtin %s still "
+                                "reachable, blocking it.", name);
+                return NULL;
+            }
+            else
+#endif
+                return plugin->descriptor;
+        }
+    }
+
+    return NULL;
+}
+
+
+static int parse_plugin_arg(mrp_plugin_arg_t *arg, mrp_plugin_arg_t *parg)
+{
+    char        *end;
+    mrp_json_t **json;
+    char        *jstr;
+    int          jlen;
+
+    switch (parg->type) {
+    case MRP_PLUGIN_ARG_TYPE_STRING:
+        if (arg->str != NULL) {
+            parg->str = arg->str;
+            arg->str  = NULL;
+        }
+        return TRUE;
+
+    case MRP_PLUGIN_ARG_TYPE_BOOL:
+        if (arg->str != NULL) {
+            if (!strcasecmp(arg->str, "TRUE"))
+                parg->bln = TRUE;
+            else if (!strcasecmp(arg->str, "FALSE"))
+                parg->bln = FALSE;
+            else
+                return FALSE;
+        }
+        else
+            parg->bln = TRUE;
+        return TRUE;
+
+    case MRP_PLUGIN_ARG_TYPE_UINT32:
+        parg->u32 = (uint32_t)strtoul(arg->str, &end, 0);
+        if (end && !*end)
+            return TRUE;
+        else
+            return FALSE;
+
+    case MRP_PLUGIN_ARG_TYPE_INT32:
+        parg->i32 = (int32_t)strtol(arg->str, &end, 0);
+        if (end && !*end)
+            return TRUE;
+        else
+            return FALSE;
+
+    case MRP_PLUGIN_ARG_TYPE_DOUBLE:
+        parg->dbl = strtod(arg->str, &end);
+        if (end && !*end)
+            return TRUE;
+        else
+            return FALSE;
+
+    case MRP_PLUGIN_ARG_TYPE_OBJECT:
+        jstr = arg->obj.str;
+        jlen = strlen(jstr);
+        json = &parg->obj.json;
+        if (mrp_json_parse_object(&jstr, &jlen, json) < 0 || jlen != 0)
+            return FALSE;
+        else
+            return TRUE;
+
+    default:
+        return FALSE;
+    }
+}
+
+
+static int parse_undeclared_arg(mrp_plugin_arg_t *arg, mrp_plugin_arg_t *pa)
+{
+    mrp_plugin_arg_t *a;
+    char             *value, *end;
+    int               prfx;
+
+    if (mrp_reallocz(pa->rest.args, pa->rest.narg, pa->rest.narg + 1)) {
+        a = pa->rest.args + pa->rest.narg++;
+        a->key = mrp_strdup(arg->key);
+
+        if (a->key == NULL)
+            return FALSE;
+
+        if (arg->str == NULL) {
+            a->type = MRP_PLUGIN_ARG_TYPE_STRING;
+            a->str  = NULL;
+
+            return TRUE;
+        }
+
+        if (!strncmp(arg->str, "string:", 7)) {
+            value = arg->str + 7;
+        string:
+            a->type = MRP_PLUGIN_ARG_TYPE_STRING;
+            a->str  = mrp_strdup(value);
+
+            if (a->str != NULL)
+                return TRUE;
+            else
+                return FALSE;
+        }
+        else if (!strncmp(arg->str, "bool:", 5)) {
+            a->type = MRP_PLUGIN_ARG_TYPE_BOOL;
+            if      (!strcasecmp(arg->str + 5, "TRUE"))  a->bln = TRUE;
+            else if (!strcasecmp(arg->str + 5, "FALSE")) a->bln = FALSE;
+            else                                         return FALSE;
+        }
+        else if (!strncmp(arg->str, "int32:", 6)) {
+            a->type = MRP_PLUGIN_ARG_TYPE_INT32;
+            a->i32  = (int32_t)strtol(arg->str + 6, &end, 0);
+
+            if (end && !*end)
+                return TRUE;
+            else
+                return FALSE;
+        }
+        else if (!strncmp(arg->str, "uint32:", 7)) {
+            a->type = MRP_PLUGIN_ARG_TYPE_UINT32;
+            a->u32  = (uint32_t)strtoul(arg->str + 7, &end, 0);
+
+            if (end && !*end)
+                return TRUE;
+            else
+                return FALSE;
+        }
+        else if (!strncmp(arg->str, "double:", 7)) {
+            a->type = MRP_PLUGIN_ARG_TYPE_DOUBLE;
+            a->dbl  = strtod(arg->str + 7, &end);
+
+            if (end && !*end)
+                return TRUE;
+            else
+                return FALSE;
+        }
+        else if (!strncmp(arg->str, "object:", prfx=7) ||
+                 !strncmp(arg->str, "json:"  , prfx=5)) {
+            mrp_json_t **json = &a->obj.json;
+            char        *jstr = a->obj.str + prfx;
+            int          jlen = strlen(jstr);
+
+            if (mrp_json_parse_object(&jstr, &jlen, json) < 0 || jlen != 0)
+                return FALSE;
+            else {
+                a->type = MRP_PLUGIN_ARG_TYPE_OBJECT;
+                return TRUE;
+            }
+        }
+        else {
+            if (!strcasecmp(arg->str, "TRUE") ||
+                !strcasecmp(arg->str, "FALSE")) {
+                a->type = MRP_PLUGIN_ARG_TYPE_BOOL;
+                a->bln  = arg->str[0] == 't' || arg->str[0] == 'T';
+
+                return TRUE;
+            }
+            if (arg->str[0] == '-' || arg->str[0] == '+' ||
+                (arg->str[0] == '0' && arg->str[1] == 'x') ||
+                ('0' <= arg->str[0] && arg->str[0] <= '9')) {
+                a->i32 = strtol(arg->str, &end, 0);
+
+                if (end && !*end) {
+                    a->type = MRP_PLUGIN_ARG_TYPE_INT32;
+                    return TRUE;
+                }
+                a->dbl = strtod(arg->str, &end);
+
+                if (end && !*end) {
+                    a->type = MRP_PLUGIN_ARG_TYPE_DOUBLE;
+                    return TRUE;
+                }
+            }
+            else {
+                value = arg->str;
+                goto string;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+static int parse_plugin_args(mrp_plugin_t *plugin,
+                             mrp_plugin_arg_t *argv, int argc)
+{
+    mrp_plugin_descr_t *descr;
+    mrp_plugin_arg_t   *valid, *args, *pa, *a, *rest;
+    int                 i, j, cnt;
+
+    if (argv == NULL) {
+        plugin->args = plugin->descriptor->args;
+        return TRUE;
+    }
+
+    descr = plugin->descriptor;
+    valid = descr->args;
+
+    if (valid == NULL && argv != NULL) {
+        mrp_log_error("Plugin '%s' (%s) does not take any arguments.",
+                      plugin->instance, descr->name);
+        return FALSE;
+    }
+
+    if ((args = mrp_allocz_array(typeof(*args), descr->narg)) == NULL) {
+        mrp_log_error("Failed to allocate arguments for plugin '%s'.",
+                      plugin->instance);
+        return FALSE;
+    }
+
+    memcpy(args, descr->args, descr->narg * sizeof(*args));
+    plugin->args = args;
+
+    for (i = 0, pa = plugin->args; i < descr->narg; i++, pa++) {
+        if (pa->type == MRP_PLUGIN_ARG_TYPE_OBJECT) {
+            mrp_json_t **json = &pa->obj.json;
+            char        *jstr = pa->obj.str;
+            int          jlen = strlen(jstr);
+
+            if (mrp_json_parse_object(&jstr, &jlen, json) < 0 || jlen != 0)
+                return FALSE;
+        }
+    }
+
+    rest = NULL;
+    j    = 0;
+    for (i = 0, a = argv; i < argc; i++, a++) {
+        for (cnt = 0, pa = NULL; pa == NULL && cnt < descr->narg; cnt++) {
+            if (args[j].type != MRP_PLUGIN_ARG_TYPE_UNDECL) {
+                if (!strcmp(a->key, args[j].key))
+                    pa = args + j;
+            }
+            else
+                rest = args + j;
+
+            if (++j >= descr->narg)
+                j = 0;
+        }
+
+        if (pa != NULL) {
+            if (!parse_plugin_arg(a, pa)) {
+                mrp_log_error("Invalid argument '%s' for plugin '%s'.",
+                              a->key, plugin->instance);
+                return FALSE;
+            }
+        }
+        else if (rest != NULL) {
+            if (!parse_undeclared_arg(a, rest)) {
+                mrp_log_error("Failed to parse argument '%s' for plugin '%s'.",
+                              a->key, plugin->instance);
+                return FALSE;
+            }
+        }
+        else {
+            mrp_log_error("Plugin '%s' (%s) does not support argument '%s'",
+                          plugin->instance, descr->name, a->key);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+mrp_plugin_arg_t *mrp_plugin_find_undecl_arg(mrp_plugin_arg_t *unspec,
+                                             const char *key,
+                                             mrp_plugin_arg_type_t type)
+{
+    mrp_plugin_arg_t *arg;
+    int               i;
+
+    for (i = 0, arg = unspec->rest.args; i < unspec->rest.narg; i++, arg++)
+        if (!strcmp(arg->key, key) && (!type || type == arg->type))
+            return arg;
+
+    return NULL;
+}
+
+
+static int export_plugin_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t *methods = plugin->descriptor->exports, *m;
+    int                 nmethod = plugin->descriptor->nexport, i;
+
+    for (i = 0, m = methods; i < nmethod; i++, m++) {
+        m->plugin = plugin;
+        if (mrp_export_method(m) != 0) {
+            mrp_log_error("Failed to export method %s from plugin %s.",
+                          m->name, plugin->instance);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+static int remove_plugin_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t *methods = plugin->descriptor->exports, *m;
+    int                 nmethod = plugin->descriptor->nexport, i;
+    int                 success = TRUE;
+
+    for (i = 0, m = methods; i < nmethod; i++, m++) {
+        m->plugin = plugin;
+        if (mrp_remove_method(m) != 0) {
+            mrp_log_error("Failed to remove exported method %s of plugin %s.",
+                          m->name, plugin->instance);
+            success = FALSE;
+        }
+    }
+
+    return success;
+}
+
+
+static int import_plugin_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t *methods = plugin->descriptor->imports, *m;
+    int                 nmethod = plugin->descriptor->nimport, i;
+
+    for (i = 0, m = methods; i < nmethod; i++, m++) {
+        if (mrp_import_method(m->name, m->signature,
+                              (void **)m->native_ptr, NULL, NULL) != 0) {
+            mrp_log_error("Failed to import method %s (%s) for plugin %s.",
+                          m->name, m->signature, plugin->instance);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+static int release_plugin_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t *methods = plugin->descriptor->imports, *m;
+    int                 nmethod = plugin->descriptor->nimport, i;
+    int                 success = TRUE;
+
+    for (i = 0, m = methods; i < nmethod; i++, m++) {
+        if (mrp_release_method(m->name, m->signature,
+                               (void **)m->native_ptr, NULL) != 0) {
+            mrp_log_error("Failed to release imported method %s of plugin %s.",
+                          m->name, plugin->instance);
+            success = FALSE;
+        }
+    }
+
+    return success;
+}
diff --git a/src/core/plugin.h b/src/core/plugin.h
new file mode 100644 (file)
index 0000000..a7e569c
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_PLUGIN_H__
+#define __MURPHY_PLUGIN_H__
+
+#include <stdbool.h>
+#include <dlfcn.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/refcnt.h>
+#include <murphy/common/json.h>
+#include <murphy/core/context.h>
+#include <murphy/core/console-command.h>
+
+typedef struct mrp_plugin_s mrp_plugin_t;
+
+#include <murphy/core/method.h>
+
+#ifndef MRP_DEFAULT_PLUGIN_DIR
+#    define MRP_DEFAULT_PLUGIN_DIR LIBDIR"/murphy/plugins"
+#endif
+
+#define MRP_PLUGIN_DESCRIPTOR "mrp_get_plugin_descriptor"
+
+
+/*
+ * names of plugin-related events we emit
+ */
+
+#define MRP_PLUGIN_BUS            "plugin-bus"
+#define MRP_PLUGIN_EVENT_LOADED   "plugin-loaded"
+#define MRP_PLUGIN_EVENT_STARTED  "plugin-started"
+#define MRP_PLUGIN_EVENT_FAILED   "plugin-failed"
+#define MRP_PLUGIN_EVENT_STOPPING "plugin-stopping"
+#define MRP_PLUGIN_EVENT_STOPPED  "plugin-stopped"
+#define MRP_PLUGIN_EVENT_UNLOADED "plugin-unloaded"
+
+
+/*
+ * event message data tags
+ */
+
+#define MRP_PLUGIN_TAG_PLUGIN     ((uint16_t)1)  /* plugin name string */
+#define MRP_PLUGIN_TAG_INSTANCE   ((uint16_t)2)  /* plugin instance string */
+
+
+/*
+ * plugin arguments
+ */
+
+typedef enum {
+    MRP_PLUGIN_ARG_TYPE_UNKNOWN = 0,
+    MRP_PLUGIN_ARG_TYPE_STRING,
+    MRP_PLUGIN_ARG_TYPE_BOOL,
+    MRP_PLUGIN_ARG_TYPE_UINT32,
+    MRP_PLUGIN_ARG_TYPE_INT32,
+    MRP_PLUGIN_ARG_TYPE_DOUBLE,
+    MRP_PLUGIN_ARG_TYPE_OBJECT,
+    MRP_PLUGIN_ARG_TYPE_UNDECL,
+    /* add more as needed */
+} mrp_plugin_arg_type_t;
+
+typedef struct mrp_plugin_arg_s mrp_plugin_arg_t;
+
+struct mrp_plugin_arg_s {
+    char                  *key;          /* plugin argument name */
+    mrp_plugin_arg_type_t  type;         /* plugin argument type */
+    union {                              /* default/supplied value */
+        char              *str;          /* string values */
+        bool               bln;          /* boolean values */
+        uint32_t           u32;          /* 32-bit unsigned values */
+        int32_t            i32;          /* 32-bit signed values */
+        double             dbl;          /* double prec. floating pt. values */
+        struct {                         /* a JSON object */
+            char       *str;
+            mrp_json_t *json;
+        } obj;
+        struct {                         /* other undeclared arguments */
+            mrp_plugin_arg_t *args;
+            int               narg;
+        } rest;
+    };
+};
+
+
+/** Macro for declaring a plugin argument table. */
+#define MRP_PLUGIN_ARGUMENTS(table, ...)     \
+    static mrp_plugin_arg_t table[] =        \
+        __VA_ARGS__                          \
+
+
+/** Convenience macros for setting up argument tables with type and defaults. */
+#define MRP_PLUGIN_ARG_STRING(name, defval)                                \
+    { key: name, type: MRP_PLUGIN_ARG_TYPE_STRING, { str: defval } }
+
+#define MRP_PLUGIN_ARG_BOOL(name, defval)                                  \
+    { key: name, type: MRP_PLUGIN_ARG_TYPE_BOOL  , { bln: !!defval } }
+
+#define MRP_PLUGIN_ARG_UINT32(name, defval)                                \
+    { key: name, type: MRP_PLUGIN_ARG_TYPE_UINT32, { u32: defval } }
+
+#define MRP_PLUGIN_ARG_INT32(name, defval)                                 \
+    { key: name, type: MRP_PLUGIN_ARG_TYPE_INT32 , { i32: defval } }
+
+#define MRP_PLUGIN_ARG_DOUBLE(name, defval)                                \
+    { key: name, type: MRP_PLUGIN_ARG_TYPE_DOUBLE, { dbl: defval } }
+
+#define MRP_PLUGIN_ARG_OBJECT(name, defval)                                \
+    { key: name, type: MRP_PLUGIN_ARG_TYPE_OBJECT,                         \
+            { obj: { str: defval, json: NULL } } }
+
+#define MRP_PLUGIN_ARG_UNDECL(name, defval)                                \
+    { key: "*", type: MRP_PLUGIN_ARG_TYPE_UNDECL,  { str: NULL } }
+
+/** Similar convenience macros for indexed argument access. */
+#define MRP_PLUGIN_ARGIDX_STRING(idx, name, defval) \
+    [idx] MRP_PLUGIN_ARG_STRING(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_STRING(idx, name, defval) \
+    [idx] MRP_PLUGIN_ARG_STRING(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_BOOL(idx, name, defval)   \
+    [idx] MRP_PLUGIN_ARG_BOOL(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_UINT32(idx, name, defval) \
+    [idx] MRP_PLUGIN_ARG_UINT32(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_INT32(idx, name, defval)  \
+    [idx] MRP_PLUGIN_ARG_INT32(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_DOUBLE(idx, name, defval) \
+    [idx] MRP_PLUGIN_ARG_DOUBLE(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_OBJECT(idx, name, defval) \
+    [idx] MRP_PLUGIN_ARG_OBJECT(name, defval)
+
+#define MRP_PLUGIN_ARGIDX_UNDECL(idx, name, defval) \
+    [idx] MRP_PLUGIN_ARG_UNDECL(name, defval)
+
+/** Macro for looping through all collected undeclared arguments. */
+#define mrp_plugin_foreach_undecl_arg(_undecl, _arg)                    \
+    for ((_arg) = (_undecl)->rest.args;                                 \
+         (_arg) - (_undecl)->rest.args < (_undecl)->rest.narg;          \
+         (_arg)++)
+
+
+/**
+ * Generic convenience macro for indexed argument access.
+ *
+ * Here is how you can use these macros to declare and access your plugin
+ * arguments:
+ *
+ * #define TEST_HELP "Just a stupid test..."
+ * #define TEST_DESCRIPTION "A test plugin."
+ * #define TEST_AUTHORS "D. Duck <donald.duck@ducksburg.org>"
+ *
+ * enum {
+ *     ARG_FOO,
+ *     ARG_BAR,
+ *     ARG_FOOBAR,
+ *     ARG_BARFOO
+ * };
+ *
+ * mrp_plugin_arg_t test_args[] = {
+ *     MRP_PLUGIN_ARGIDX(ARG_FOO   , STRING, "foo"   , "default foo"),
+ *     MRP_PLUGIN_ARGIDX(ARG_BAR   , BOOL  , "bar"   , FALSE        ),
+ *     MRP_PLUGIN_ARGIDX(ARG_FOOBAR, UINT32, "foobar", 1984         ),
+ *     MRP_PLUGIN_ARGIDX(ARG_BARFOO, DOUBLE, "barfoo", 3.141        ),
+ * };
+ *
+ * static int test_init(mrp_plugin_t *plugin)
+ * {
+ *     mrp_plugin_arg_t *args = plugin->args;
+ *
+ *     if (args[ARG_BAR].bln) {
+ *         mrp_log_info("   foo: %s", args[ARG_FOO].str);
+ *         mrp_log_info("foobar: %u", args[ARG_FOOBAR].u32);
+ *         mrp_log_info("barfoo: %f", args[ARG_BARFOO].dbl);
+ *     }
+ *     else
+ *         mrp_log_info("I was not asked to dump my arguments...");
+ *     ...
+ * }
+ *
+ * MURPHY_REGISTER_PLUGIN("test", TEST_DESCRIPTION, TEST_AUTHORS, TEST_HELP,
+ *                        MRP_MULTIPLE, test_init, test_exit, test_args);
+ */
+
+#define MRP_PLUGIN_ARGIDX(idx, type, name, defval) \
+    [idx] MRP_PLUGIN_ARG_##type(name, defval)
+
+
+/*
+ * plugin API version
+ */
+
+#define MRP_PLUGIN_API_MAJOR 0
+#define MRP_PLUGIN_API_MINOR 1
+
+#define MRP_PLUGIN_API_VERSION \
+    MRP_VERSION_INT(MRP_PLUGIN_API_MAJOR, MRP_PLUGIN_API_MINOR, 0)
+
+#define MRP_PLUGIN_API_VERSION_STRING \
+    MRP_VERSION_STRING(MRP_PLUGIN_API_MAJOR, MRP_PLUGIN_API_MINOR, 0)
+
+
+/*
+ * plugin descriptors
+ */
+
+
+typedef int  (*mrp_plugin_init_t)(mrp_plugin_t *);
+typedef void (*mrp_plugin_exit_t)(mrp_plugin_t *);
+
+#define MRP_SINGLETON TRUE
+#define MRP_MULTIPLE  FALSE
+
+typedef struct {
+    char                *name;                 /* plugin name */
+    char                *path;                 /* plugin path */
+    mrp_plugin_init_t    init;                 /* initialize plugin */
+    mrp_plugin_exit_t    exit;                 /* cleanup plugin */
+    mrp_plugin_arg_t    *args;                 /* table of valid arguments */
+    int                  narg;                 /* number of valid arguments */
+    int                  core : 1;             /* is this a core plugin? */
+    int                  singleton : 1;        /* deny multiple instances? */
+    int                  ninstance;            /* number of instances */
+    /* miscallaneous plugin metadata */
+    int                  version;              /* plugin version */
+    int                  mrp_version;          /* murphy API version */
+    const char          *description;          /* plugin description */
+    const char          *authors;              /* plugin authors */
+    const char          *help;                 /* plugin help string */
+    mrp_console_group_t *cmds;                 /* default console commands */
+    mrp_method_descr_t  *exports;              /* exported methods */
+    int                  nexport;              /* number of exported methods */
+    mrp_method_descr_t  *imports;              /* imported methods */
+    int                  nimport;              /* number of imported methods */
+} mrp_plugin_descr_t;
+
+
+/*
+ * plugins
+ */
+
+typedef enum {
+    MRP_PLUGIN_LOADED = 0,                     /* has been loaded */
+    MRP_PLUGIN_RUNNING,                        /* has been started */
+    MRP_PLUGIN_STOPPED,                        /* has been stopped */
+} mrp_plugin_state_t;
+
+struct mrp_plugin_s {
+    char                *path;                 /* plugin path */
+    char                *instance;             /* plugin instance */
+    mrp_list_hook_t      hook;                 /* hook to list of plugins */
+    mrp_context_t       *ctx;                  /* murphy context */
+    mrp_plugin_descr_t  *descriptor;           /* plugin descriptor */
+    void                *handle;               /* DSO handle */
+    mrp_plugin_state_t   state;                /* plugin state */
+    mrp_refcnt_t         refcnt;               /* reference count */
+    void                *data;                 /* private plugin data */
+    mrp_plugin_arg_t    *args;                 /* plugin arguments */
+    mrp_console_group_t *cmds;                 /* default console commands */
+    int                  may_fail : 1;         /* load / start may fail */
+};
+
+
+#ifdef __MURPHY_BUILTIN_PLUGIN__
+/*   statically linked in plugins */
+#    define __MURPHY_REGISTER_PLUGIN(_name,                               \
+                                     _version,                            \
+                                     _description,                        \
+                                     _authors,                            \
+                                     _help,                               \
+                                     _core,                               \
+                                     _single,                             \
+                                     _init,                               \
+                                     _exit,                               \
+                                     _args,                               \
+                                     _narg,                               \
+                                     _exports,                            \
+                                     _nexport,                            \
+                                     _imports,                            \
+                                     _nimport,                            \
+                                     _cmds)                               \
+                                                                          \
+    static void register_plugin(void) __attribute__((constructor));       \
+                                                                          \
+    static void register_plugin(void) {                                   \
+        char *path = __FILE__, *base;                                     \
+        static mrp_plugin_descr_t descriptor = {                          \
+            .name        = _name,                                         \
+            .version     = _version,                                      \
+            .description = _description,                                  \
+            .authors     = _authors,                                      \
+            .mrp_version = MRP_PLUGIN_API_VERSION,                        \
+            .help        = _help,                                         \
+            .init        = _init,                                         \
+            .exit        = _exit,                                         \
+            .core        = _core,                                         \
+            .singleton   = _single,                                       \
+            .ninstance   = 0,                                             \
+            .args        = _args,                                         \
+            .narg        = _narg,                                         \
+            .cmds        = _cmds,                                         \
+            .exports     = _exports,                                      \
+            .nexport     = _nexport,                                      \
+            .imports     = _imports,                                      \
+            .nimport     = _nimport,                                      \
+        };                                                                \
+                                                                          \
+        if ((base = strrchr(path, '/')) != NULL)                          \
+            descriptor.path = base + 1;                                   \
+        else                                                              \
+            descriptor.path = (char *)path;                               \
+                                                                          \
+        mrp_register_builtin_plugin(&descriptor);                         \
+    }                                                                     \
+    struct mrp_allow_trailing_semicolon
+#else /* dynamically loaded plugins */
+#    define __MURPHY_REGISTER_PLUGIN(_name,                               \
+                                     _version,                            \
+                                     _description,                        \
+                                     _authors,                            \
+                                     _help,                               \
+                                     _core,                               \
+                                     _single,                             \
+                                     _init,                               \
+                                     _exit,                               \
+                                     _args,                               \
+                                     _narg,                               \
+                                     _exports,                            \
+                                     _nexport,                            \
+                                     _imports,                            \
+                                     _nimport,                            \
+                                     _cmds)                               \
+                                                                          \
+    mrp_plugin_descr_t *mrp_get_plugin_descriptor(void) {                 \
+        static mrp_plugin_descr_t descriptor = {                          \
+            .name        = _name,                                         \
+            .version     = _version,                                      \
+            .description = _description,                                  \
+            .authors     = _authors,                                      \
+            .mrp_version = MRP_PLUGIN_API_VERSION,                        \
+            .help        = _help,                                         \
+            .init        = _init,                                         \
+            .exit        = _exit,                                         \
+            .core        = _core,                                         \
+            .singleton   = _single,                                       \
+            .ninstance   = 0,                                             \
+            .args        = _args,                                         \
+            .narg        = _narg,                                         \
+            .cmds        = _cmds,                                         \
+            .exports     = _exports,                                      \
+            .nexport     = _nexport,                                      \
+            .imports     = _imports,                                      \
+            .nimport     = _nimport,                                      \
+        };                                                                \
+                                                                          \
+        return &descriptor;                                               \
+    }                                                                     \
+    struct mrp_allow_trailing_semicolon
+#endif
+
+
+#define MURPHY_REGISTER_PLUGIN(_n, _v, _d, _a, _h, _s, _i, _e,          \
+                               _args, _narg,                            \
+                               _exports, _nexport,                      \
+                               _imports, _nimport,                      \
+                               _cmds)                                   \
+    __MURPHY_REGISTER_PLUGIN(_n, _v, _d, _a, _h, FALSE, _s, _i, _e,     \
+                             _args, _narg,                              \
+                             _exports, _nexport,                        \
+                             _imports, _nimport,                        \
+                             _cmds)
+
+#define MURPHY_REGISTER_CORE_PLUGIN(_n, _v, _d, _a, _h, _s, _i, _e,     \
+                                    _args, _narg,                       \
+                                    _exports, _nexport,                 \
+                                    _imports, _nimport,                 \
+                                    _cmds)                              \
+    __MURPHY_REGISTER_PLUGIN(_n, _v, _d, _a, _h, TRUE, _s, _i, _e,      \
+                             _args, _narg,                              \
+                             _exports, _nexport,                        \
+                             _imports, _nimport,                        \
+                             _cmds)
+
+#define MRP_REGISTER_PLUGIN MURPHY_REGISTER_PLUGIN
+#define MRP_REGISTER_CORE_PLUGIN MURPHY_REGISTER_CORE_PLUGIN
+
+
+int mrp_register_builtin_plugin(mrp_plugin_descr_t *descr);
+int mrp_plugin_exists(mrp_context_t *ctx, const char *name);
+mrp_plugin_t *mrp_load_plugin(mrp_context_t *ctx, const char *name,
+                              const char *instance, mrp_plugin_arg_t *args,
+                              int narg);
+int mrp_load_all_plugins(mrp_context_t *ctx);
+int mrp_unload_plugin(mrp_plugin_t *plugin);
+int mrp_plugin_loaded(mrp_context_t *ctx, const char *name);
+int mrp_start_plugins(mrp_context_t *ctx);
+int mrp_start_plugin(mrp_plugin_t *plugin);
+int mrp_plugin_running(mrp_context_t *ctx, const char *name);
+int mrp_stop_plugin(mrp_plugin_t *plugin);
+int mrp_request_plugin(mrp_context_t *ctx, const char *name,
+                       const char *instance);
+void mrp_block_blacklisted_plugins(mrp_context_t *ctx);
+
+mrp_plugin_arg_t *mrp_plugin_find_undecl_arg(mrp_plugin_arg_t *undecl,
+                                             const char *key,
+                                             mrp_plugin_arg_type_t type);
+
+
+static inline mrp_plugin_t *mrp_ref_plugin(mrp_plugin_t *plugin)
+{
+    return mrp_ref_obj(plugin, refcnt);
+}
+
+
+static inline int mrp_unref_plugin(mrp_plugin_t *plugin)
+{
+    return mrp_unref_obj(plugin, refcnt);
+}
+
+
+#endif /* __MURPHY_PLUGIN_H__ */
diff --git a/src/core/scripting.c b/src/core/scripting.c
new file mode 100644 (file)
index 0000000..c99b6c9
--- /dev/null
@@ -0,0 +1,523 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+
+#include <murphy/core/scripting.h>
+
+static MRP_LIST_HOOK(interpreters);                /* registered interpreters */
+static const char *default_interpreter = "simple"; /* the default interpreter */
+
+
+/*
+ * a context variable
+ */
+
+typedef struct {
+    const char        *name;             /* variable name */
+    mrp_script_type_t  type;             /* type if declared */
+    int                id;               /* variable id */
+} context_var_t;
+
+
+/*
+ * a context frame (a set of context variable values)
+ */
+
+typedef struct context_value_s context_value_t;
+struct context_value_s {
+    int                 id;              /* variable id */
+    mrp_script_value_t  value;           /* value for this variable */
+    context_value_t    *next;            /* next value in this frame */
+};
+
+typedef struct context_frame_s context_frame_t;
+struct context_frame_s {
+    context_value_t *values;             /* hook to more value */
+    context_frame_t *prev;               /* previous frame */
+};
+
+
+/*
+ * table of context variables and context frames
+ */
+
+struct mrp_context_tbl_s {
+    context_var_t   *variables;          /* known/declared context variables */
+    int              nvariable;          /* number of variables */
+    mrp_htbl_t      *names;              /* variable name to id mapping */
+    context_frame_t *frame;              /* active frame */
+};
+
+
+
+int mrp_register_interpreter(mrp_interpreter_t *i)
+{
+    MRP_UNUSED(default_interpreter);
+
+    mrp_list_init(&i->hook);
+    mrp_list_append(&interpreters, &i->hook);
+
+    return TRUE;
+}
+
+
+static void unregister_interpreter(mrp_interpreter_t *i)
+{
+    mrp_list_delete(&i->hook);
+    mrp_list_init(&i->hook);
+}
+
+
+int mrp_unregister_interpreter(const char *name)
+{
+    mrp_interpreter_t *i;
+
+    i = mrp_lookup_interpreter(name);
+
+    if (i != NULL) {
+        unregister_interpreter(i);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+
+}
+
+
+mrp_interpreter_t *mrp_lookup_interpreter(const char *name)
+{
+    mrp_list_hook_t   *p, *n;
+    mrp_interpreter_t *i;
+
+    mrp_list_foreach(&interpreters, p, n) {
+        i = mrp_list_entry(p, typeof(*i), hook);
+        if (!strcmp(i->name, name))
+            return i;
+    }
+
+    return NULL;
+}
+
+
+mrp_scriptlet_t *mrp_create_script(const char *type, const char *source)
+{
+    mrp_interpreter_t *i;
+    mrp_scriptlet_t   *s;
+
+    s = NULL;
+    i = mrp_lookup_interpreter(type);
+
+    if (i != NULL) {
+        s = mrp_allocz(sizeof(*s));
+
+        if (s != NULL) {
+            s->interpreter = i;
+            s->source      = mrp_strdup(source);
+
+            if (s->source != NULL)
+                return s;
+            else {
+                mrp_free(s);
+                s = NULL;
+            }
+        }
+    }
+    else
+        errno = ENOENT;
+
+    return NULL;
+}
+
+
+void mrp_destroy_script(mrp_scriptlet_t *script)
+{
+    if (script != NULL) {
+        if (script->interpreter && script->interpreter->cleanup)
+            script->interpreter->cleanup(script);
+
+        mrp_free(script->source);
+        mrp_free(script);
+    }
+}
+
+
+int mrp_compile_script(mrp_scriptlet_t *s)
+{
+    if (s != NULL)
+        return s->interpreter->compile(s);
+    else
+        return 0;
+}
+
+
+int mrp_prepare_script(mrp_scriptlet_t *s)
+{
+    if (s != NULL && s->interpreter->prepare != NULL)
+        return s->interpreter->prepare(s);
+    else
+        return 0;
+}
+
+
+int mrp_execute_script(mrp_scriptlet_t *s, mrp_context_tbl_t *ctbl)
+{
+    if (s != NULL)
+        return s->interpreter->execute(s, ctbl);
+    else
+        return TRUE;
+}
+
+
+char *mrp_print_value(char *buf, size_t size, mrp_script_value_t *value)
+{
+#define HANDLE_TYPE(type, fmt, val)                     \
+        case MRP_SCRIPT_TYPE_##type:                    \
+            snprintf(buf, size, fmt, val);              \
+            break
+
+    switch (value->type) {
+        HANDLE_TYPE(UNKNOWN, "%s"     , "<unknown/invalid type>");
+        HANDLE_TYPE(STRING , "'%s'"   , value->str);
+        HANDLE_TYPE(BOOL   , "%s"     , value->bln ? "true" : "false");
+        HANDLE_TYPE(UINT8  , "%uU8"   , value->u8);
+        HANDLE_TYPE(SINT8  , "%dS8"   , value->s8);
+        HANDLE_TYPE(UINT16 , "%uU16"  , value->u16);
+        HANDLE_TYPE(SINT16 , "%dS16"  , value->s16);
+        HANDLE_TYPE(UINT32 , "%uU32"  , value->u32);
+        HANDLE_TYPE(SINT32 , "%dS32"  , value->s32);
+        HANDLE_TYPE(UINT64 , "%lluU64", (unsigned long long)value->u64);
+        HANDLE_TYPE(SINT64 , "%lldS64", (  signed long long)value->s64);
+        HANDLE_TYPE(DOUBLE , "%f"     , value->dbl);
+    default:
+        snprintf(buf, size, "<invalid type 0x%x>", value->type);
+    }
+
+#undef HANDLE_TYPE
+
+    return buf;
+}
+
+
+mrp_context_tbl_t *mrp_create_context_table(void)
+{
+    mrp_context_tbl_t *tbl;
+    mrp_htbl_config_t  hcfg;
+
+    tbl = mrp_allocz(sizeof(*tbl));
+
+    if (tbl != NULL) {
+        mrp_clear(&hcfg);
+        hcfg.comp = mrp_string_comp;
+        hcfg.hash = mrp_string_hash;
+        hcfg.free = NULL;
+
+        tbl->frame = NULL;
+        tbl->names = mrp_htbl_create(&hcfg);
+
+        if (tbl->names != NULL)
+            return tbl;
+
+        mrp_free(tbl);
+    }
+
+    return NULL;
+}
+
+
+void mrp_destroy_context_table(mrp_context_tbl_t *tbl)
+{
+    if (tbl != NULL) {
+        while (mrp_pop_context_frame(tbl) == 0)
+            ;
+
+        mrp_htbl_destroy(tbl->names, FALSE);
+        mrp_free(tbl);
+    }
+}
+
+
+static context_var_t *lookup_context_var(mrp_context_tbl_t *tbl,
+                                         const char *name)
+{
+    int id;
+
+    id = (int)(ptrdiff_t)mrp_htbl_lookup(tbl->names, (void *)name);
+
+    if (0 < id && id <= tbl->nvariable)
+        return tbl->variables + id - 1;
+    else
+        return NULL;
+}
+
+
+int mrp_declare_context_variable(mrp_context_tbl_t *tbl, const char *name,
+                                 mrp_script_type_t type)
+{
+    context_var_t *var;
+
+    var = lookup_context_var(tbl, name);
+
+    if (var != NULL) {
+        if (!var->type) {
+            var->type = type;
+            return var->id;
+        }
+
+        if (!type || var->type == type)
+            return var->id;
+
+        errno = EEXIST;
+        return -1;
+    }
+    else {
+        size_t o, n;
+
+        o = sizeof(*tbl->variables) * tbl->nvariable;
+        n = sizeof(*tbl->variables) * (tbl->nvariable + 1);
+
+        if (!mrp_reallocz(tbl->variables, o, n))
+            return -1;
+
+        var = tbl->variables + tbl->nvariable++;
+
+        var->name = mrp_strdup(name);
+        var->type = type;
+        var->id   = tbl->nvariable;        /* this is a 1-based index... */
+
+        if (var->name != NULL) {
+            if (mrp_htbl_insert(tbl->names, (void *)var->name,
+                                (void *)(ptrdiff_t)var->id))
+                return var->id;
+        }
+
+        return -1;
+    }
+}
+
+
+int mrp_push_context_frame(mrp_context_tbl_t *tbl)
+{
+    context_frame_t *f;
+
+    f = mrp_allocz(sizeof(*f));
+
+    if (f != NULL) {
+        f->values  = NULL;
+        f->prev    = tbl->frame;
+        tbl->frame = f;
+
+        mrp_debug("pushed new context frame...");
+
+        return 0;
+    }
+    else
+        return -1;
+}
+
+
+int mrp_pop_context_frame(mrp_context_tbl_t *tbl)
+{
+    context_frame_t *f;
+    context_value_t *v, *n;
+
+    f = tbl->frame;
+
+    if (f != NULL) {
+        for (v = f->values; v != NULL; v = n) {
+            n = v->next;
+
+            if (v->value.type == MRP_SCRIPT_TYPE_STRING)
+                mrp_free(v->value.str);
+
+            mrp_debug("popped variable <%d>", v->id);
+            mrp_free(v);
+        }
+
+        tbl->frame = f->prev;
+        mrp_free(f);
+
+        mrp_debug("popped context frame");
+
+        return 0;
+    }
+    else {
+        errno = ENOENT;
+        return -1;
+    }
+}
+
+
+int get_context_id(mrp_context_tbl_t *tbl, const char *name)
+{
+    return (int)(ptrdiff_t)mrp_htbl_lookup(tbl->names, (void *)name);
+}
+
+
+int get_context_value(mrp_context_tbl_t *tbl, int id, mrp_script_value_t *value)
+{
+    context_frame_t *f;
+    context_value_t *v;
+
+    if (0 < id && id <= tbl->nvariable) {
+        for (f = tbl->frame; f != NULL; f = f->prev) {
+            for (v = f->values; v != NULL; v = v->next) {
+                if (v->id == id) {
+                    *value = v->value;
+                    return 0;
+                }
+            }
+        }
+    }
+
+    value->type = MRP_SCRIPT_TYPE_INVALID;
+    errno       = ENOENT;
+
+    return -1;
+}
+
+
+int set_context_value(mrp_context_tbl_t *tbl, int id, mrp_script_value_t *value)
+{
+    context_frame_t *f;
+    context_var_t   *var;
+    context_value_t *val;
+    char             vbuf[64];
+
+    if (!(0 < id && id <= tbl->nvariable)) {
+        errno = ENOENT;
+        return -1;
+    }
+
+    var = tbl->variables + id - 1;
+    if (var->type != MRP_SCRIPT_TYPE_INVALID && var->type != value->type) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    f = tbl->frame;
+    if (f != NULL) {
+        val = mrp_allocz(sizeof(*val));
+
+        if (val != NULL) {
+            val->id    = id;
+            val->value = *value;
+
+            if (val->value.type != MRP_SCRIPT_TYPE_STRING ||
+                ((val->value.str = mrp_strdup(val->value.str)) != NULL)) {
+                val->next = f->values;
+                f->values = val;
+
+                mrp_debug("set &%s=%s", var->name,
+                          mrp_print_value(vbuf, sizeof(vbuf), value));
+
+                return 0;
+            }
+            else
+                mrp_free(val);
+        }
+    }
+    else
+        errno = ENOSPC;
+
+    return -1;
+}
+
+
+int set_context_values(mrp_context_tbl_t *tbl, int *ids,
+                       mrp_script_value_t *values, int nid)
+{
+    int i;
+
+    for (i = 0; i < nid; i++) {
+        if (set_context_value(tbl, ids[i], values + i) < 0)
+            return -1;
+    }
+
+    return 0;
+}
+
+
+int mrp_get_context_id(mrp_context_tbl_t *tbl, const char *name)
+{
+    int id;
+
+    id = get_context_id(tbl, name);
+
+    if (id <= 0)
+        id = mrp_declare_context_variable(tbl, name, MRP_SCRIPT_TYPE_UNKNOWN);
+
+    return id;
+}
+
+int mrp_get_context_value(mrp_context_tbl_t *tbl, int id,
+                          mrp_script_value_t *value)
+{
+    return get_context_value(tbl, id, value);
+
+}
+
+int mrp_set_context_value(mrp_context_tbl_t *tbl, int id,
+                          mrp_script_value_t *value)
+{
+    return set_context_value(tbl, id, value);
+}
+
+
+int mrp_get_context_value_by_name(mrp_context_tbl_t *tbl, const char *name,
+                                  mrp_script_value_t *value)
+{
+    return get_context_value(tbl, get_context_id(tbl, name), value);
+}
+
+
+int mrp_set_context_value_by_name(mrp_context_tbl_t *tbl, const char *name,
+                                  mrp_script_value_t *value)
+{
+    int id;
+
+    id = get_context_id(tbl, name);
+
+    if (id <= 0)            /* auto-declare as an untyped variable */
+        id = mrp_declare_context_variable(tbl, name, MRP_SCRIPT_TYPE_UNKNOWN);
+
+    return set_context_value(tbl, id, value);
+}
diff --git a/src/core/scripting.h b/src/core/scripting.h
new file mode 100644 (file)
index 0000000..6b85ebf
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CORE_SCRIPTING_H__
+#define __MURPHY_CORE_SCRIPTING_H__
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/list.h>
+#include <murphy/common/log.h>
+
+
+MRP_CDECL_BEGIN
+
+typedef struct mrp_interpreter_s  mrp_interpreter_t;
+typedef struct mrp_scriptlet_s    mrp_scriptlet_t;
+typedef struct mrp_context_tbl_s  mrp_context_tbl_t;
+typedef struct mrp_script_value_s mrp_script_value_t;
+
+
+/*
+ * call/execution context passed to exported boilerplate methods
+ *
+ * This context is used to pass positional and keyword arguments
+ * when calling exported scripting boilerplate methods. For instance
+ * the primitive resolver scriptlet interpreter uses this to execute
+ * function calls.
+ */
+
+typedef struct {
+    mrp_script_value_t *args;            /* positional arguments */
+    int                 narg;            /* number of arguments */
+    mrp_context_tbl_t  *ctbl;            /* named arguments */
+} mrp_script_env_t;
+
+
+/*
+ * supported data types to pass to/from scripts (XXX TODO: arrays...)
+ */
+
+#define A(t) MRP_SCRIPT_TYPE_##t
+typedef enum {
+    MRP_SCRIPT_TYPE_UNKNOWN = 0x00,
+    MRP_SCRIPT_TYPE_INVALID = 0x00,      /* defined invalid type */
+    MRP_SCRIPT_TYPE_STRING  = 0x01,      /* string */
+    MRP_SCRIPT_TYPE_BOOL    = 0x02,      /* boolean */
+    MRP_SCRIPT_TYPE_UINT8   = 0x03,      /* signed 8-bit integer */
+    MRP_SCRIPT_TYPE_SINT8   = 0x04,      /* unsigned 8-bit integer */
+    MRP_SCRIPT_TYPE_INT8    = A(SINT8),  /* alias for SINT8 */
+    MRP_SCRIPT_TYPE_UINT16  = 0x05,      /* unsigned 16-bit integer */
+    MRP_SCRIPT_TYPE_SINT16  = 0x06,      /* signed 16-bit integer */
+    MRP_SCRIPT_TYPE_INT16   = A(SINT16), /* alias for SINT16 */
+    MRP_SCRIPT_TYPE_UINT32  = 0x07,      /* unsigned 32-bit integer */
+    MRP_SCRIPT_TYPE_SINT32  = 0x08,      /* signed 32-bit integer */
+    MRP_SCRIPT_TYPE_INT32   = A(SINT32), /* alias for SINT32 */
+    MRP_SCRIPT_TYPE_UINT64  = 0x09,      /* unsigned 64-bit integer */
+    MRP_SCRIPT_TYPE_SINT64  = 0x0a,      /* signed 64-bit integer */
+    MRP_SCRIPT_TYPE_INT64   = A(SINT64), /* alias for SINT64 */
+    MRP_SCRIPT_TYPE_DOUBLE  = 0x0b,      /* double-prec. floating point */
+    MRP_SCRIPT_TYPE_ARRAY   = 0x80,      /* type/marker for arrays */
+} mrp_script_type_t;
+#undef A
+
+#define MRP_SCRIPT_VALUE_UNION union {          \
+        char      *str;                         \
+        bool       bln;                         \
+        uint8_t    u8;                          \
+        int8_t     s8;                          \
+        uint16_t   u16;                         \
+        int16_t    s16;                         \
+        uint32_t   u32;                         \
+        int32_t    s32;                         \
+        uint64_t   u64;                         \
+        int64_t    s64;                         \
+        double     dbl;                         \
+    }
+
+typedef MRP_SCRIPT_VALUE_UNION mrp_script_value_u;
+
+struct mrp_script_value_s {
+    mrp_script_type_t type;
+    MRP_SCRIPT_VALUE_UNION;
+};
+
+/** Helper macros for passing values to variadic arglist functions. */
+#define MRP_SCRIPT_STRING(s) MRP_SCRIPT_TYPE_STRING, s
+#define MRP_SCRIPT_BOOL(b)   MRP_SCRIPT_TYPE_BOOL  , b
+#define MRP_SCRIPT_UINT8(u)  MRP_SCRIPT_TYPE_UINT8 , u
+#define MRP_SCRIPT_SINT8(s)  MRP_SCRIPT_TYPE_SINT8 , s
+#define MRP_SCRIPT_UINT16(u) MRP_SCRIPT_TYPE_UINT16, u
+#define MRP_SCRIPT_SINT16(s) MRP_SCRIPT_TYPE_SINT16, s
+#define MRP_SCRIPT_UINT32(u) MRP_SCRIPT_TYPE_UINT32, u
+#define MRP_SCRIPT_SINT32(s) MRP_SCRIPT_TYPE_SINT32, s
+#define MRP_SCRIPT_UINT64(u) MRP_SCRIPT_TYPE_UINT64, u
+#define MRP_SCRIPT_DOUBLE(d) MRP_SCRIPT_TYPE_DOUBLE, d
+
+/** Helper macro for initializing/assigning to value arrays. */
+#define __MRP_SCRIPT_VALUE(_t, _m, _v) \
+    (mrp_script_value_t){ .type = MRP_SCRIPT_TYPE_##_t, ._m = _v }
+
+#define MRP_SCRIPT_VALUE_STRING(v) __MRP_SCRIPT_VALUE(STRING, str, v)
+#define MRP_SCRIPT_VALUE_BOOL(v)   __MRP_SCRIPT_VALUE(BOOL  , bln, v)
+#define MRP_SCRIPT_VALUE_UINT8(v)  __MRP_SCRIPT_VALUE(UINT8 , u8 , v)
+#define MRP_SCRIPT_VALUE_SINT8(v)  __MRP_SCRIPT_VALUE(SINT8 , s8 , v)
+#define MRP_SCRIPT_VALUE_UINT16(v) __MRP_SCRIPT_VALUE(UINT16, u16, v)
+#define MRP_SCRIPT_VALUE_SINT16(v) __MRP_SCRIPT_VALUE(SINT16, s16, v)
+#define MRP_SCRIPT_VALUE_UINT32(v) __MRP_SCRIPT_VALUE(UINT32, u32, v)
+#define MRP_SCRIPT_VALUE_SINT32(v) __MRP_SCRIPT_VALUE(SINT32, s32, v)
+#define MRP_SCRIPT_VALUE_UINT64(v) __MRP_SCRIPT_VALUE(UINT64, u64, v)
+#define MRP_SCRIPT_VALUE_SINT64(v) __MRP_SCRIPT_VALUE(SINT64, s64, v)
+#define MRP_SCRIPT_VALUE_DOUBLE(v) __MRP_SCRIPT_VALUE(DOUBLE, dbl, v)
+
+/** Print the given value to the given buffer. */
+char *mrp_print_value(char *buf, size_t size, mrp_script_value_t *value);
+
+
+/*
+ * a script interpreter as exposed to the resolver
+ */
+
+struct mrp_interpreter_s {
+    mrp_list_hook_t    hook;             /* to list of interpreters */
+    const char        *name;             /* interpreter identifier */
+    void              *data;             /* opaque global interpreter data */
+    /*                                      interpreter operations */
+    int  (*compile)(mrp_scriptlet_t *script);
+    int  (*prepare)(mrp_scriptlet_t *script);
+    int  (*execute)(mrp_scriptlet_t *script, mrp_context_tbl_t *ctbl);
+    void (*cleanup)(mrp_scriptlet_t *script);
+};
+
+/** Macro to automatically register an interpreter on startup. */
+#define MRP_REGISTER_INTERPRETER(_type, _compile, _prepare, _execute,   \
+                                 _cleanup)                              \
+    static void auto_register_interpreter(void)                         \
+         __attribute__((constructor));                                  \
+                                                                        \
+    static void auto_register_interpreter(void) {                       \
+        static mrp_interpreter_t interpreter = {                        \
+            .name    = _type,                                           \
+            .compile = _compile,                                        \
+            .prepare = _prepare,                                        \
+            .execute = _execute,                                        \
+            .cleanup = _cleanup                                         \
+        };                                                              \
+                                                                        \
+        if (!mrp_register_interpreter(&interpreter))                    \
+            mrp_log_error("Failed to register interpreter '%s'.",       \
+                          _type);                                       \
+        else                                                            \
+            mrp_log_info("Registered interpreter '%s'.", _type);        \
+    }                                                                   \
+    struct mrp_allow_trailing_semicolon
+
+
+/** Register a new scriptlet interpreter. */
+int mrp_register_interpreter(mrp_interpreter_t *i);
+
+/** Unregister a scriptlet interpreter. */
+int mrp_unregister_interpreter(const char *type);
+
+/** Find a scriptlet interpreter by type. */
+mrp_interpreter_t *mrp_lookup_interpreter(const char *type);
+
+
+/*
+ * a resolver target update script
+ */
+
+struct mrp_scriptlet_s {
+    char              *source;           /* scriptlet code */
+    mrp_interpreter_t *interpreter;      /* interpreter handling this */
+    void              *data;             /* opaque interpreter data */
+    void              *compiled;         /* compiled scriptlet */
+};
+
+/** Create a scriptlet of the given type and source. */
+mrp_scriptlet_t *mrp_create_script(const char *type, const char *source);
+
+/** Destroy the given scriptlet, freeing all of its resources. */
+void mrp_destroy_script(mrp_scriptlet_t *script);
+
+/** Compile the given scriptlet. */
+int mrp_compile_script(mrp_scriptlet_t *s);
+
+/** Prepare the given scriptlet for execution. */
+int mrp_prepare_script(mrp_scriptlet_t *s);
+
+/** Execute the given scriptlet with the given context variables. */
+int mrp_execute_script(mrp_scriptlet_t *s, mrp_context_tbl_t *ctbl);
+
+
+
+/*
+ * Context variable (keyword argument) handling.
+ * XXX TODO: Uhmm... this needs to be rethought/redone. :-(
+ */
+
+mrp_context_tbl_t *mrp_create_context_table(void);
+void mrp_destroy_context_table(mrp_context_tbl_t *tbl);
+int mrp_declare_context_variable(mrp_context_tbl_t *tbl, const char *name,
+                                 mrp_script_type_t type);
+int mrp_push_context_frame(mrp_context_tbl_t *tbl);
+int mrp_pop_context_frame(mrp_context_tbl_t *tbl);
+int mrp_get_context_id(mrp_context_tbl_t *tbl, const char *name);
+int mrp_get_context_value(mrp_context_tbl_t *tbl, int id,
+                          mrp_script_value_t *value);
+int mrp_set_context_value(mrp_context_tbl_t *tbl, int id,
+                          mrp_script_value_t *value);
+int mrp_get_context_value_by_name(mrp_context_tbl_t *tbl, const char *name,
+                          mrp_script_value_t *value);
+int mrp_set_context_value_by_name(mrp_context_tbl_t *tbl, const char *name,
+                                  mrp_script_value_t *value);
+
+
+
+
+MRP_CDECL_END
+
+
+
+
+#endif /* __MURPHY_CORE_SCRIPTING_H__ */
diff --git a/src/core/tests/Makefile.am b/src/core/tests/Makefile.am
new file mode 100644 (file)
index 0000000..5d9e084
--- /dev/null
@@ -0,0 +1 @@
+AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir) $(JSON_CFLAGS)
diff --git a/src/daemon/Makefile b/src/daemon/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/daemon/config.c b/src/daemon/config.c
new file mode 100644 (file)
index 0000000..7691e2a
--- /dev/null
@@ -0,0 +1,1550 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common/log.h>
+#include <murphy/core/context.h>
+#include <murphy/core/plugin.h>
+#include <murphy/daemon/config.h>
+
+#ifndef PATH_MAX
+#    define PATH_MAX 1024
+#endif
+#define MAX_ARGS 64
+
+static void valgrind(const char *vg_path, int argc, char **argv, int vg_offs,
+                     int saved_argc, char **saved_argv, char **envp);
+
+/*
+ * command line processing
+ */
+
+static void print_usage(mrp_context_t *ctx, const char *argv0, int exit_code,
+                        const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options] [-V [valgrind-path] [valgrind-options]]\n\n"
+           "The possible options are:\n"
+           "  -c, --config-file=PATH         main configuration file to use\n"
+           "      The default configuration file is '%s'.\n"
+           "  -C, --config-dir=PATH          configuration directory to use\n"
+           "      If omitted, defaults to '%s'.\n"
+           "  -P, --plugin-dir=PATH          load plugins from DIR\n"
+           "      The default plugin directory is '%s'.\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable given debug configuration\n"
+           "  -D, --list-debug               list known debug sites\n"
+           "  -f, --foreground               don't daemonize\n"
+           "  -h, --help                     show help on usage\n"
+           "  -q, --query-plugins            show detailed information about\n"
+           "                                 all the available plugins\n"
+           "  -B, --blacklist-plugins <list> disable list of plugins\n"
+           "  -I, --blacklist-builtin <list> disable list of builtin plugins\n"
+           "  -E, --blacklist-dynamic <list> disable list of dynamic plugins\n"
+           "  -w, --whitelist-plugins <list> disable list of plugins\n"
+           "  -i, --whitelist-builtin <list> disable list of builtin plugins\n"
+           "  -e, --whitelist-dynamic <list> disable list of dynamic plugins\n"
+           "  -R, --no-poststart-load        "
+                    "disable post-startup plugin loading\n"
+           "  -p, --disable-console          disable Murphy debug console\n"
+           "  -V, --valgrind                 run through valgrind\n",
+           argv0, ctx->config_file, ctx->config_dir, ctx->plugin_dir);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void print_plugin_help(mrp_context_t *ctx, int detailed)
+{
+#define PRNT(fmt, arg) snprintf(defval, sizeof(defval), fmt, arg)
+
+    mrp_plugin_t       *plugin;
+    mrp_plugin_descr_t *descr;
+    mrp_plugin_arg_t   *arg;
+    mrp_list_hook_t    *p, *n;
+    char               *type, defval[64];
+    int                 i;
+
+    mrp_load_all_plugins(ctx);
+
+    printf("\nAvailable plugins:\n\n");
+
+    mrp_list_foreach(&ctx->plugins, p, n) {
+        plugin = mrp_list_entry(p, typeof(*plugin), hook);
+        descr  = plugin->descriptor;
+
+        printf("- %splugin %s:", plugin->handle ? "" : "Builtin ", descr->name);
+        if (detailed) {
+            printf(" (%s, version %d.%d.%d)\n", plugin->path,
+                   MRP_VERSION_MAJOR(descr->version),
+                   MRP_VERSION_MINOR(descr->version),
+                   MRP_VERSION_MICRO(descr->version));
+            printf("  Authors: %s\n", descr->authors);
+        }
+        else
+            printf("\n");
+
+        if (detailed)
+            printf("  Description:\n    %s\n", descr->description);
+
+        if (descr->args != NULL) {
+            printf("  Arguments:\n");
+
+            for (i = 0, arg = descr->args; i < descr->narg; i++, arg++) {
+                printf("    %s: ", arg->key);
+                switch (arg->type) {
+                case MRP_PLUGIN_ARG_TYPE_STRING:
+                    type = "string";
+                    PRNT("%s", arg->str ? arg->str : "<none>");
+                    break;
+                case MRP_PLUGIN_ARG_TYPE_BOOL:
+                    type = "boolean";
+                    PRNT("%s", arg->bln ? "TRUE" : "FALSE");
+                    break;
+                case MRP_PLUGIN_ARG_TYPE_UINT32:
+                    type = "unsigned 32-bit integer";
+                    PRNT("%u", arg->u32);
+                    break;
+                case MRP_PLUGIN_ARG_TYPE_INT32:
+                    type = "signed 32-bit integer";
+                    PRNT("%d", arg->i32);
+                    break;
+                case MRP_PLUGIN_ARG_TYPE_DOUBLE:
+                    type = "double-precision floating point";
+                    PRNT("%f", arg->dbl);
+                    break;
+                default:
+                    type = "<unknown argument type>";
+                    PRNT("%s", "<unknown>");
+                }
+
+                printf("%s, default value=%s\n", type, defval);
+            }
+        }
+
+        if (descr->help != NULL && descr->help[0])
+            printf("  Help:\n    %s\n", descr->help);
+
+        printf("\n");
+    }
+
+    printf("\n");
+
+#if 0
+    printf("Note that you can disable any plugin from the command line by\n");
+    printf("using the '-a name:%s' option.\n", MURPHY_PLUGIN_ARG_DISABLED);
+#endif
+}
+
+
+static void config_set_defaults(mrp_context_t *ctx, char *argv0)
+{
+    static char cfg_file[PATH_MAX], cfg_dir[PATH_MAX], plugin_dir[PATH_MAX];
+    char *e;
+    int   l;
+
+    if ((e = strstr(argv0, "/src/murphyd")) != NULL ||
+        (e = strstr(argv0, "/src/.libs/lt-murphyd")) != NULL) {
+        mrp_log_mask_t saved = mrp_log_set_mask(MRP_LOG_MASK_WARNING);
+        mrp_log_warning("***");
+        mrp_log_warning("*** Looks like we are run from the source tree.");
+        mrp_log_warning("*** Runtime defaults will be set accordingly...");
+        mrp_log_warning("***");
+        mrp_log_set_mask(saved);
+
+        l = e - argv0;
+        snprintf(cfg_dir, sizeof(cfg_dir), "%*.*s/src/daemon", l, l, argv0);
+        snprintf(cfg_file, sizeof(cfg_file), "%s/murphy-lua.conf", cfg_dir);
+        snprintf(plugin_dir, sizeof(plugin_dir), "%*.*s/src/.libs",
+                 l, l, argv0);
+
+        ctx->config_file = cfg_file;
+        ctx->config_dir  = cfg_dir;
+        ctx->plugin_dir  = plugin_dir;
+        ctx->log_mask    = MRP_LOG_UPTO(MRP_LOG_INFO);
+        ctx->log_target  = MRP_LOG_TO_STDERR;
+        ctx->foreground  = TRUE;
+    }
+    else {
+        ctx->config_file = MRP_DEFAULT_CONFIG_FILE;
+        ctx->config_dir  = MRP_DEFAULT_CONFIG_DIR;
+        ctx->plugin_dir  = MRP_DEFAULT_PLUGIN_DIR;
+        ctx->log_mask    = MRP_LOG_MASK_ERROR;
+        ctx->log_target  = MRP_LOG_TO_STDERR;
+    }
+}
+
+
+void mrp_parse_cmdline(mrp_context_t *ctx, int argc, char **argv, char **envp)
+{
+#   define OPTIONS "c:C:l:t:fP:a:vd:hHqB:I:E:w:i:e:RpV"
+    struct option options[] = {
+        { "config-file"      , required_argument, NULL, 'c' },
+        { "config-dir"       , required_argument, NULL, 'C' },
+        { "plugin-dir"       , required_argument, NULL, 'P' },
+        { "log-level"        , required_argument, NULL, 'l' },
+        { "log-target"       , required_argument, NULL, 't' },
+        { "verbose"          , optional_argument, NULL, 'v' },
+        { "debug"            , required_argument, NULL, 'd' },
+        { "foreground"       , no_argument      , NULL, 'f' },
+        { "help"             , no_argument      , NULL, 'h' },
+        { "more-help"        , no_argument      , NULL, 'H' },
+        { "query-plugins"    , no_argument      , NULL, 'q' },
+        { "blacklist"        , required_argument, NULL, 'B' },
+        { "blacklist-plugins", required_argument, NULL, 'B' },
+        { "blacklist-builtin", required_argument, NULL, 'I' },
+        { "blacklist-dynamic", required_argument, NULL, 'E' },
+        { "whitelist"        , required_argument, NULL, 'w' },
+        { "whitelist-plugins", required_argument, NULL, 'w' },
+        { "whitelist-builtin", required_argument, NULL, 'i' },
+        { "whitelist-dynamic", required_argument, NULL, 'e' },
+        { "no-poststart-load", no_argument      , NULL, 'R' },
+        { "disable-console"  , no_argument      , NULL, 'p' },
+        { "valgrind"         , optional_argument, NULL, 'V' },
+        { NULL, 0, NULL, 0 }
+    };
+
+#   define SAVE_ARG(a) do {                                     \
+        if (saved_argc >= MAX_ARGS)                             \
+            print_usage(ctx, argv[0], EINVAL,                   \
+                        "too many command line arguments");     \
+        else                                                    \
+            saved_argv[saved_argc++] = a;                       \
+    } while (0)
+#   define SAVE_OPT(o)       SAVE_ARG(o)
+#   define SAVE_OPTARG(o, a) SAVE_ARG(o); SAVE_ARG(a)
+    char *saved_argv[MAX_ARGS];
+    int   saved_argc;
+
+    int opt, help;
+
+    config_set_defaults(ctx, argv[0]);
+    mrp_log_set_mask(ctx->log_mask);
+    mrp_log_set_target(ctx->log_target);
+
+    saved_argc = 0;
+    saved_argv[saved_argc++] = argv[0];
+
+    help = FALSE;
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'c':
+            SAVE_OPTARG("-c", optarg);
+            ctx->config_file = optarg;
+            break;
+
+        case 'C':
+            SAVE_OPTARG("-C", optarg);
+            ctx->config_dir = optarg;
+            break;
+
+        case 'P':
+            SAVE_OPTARG("-P", optarg);
+            ctx->plugin_dir = optarg;
+            break;
+
+        case 'v':
+            SAVE_OPT("-v");
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            mrp_log_set_mask(ctx->log_mask);
+            break;
+
+        case 'l':
+            SAVE_OPTARG("-l", optarg);
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(ctx, argv[0], EINVAL,
+                            "invalid log level '%s'", optarg);
+            else
+                mrp_log_set_mask(ctx->log_mask);
+            break;
+
+        case 't':
+            SAVE_OPTARG("-t", optarg);
+            ctx->log_target = optarg;
+            break;
+
+        case 'd':
+            SAVE_OPTARG("-d", optarg);
+            ctx->log_mask |= MRP_LOG_MASK_DEBUG;
+            mrp_debug_set_config(optarg);
+            mrp_debug_enable(TRUE);
+            break;
+
+        case 'f':
+            SAVE_OPT("-f");
+            ctx->foreground = TRUE;
+            break;
+
+        case 'h':
+            SAVE_OPT("-h");
+            help++;
+            break;
+
+        case 'H':
+            SAVE_OPT("-H");
+            help += 2;
+            break;
+
+        case 'q':
+            SAVE_OPT("-q");
+            print_plugin_help(ctx, TRUE);
+            break;
+
+        case 'B':
+            if (ctx->blacklist_plugins != NULL)
+                print_usage(ctx, argv[0], EINVAL,
+                            "blacklist option given multiple times");
+            SAVE_OPTARG("-B", optarg);
+            ctx->blacklist_plugins = optarg;
+            break;
+        case 'I':
+            if (ctx->blacklist_builtin != NULL)
+                print_usage(ctx, argv[0], EINVAL,
+                            "builtin blacklist option given multiple times");
+            SAVE_OPTARG("-I", optarg);
+            ctx->blacklist_builtin = optarg;
+            break;
+        case 'E':
+            if (ctx->blacklist_dynamic != NULL)
+                print_usage(ctx, argv[0], EINVAL,
+                            "dynamic blacklist option given multiple times");
+            SAVE_OPTARG("-E", optarg);
+            ctx->blacklist_dynamic = optarg;
+            break;
+        case 'w':
+            if (ctx->whitelist_plugins != NULL)
+                print_usage(ctx, argv[0], EINVAL,
+                            "whitelist option given multiple times");
+            SAVE_OPTARG("-w", optarg);
+            ctx->whitelist_plugins = optarg;
+            break;
+        case 'i':
+            if (ctx->whitelist_builtin != NULL)
+                print_usage(ctx, argv[0], EINVAL,
+                            "builtin whitelist option given multiple times");
+            SAVE_OPTARG("-i", optarg);
+            ctx->whitelist_builtin = optarg;
+            break;
+        case 'e':
+            if (ctx->whitelist_dynamic != NULL)
+                print_usage(ctx, argv[0], EINVAL,
+                            "dynamic whitelist option given multiple times");
+            SAVE_OPTARG("-e", optarg);
+            ctx->whitelist_dynamic = optarg;
+            break;
+
+        case 'R':
+            SAVE_OPT("-R");
+            ctx->disable_runtime_load = true;
+            break;
+
+        case 'p':
+            SAVE_OPT("-p");
+            ctx->disable_console = TRUE;
+            break;
+        case 'V':
+            valgrind(optarg, argc, argv, optind, saved_argc, saved_argv, envp);
+            break;
+
+        default:
+            print_usage(ctx, argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    if (help) {
+        print_usage(ctx, argv[0], -1, "");
+        if (help > 1)
+            print_plugin_help(ctx, FALSE);
+        exit(0);
+    }
+
+}
+
+
+
+/*
+ * configuration file processing
+ */
+
+typedef struct {
+    char  buf[MRP_CFG_MAXLINE];          /* input buffer */
+    char *token;                         /* current token */
+    char *in;                            /* filling pointer */
+    char *out;                           /* consuming pointer */
+    char *next;                          /* next token buffer position */
+    int   fd;                            /* input file */
+    int   error;                         /* whether has encounted and error */
+    char *file;                          /* file being processed */
+    int   line;                          /* line number */
+    int   next_newline;
+    int   was_newline;
+} input_t;
+
+
+#define COMMON_ACTION_FIELDS                                                \
+    action_type_t   type;                /* action to execute */            \
+    mrp_list_hook_t hook                 /* to command sequence */
+
+typedef enum {                           /* action types */
+    ACTION_UNKNOWN = 0,
+    ACTION_LOAD,                         /* load a plugin */
+    ACTION_TRYLOAD,                      /* load a plugin, ignore errors */
+    ACTION_IF,                           /* if-else branch */
+    ACTION_SETCFG,                       /* set a config variable */
+    ACTION_INFO,                         /* emit an info message */
+    ACTION_WARNING,                      /* emit a warning message */
+    ACTION_ERROR,                        /* emit and error message and exit */
+    ACTION_MAX,
+} action_type_t;
+
+typedef enum {                           /* branch operators */
+    BR_UNKNOWN = 0,
+    BR_PLUGIN_EXISTS,                    /* test if a plugin exists */
+} branch_op_t;
+
+typedef struct {                         /* a generic action */
+    COMMON_ACTION_FIELDS;                /* type, hook */
+} any_action_t;
+
+typedef struct {                         /* a command-type of action */
+    COMMON_ACTION_FIELDS;                /* type, hook */
+    char **args;                         /* arguments for the action */
+    int    narg;                         /* number of arguments */
+} cmd_action_t;
+
+typedef struct {                         /* a command-type of action */
+    COMMON_ACTION_FIELDS;                /* type, hook */
+    char             *name;              /* plugin to load */
+    char             *instance;          /* load as this instance */
+    mrp_plugin_arg_t *args;              /* plugin arguments */
+    int               narg;              /* number of arguments */
+} load_action_t;
+
+typedef struct {                         /* a branch test action */
+    COMMON_ACTION_FIELDS;                /* type, hook */
+    branch_op_t      op;                 /* branch operator */
+    char            *arg1;               /* argument for the operator */
+    char            *arg2;               /* argument for the operator */
+    mrp_list_hook_t  pos;                /* postitive branch */
+    mrp_list_hook_t  neg;                /* negative branch */
+} branch_action_t;
+
+typedef struct {                         /* a branch test action */
+    COMMON_ACTION_FIELDS;                /* type, hook */
+    char            *message;            /* message to show */
+} message_action_t;
+
+typedef enum {
+    CFGVAR_UNKNOWN = 0,
+    CFGVAR_RESOLVER_RULES,               /* resolver ruleset file */
+} cfgvar_t;
+
+typedef struct {
+    COMMON_ACTION_FIELDS;
+    cfgvar_t  id;                        /* confguration variable */
+    char     *value;                     /* value for variable */
+} setcfg_action_t;
+
+typedef struct {
+    const char   *keyword;
+    any_action_t *(*parse)(input_t *in, char **args, int narg);
+    int           (*exec)(mrp_context_t *ctx, any_action_t *action);
+    void          (*free)(any_action_t *a);
+} action_descr_t;
+
+
+
+static any_action_t *parse_action(input_t *in, char **args, int narg);
+static any_action_t *parse_load(input_t *in, char **argv, int argc);
+static any_action_t *parse_if_else(input_t *in, char **argv, int argc);
+static any_action_t *parse_setcfg(input_t *in, char **argv, int argc);
+static any_action_t *parse_message(input_t *in, char **argv, int argc);
+static int exec_action(mrp_context_t *ctx, any_action_t *action);
+static int exec_load(mrp_context_t *ctx, any_action_t *action);
+static int exec_if_else(mrp_context_t *ctx, any_action_t *action);
+static int exec_setcfg(mrp_context_t *ctx, any_action_t *action);
+static int exec_message(mrp_context_t *ctx, any_action_t *action);
+static void free_action(any_action_t *action);
+static void free_if_else(any_action_t *action);
+static void free_load(any_action_t *action);
+static void free_setcfg(any_action_t *action);
+static void free_message(any_action_t *action);
+
+static char *get_next_token(input_t *in);
+static int get_next_line(input_t *in, char **args, size_t size);
+static char *replace_tokens(input_t *in, char *first, char *last,
+                            char *token, int size);
+
+#define A(type, keyword, parse, exec, free) \
+    [ACTION_##type] = { MRP_KEYWORD_##keyword, parse, exec, free }
+
+static action_descr_t actions[] = {
+    [ACTION_UNKNOWN] = { NULL, NULL, NULL, NULL },
+
+    A(LOAD   , LOAD   , parse_load   , exec_load   , free_load),
+    A(TRYLOAD, TRYLOAD, parse_load   , exec_load   , free_load),
+    A(IF     , IF     , parse_if_else, exec_if_else, free_if_else),
+    A(SETCFG , SETCFG , parse_setcfg , exec_setcfg , free_setcfg),
+    A(INFO   , INFO   , parse_message, exec_message, free_message),
+    A(WARNING, WARNING, parse_message, exec_message, free_message),
+    A(ERROR  , ERROR  , parse_message, exec_message, free_message),
+
+    [ACTION_MAX]     = { NULL, NULL, NULL, NULL }
+};
+
+#undef A
+
+
+
+mrp_cfgfile_t *mrp_parse_cfgfile(const char *path)
+{
+    mrp_cfgfile_t *cfg = NULL;
+    input_t        input;
+    char          *args[MRP_CFG_MAXARGS];
+    int            narg;
+    any_action_t  *a;
+
+    memset(&input, 0, sizeof(input));
+    input.token  = input.buf;
+    input.in     = input.buf;
+    input.out    = input.buf;
+    input.next   = input.buf;
+    input.fd     = open(path, O_RDONLY);
+    input.file   = (char *)path;
+    input.line   = 1;
+
+    if (input.fd < 0) {
+        mrp_log_error("Failed to open configuration file '%s' (%d: %s).",
+                      path, errno, strerror(errno));
+        goto fail;
+    }
+
+    cfg = mrp_allocz(sizeof(*cfg));
+
+    if (cfg == NULL) {
+        mrp_log_error("Failed to allocate configuration file buffer.");
+        goto fail;
+    }
+
+    mrp_list_init(&cfg->actions);
+
+    while ((narg = get_next_line(&input, args, MRP_ARRAY_SIZE(args))) > 0) {
+        a = parse_action(&input, args, narg);
+
+        if (a != NULL)
+            mrp_list_append(&cfg->actions, &a->hook);
+        else
+            goto fail;
+    }
+
+    if (narg == 0)
+        return cfg;
+
+ fail:
+    if (input.fd >= 0)
+        close(input.fd);
+    if (cfg)
+        mrp_free_cfgfile(cfg);
+
+    return NULL;
+}
+
+
+void mrp_free_cfgfile(mrp_cfgfile_t *cfg)
+{
+    mrp_list_hook_t *p, *n;
+    any_action_t    *a;
+
+    mrp_list_foreach(&cfg->actions, p, n) {
+        a = mrp_list_entry(p, typeof(*a), hook);
+        free_action(a);
+    }
+
+    mrp_free(cfg);
+}
+
+
+int mrp_exec_cfgfile(mrp_context_t *ctx, mrp_cfgfile_t *cfg)
+{
+    mrp_list_hook_t *p, *n;
+    any_action_t    *a;
+
+    mrp_list_foreach(&cfg->actions, p, n) {
+        a = mrp_list_entry(p, typeof(*a), hook);
+        if (!exec_action(ctx, a))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static any_action_t *parse_action(input_t *in, char **args, int narg)
+{
+    action_descr_t *ad = actions + 1;
+
+    while (ad->keyword != NULL) {
+        if (!strcmp(args[0], ad->keyword))
+            return ad->parse(in, args, narg);
+        ad++;
+    }
+
+    mrp_log_error("Unknown command '%s' in file '%s'.", args[0], in->file);
+    return NULL;
+}
+
+
+static void free_action(any_action_t *action)
+{
+    mrp_list_delete(&action->hook);
+
+    if (ACTION_UNKNOWN < action->type && action->type < ACTION_MAX)
+        actions[action->type].free(action);
+    else {
+        mrp_log_error("Unknown configuration action of type 0x%x.",
+                      action->type);
+        mrp_free(action);
+    }
+}
+
+
+static int exec_action(mrp_context_t *ctx, any_action_t *action)
+{
+    if (ACTION_UNKNOWN < action->type && action->type < ACTION_MAX)
+        return actions[action->type].exec(ctx, action);
+    else {
+        mrp_log_error("Unknown configuration action of type 0x%x.",
+                      action->type);
+        return FALSE;
+    }
+}
+
+
+static any_action_t *parse_load(input_t *in, char **argv, int argc)
+{
+    load_action_t    *action;
+    action_type_t     type;
+    mrp_plugin_arg_t *args, *a;
+    int               narg, i, start;
+    char             *k, *v;
+
+    MRP_UNUSED(in);
+
+    if (!strcmp(argv[0], MRP_KEYWORD_LOAD))
+        type = ACTION_LOAD;
+    else
+        type = ACTION_TRYLOAD;
+
+    if (argc < 2 || (action = mrp_allocz(sizeof(*action))) == NULL) {
+        mrp_log_error("Failed to allocate load config action.");
+        return NULL;
+    }
+
+    mrp_list_init(&action->hook);
+    action->type = type;
+    action->name = mrp_strdup(argv[1]);
+
+    if (action->name == NULL) {
+        mrp_log_error("Failed to allocate load config action.");
+        mrp_free(action);
+        return NULL;
+    }
+
+    args = NULL;
+
+    if (argc > 3 && !strcmp(argv[2], MRP_KEYWORD_AS)) {
+        /* [try-]load-plugin name as instance [args...] */
+        action->instance = mrp_strdup(argv[3]);
+        start = 4;
+
+        if (action->instance == NULL) {
+            mrp_log_error("Failed to allocate load config action.");
+            mrp_free(action->name);
+            mrp_free(action);
+            goto fail;
+        }
+    }
+    else {
+        /* [try-]load-plugin name [args...] */
+        start = 2;
+    }
+
+    narg = 0;
+    if (start < argc) {
+        if ((args = mrp_allocz_array(typeof(*args), argc - 1)) != NULL) {
+            for (i = start, a = args; i < argc; i++, a++) {
+                if (*argv[i] == MRP_START_COMMENT)
+                    break;
+
+                mrp_debug("argument #%d: '%s'", i - start, argv[i]);
+
+                k = argv[i];
+                v = strchr(k, '=');
+
+                if (v != NULL)
+                    *v++ = '\0';
+                else {
+                    if (i + 2 < argc) {
+                        if (argv[i+1][0] == '=' && argv[i+1][1] == '\0') {
+                            v  = argv[i + 2];
+                            i += 2;
+                        }
+                    }
+                    else {
+                        mrp_log_error("Invalid plugin load argument '%s'.", k);
+                        goto fail;
+                    }
+                }
+
+                a->type = MRP_PLUGIN_ARG_TYPE_STRING;
+                a->key  = mrp_strdup(k);
+                a->str  = v ? mrp_strdup(v) : NULL;
+                narg++;
+
+                if (a->key == NULL || (a->str == NULL && v != NULL)) {
+                    mrp_log_error("Failed to allocate plugin arg %s%s%s.",
+                                  k, v ? "=" : "", v ? v : "");
+                    goto fail;
+                }
+            }
+        }
+    }
+
+    action->args = args;
+    action->narg = narg;
+
+    return (any_action_t *)action;
+
+
+ fail:
+    if (args != NULL) {
+        for (i = 1; i < argc && args[i].key != NULL; i++) {
+            mrp_free(args[i].key);
+            mrp_free(args[i].str);
+        }
+        mrp_free(args);
+    }
+
+    return NULL;
+}
+
+
+static void free_load(any_action_t *action)
+{
+    load_action_t *load = (load_action_t *)action;
+    int            i;
+
+    if (load != NULL) {
+        mrp_free(load->name);
+
+        for (i = 0; i < load->narg; i++) {
+            mrp_free(load->args[i].key);
+            mrp_free(load->args[i].str);
+        }
+
+        mrp_free(load->args);
+    }
+}
+
+
+static int exec_load(mrp_context_t *ctx, any_action_t *action)
+{
+    load_action_t *load = (load_action_t *)action;
+    mrp_plugin_t  *plugin;
+
+    plugin = mrp_load_plugin(ctx, load->name, load->instance,
+                             load->args, load->narg);
+
+    if (plugin != NULL) {
+        plugin->may_fail = (load->type == ACTION_TRYLOAD);
+
+        return TRUE;
+    }
+    else
+        return (load->type == ACTION_TRYLOAD);
+}
+
+
+static any_action_t *parse_if_else(input_t *in, char **argv, int argc)
+{
+    branch_action_t *branch;
+    mrp_list_hook_t *actions;
+    any_action_t    *a;
+    char            *args[MRP_CFG_MAXARGS], *op, *name;
+    int              start, narg, pos;
+
+    if (argc < 2) {
+        mrp_log_error("%s:%d: invalid use of if-conditional.",
+                      in->file, in->line - 1);
+        return NULL;
+    }
+
+    start = in->line - 1;
+    op    = argv[1];
+    name  = argv[2];
+
+    if (strcmp(op, MRP_KEYWORD_EXISTS)) {
+        mrp_log_error("%s:%d: unknown operator '%s' in if-conditional.",
+                      in->file, in->line - 1, op);
+    }
+
+    branch = mrp_allocz(sizeof(*branch));
+
+    if (branch != NULL) {
+        mrp_list_init(&branch->hook);
+        mrp_list_init(&branch->pos);
+        mrp_list_init(&branch->neg);
+
+        branch->type = ACTION_IF;
+        branch->op   = BR_PLUGIN_EXISTS;
+        branch->arg1 = mrp_strdup(name);
+
+        if (branch->arg1 == NULL) {
+            mrp_log_error("Failed to allocate configuration if-conditional.");
+            goto fail;
+        }
+
+        pos     = TRUE;
+        actions = &branch->pos;
+        while ((narg = get_next_line(in, args, sizeof(args))) > 0) {
+            if (narg == 1) {
+                if (!strcmp(args[0], MRP_KEYWORD_END))
+                    return (any_action_t *)branch;
+
+                if (!strcmp(args[0], MRP_KEYWORD_ELSE)) {
+                    if (pos) {
+                        actions = &branch->neg;
+                        pos = FALSE;
+                    }
+                    else {
+                        mrp_log_error("%s:%d: extra else without if.",
+                                      in->file, in->line - 1);
+                        goto fail;
+                    }
+                }
+            }
+            else {
+                a = parse_action(in, args, narg);
+
+                if (a != NULL)
+                    mrp_list_append(actions, &a->hook);
+                else
+                    goto fail;
+            }
+        }
+    }
+    else {
+        mrp_log_error("Failed to allocate configuration if-conditional.");
+        return NULL;
+    }
+
+    mrp_log_error("%s:%d: unterminated if-conditional (missing 'end')",
+                  in->file, start);
+
+ fail:
+    free_action((any_action_t *)branch);
+    return NULL;
+}
+
+
+static void free_if_else(any_action_t *action)
+{
+    branch_action_t *branch = (branch_action_t *)action;
+    any_action_t    *a;
+    mrp_list_hook_t *p, *n;
+
+    if (branch != NULL) {
+        mrp_free(branch->arg1);
+        mrp_free(branch->arg2);
+
+        mrp_list_foreach(&branch->pos, p, n) {
+            a = mrp_list_entry(p, typeof(*a), hook);
+            free_action(a);
+        }
+
+        mrp_list_foreach(&branch->neg, p, n) {
+            a = mrp_list_entry(p, typeof(*a), hook);
+            free_action(a);
+        }
+
+        mrp_free(branch);
+    }
+}
+
+
+static int exec_if_else(mrp_context_t *ctx, any_action_t *action)
+{
+    branch_action_t *branch = (branch_action_t *)action;
+    mrp_list_hook_t *p, *n, *actions;
+    any_action_t    *a;
+
+    if (branch->op != BR_PLUGIN_EXISTS || branch->arg1 == NULL)
+        return FALSE;
+
+    if (mrp_plugin_exists(ctx, branch->arg1))
+        actions = &branch->pos;
+    else
+        actions = &branch->neg;
+
+    mrp_list_foreach(actions, p, n) {
+        a = mrp_list_entry(p, typeof(*a), hook);
+
+        if (!exec_action(ctx, a))
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static any_action_t *parse_setcfg(input_t *in, char **argv, int argc)
+{
+    setcfg_action_t *action;
+    struct {
+        const char *name;
+        cfgvar_t    id;
+    } *var, vartbl[] = {
+        { MRP_CFGVAR_RESOLVER, CFGVAR_RESOLVER_RULES },
+        { NULL               , 0                     },
+    };
+
+    if (argc < 3) {
+        mrp_log_error("%s:%d: configuration directive %s requires two "
+                      "arguments, %d given.", in->file, in->line,
+                      MRP_KEYWORD_SETCFG, argc - 1);
+        return NULL;
+    }
+
+    for (var = vartbl; var->name != NULL; var++)
+        if (!strcmp(var->name, argv[1]))
+            break;
+
+    if (var->name == NULL) {
+        mrp_log_error("%s:%d: unknown configuration variable '%s'.",
+                      in->file, in->line, argv[1]);
+        return NULL;
+    }
+
+    if ((action = mrp_allocz(sizeof(*action))) == NULL) {
+        mrp_log_error("Failed to allocate %s %s configuration action.",
+                      MRP_KEYWORD_SETCFG, argv[1]);
+        return NULL;
+    }
+
+    mrp_list_init(&action->hook);
+    action->type  = ACTION_SETCFG;
+    action->id    = var->id;
+    action->value = mrp_strdup(argv[2]);
+
+    if (action->value == NULL) {
+        mrp_log_error("Failed to allocate %s %s configuration action.",
+                      MRP_KEYWORD_SETCFG, argv[1]);
+        mrp_free(action);
+        return NULL;
+    }
+
+    return (any_action_t *)action;
+}
+
+
+static int exec_setcfg(mrp_context_t *ctx, any_action_t *action)
+{
+    setcfg_action_t *setcfg = (setcfg_action_t *)action;
+
+    switch (setcfg->id) {
+    case CFGVAR_RESOLVER_RULES:
+        if (ctx->resolver_ruleset == NULL) {
+            ctx->resolver_ruleset = setcfg->value;
+            setcfg->value = NULL;
+            return TRUE;
+        }
+        else {
+            mrp_log_error("Multiple resolver rulesets specified (%s, %s).",
+                          ctx->resolver_ruleset, setcfg->value);
+            return FALSE;
+        }
+        break;
+    default:
+        mrp_log_error("Invalid configuration setting.");
+    }
+
+    return FALSE;
+}
+
+
+static void free_setcfg(any_action_t *action)
+{
+    setcfg_action_t *setcfg = (setcfg_action_t *)action;
+
+    if (setcfg != NULL) {
+        mrp_free(setcfg->value);
+        mrp_free(setcfg);
+    }
+}
+
+
+static any_action_t *parse_message(input_t *in, char **argv, int argc)
+{
+    message_action_t *msg;
+    action_type_t     type;
+    char              buf[4096], *p;
+    const char       *t;
+    int               i, l, n;
+
+    MRP_UNUSED(in);
+
+    if (argc < 2) {
+        mrp_log_error("%s requires at least one argument.", argv[0]);
+        return NULL;
+    }
+
+    if (!strcmp(argv[0], MRP_KEYWORD_ERROR))
+        type = ACTION_ERROR;
+    else if (!strcmp(argv[0], MRP_KEYWORD_WARNING))
+        type = ACTION_WARNING;
+    else if (!strcmp(argv[0], MRP_KEYWORD_INFO))
+        type = ACTION_INFO;
+    else
+        return NULL;
+
+    p = buf;
+    n = sizeof(buf);
+    if ((msg = mrp_allocz(sizeof(*msg))) != NULL) {
+        for (i = 1, t=""; i < argc && n > 0; i++, t=" ") {
+            l  = snprintf(p, n, "%s%s", t, argv[i]);
+            p += l;
+            n -= l;
+        }
+
+        msg->type    = type;
+        msg->message = mrp_strdup(buf);
+
+        if (msg->message == NULL) {
+            mrp_log_error("Failed to allocate %s config action.", argv[0]);
+            mrp_free(msg);
+            msg = NULL;
+        }
+    }
+
+    return (any_action_t *)msg;
+}
+
+
+static int exec_message(mrp_context_t *ctx, any_action_t *action)
+{
+    message_action_t *msg = (message_action_t *)action;
+
+    MRP_UNUSED(ctx);
+
+    switch (action->type) {
+    case ACTION_ERROR:   mrp_log_error("%s", msg->message);   exit(1);
+    case ACTION_WARNING: mrp_log_warning("%s", msg->message); return TRUE;
+    case ACTION_INFO:    mrp_log_info("%s", msg->message);    return TRUE;
+    default:
+        return FALSE;
+    }
+}
+
+
+static void free_message(any_action_t *action)
+{
+    message_action_t *msg = (message_action_t *)action;
+
+    if (msg != NULL) {
+        mrp_free(msg->message);
+        mrp_free(msg);
+    }
+}
+
+
+static int get_next_line(input_t *in, char **args, size_t size)
+{
+#define BLOCK_START(s)                                                   \
+    ((s[0] == '{' || s[0] == '[') && (s[1] == '\0' || s[1] == '\n'))
+#define BLOCK_END(s)                                                     \
+    ((s[0] == '}' || s[0] == ']') && (s[1] == '\0' || s[1] == '\n'))
+
+    char *token, *p;
+    char  block[2], json[MRP_CFG_MAXLINE];
+    int   narg, nest, beg;
+    int   i, n, l, tot;
+
+    narg     = 0;
+    nest     = 0;
+    beg      = -1;
+    tot      = 0;
+    block[0] = block[1] = '\0';
+    while ((token = get_next_token(in)) != NULL && narg < (int)size) {
+        if (in->error)
+            return -1;
+
+        mrp_debug("read input token '%s'", token);
+
+        if (token[0] != '\n') {
+            if (BLOCK_START(token)) {
+                if (!nest) {
+                    mrp_debug("collecting JSON argument");
+
+                    block[0] = token[0];
+                    block[1] = (block[0] == '{' ? '}' : ']');
+                    nest = 1;
+                    beg  = narg;
+                    tot  = 1;
+                }
+                else {
+                    if (token[0] == block[0])
+                        nest++;
+                }
+            }
+
+            args[narg++] = token;
+
+            if (beg >= 0) {              /* if collecting, update length */
+                tot += strlen(token) + 1;
+                if (strchr(token, ' ') || strchr(token, '\t'))
+                    tot += 2;            /* will need quoting */
+            }
+
+            if (BLOCK_END(token) && nest > 0) {
+                if (token[0] == block[1])
+                    nest--;
+
+                if (nest == 0) {
+                    mrp_debug("finished collecting JSON argument");
+
+                    if (tot > (int)sizeof(json) - 1) {
+                        mrp_log_error("Maximum token length exceeded.");
+                        return -1;
+                    }
+
+                    p = json;
+                    l = tot;
+                    for (i = beg; i < narg; i++) {
+                        if (strchr(args[i], ' ') || strchr(args[i], '\t'))
+                            n = snprintf(p, l, "'%s'", args[i]);
+                        else
+                            n = snprintf(p, l, "%s", args[i]);
+                        if (n >= l)
+                            return -1;
+                        p += n;
+                        l -= n;
+                    }
+
+                    mrp_debug("collected JSON token: '%s'", json);
+
+                    args[beg] = replace_tokens(in, args[beg], args[narg-1],
+                                               json, (int)(p - json));
+
+                    if (args[beg] == NULL) {
+                        mrp_log_error("Failed to replace block of tokens.");
+                        return -1;
+                    }
+                    else
+                        narg = beg + 1;
+
+                    block[0] = '\0';
+                    block[1] = '\0';
+                    beg      = -1;
+                }
+            }
+        }
+        else {
+            if (narg && *args[0] != MRP_START_COMMENT && *args[0] != '\n')
+                return narg;
+            else
+                narg = 0;
+        }
+    }
+
+    if (in->error)
+        return -1;
+
+    if (narg >= (int)size) {
+        mrp_log_error("Too many tokens on line %d of %s.",
+                      in->line - 1, in->file);
+        return -1;
+    }
+    else {
+        if (*args[0] != MRP_START_COMMENT && *args[0] != '\n')
+            return narg;
+        else
+            return 0;
+    }
+}
+
+
+static inline void skip_whitespace(input_t *in)
+{
+    while ((*in->out == ' ' || *in->out == '\t') && in->out < in->in)
+        in->out++;
+}
+
+
+static inline void skip_rest_of_line(input_t *in)
+{
+    while (*in->out != '\n' && in->out < in->in)
+        in->out++;
+}
+
+
+static char *replace_tokens(input_t *in, char *first, char *last,
+                            char *token, int size)
+{
+    char *beg = first;
+    char *end = last + strlen(last) + 1;
+
+    if (!(in->buf < beg && end < in->out))
+        return NULL;
+
+    if ((end - beg) < size)
+        return NULL;
+
+    strcpy(first, token);
+
+    return first;
+}
+
+
+static char *get_next_token(input_t *in)
+{
+    ssize_t len;
+    int     diff, size;
+    int     quote, quote_line;
+    char   *p, *q;
+
+    /*
+     * Newline:
+     *
+     *     If the previous token was terminated by a newline,
+     *     take care of properly returning and administering
+     *     the newline token here.
+     */
+
+    if (in->next_newline) {
+        in->next_newline = FALSE;
+        in->was_newline  = TRUE;
+        in->line++;
+
+        return "\n";
+    }
+
+
+    /*
+     * if we just finished a line, discard all old data/tokens
+     */
+
+    if (*in->token == '\n' || in->was_newline) {
+        diff = in->out - in->buf;
+        size = in->in - in->out;
+        memmove(in->buf, in->out, size);
+        in->out  -= diff;
+        in->in   -= diff;
+        in->next  = in->buf;
+        *in->in   = '\0';
+    }
+
+    /*
+     * refill the buffer if we're empty or just flushed all tokens
+     */
+
+    if (in->token == in->buf && in->fd != -1) {
+        size = sizeof(in->buf) - 1 - (in->in - in->buf);
+        len  = read(in->fd, in->in, size);
+
+        if (len < size) {
+            close(in->fd);
+            in->fd = -1;
+        }
+
+        if (len < 0) {
+            mrp_log_error("Failed to read from config file (%d: %s).",
+                          errno, strerror(errno));
+            in->error = TRUE;
+            close(in->fd);
+            in->fd = -1;
+
+            return NULL;
+        }
+
+        in->in += len;
+        *in->in = '\0';
+    }
+
+    if (in->out >= in->in)
+        return NULL;
+
+    skip_whitespace(in);
+
+    quote = FALSE;
+    quote_line = 0;
+
+    p = in->out;
+    q = in->next;
+    in->token = q;
+
+    while (p < in->in) {
+        /*printf("[%c]\n", *p == '\n' ? '.' : *p);*/
+        switch (*p) {
+            /*
+             * Quoting:
+             *
+             *     If we're not within a quote, mark a quote started.
+             *     Otherwise if quote matches, close quoting. Otherwise
+             *     copy the quoted quote verbatim.
+             */
+        case '\'':
+        case '\"':
+            in->was_newline = FALSE;
+            if (!quote) {
+                quote      = *p++;
+                quote_line = in->line;
+            }
+            else {
+                if (*p == quote) {
+                    quote      = FALSE;
+                    quote_line = 0;
+                    p++;
+                    *q++ = '\0';
+
+                    in->out  = p;
+                    in->next = q;
+
+                    return in->token;
+                }
+                else {
+                    *q++ = *p++;
+                }
+            }
+            break;
+
+            /*
+             * Whitespace:
+             *
+             *     If we're quoting, copy verbatim. Otherwise mark the end
+             *     of the token.
+             */
+        case ' ':
+        case '\t':
+            if (quote)
+                *q++ = *p++;
+            else {
+                p++;
+                *q++ = '\0';
+
+                in->out  = p;
+                in->next = q;
+
+                return in->token;
+            }
+            in->was_newline = FALSE;
+            break;
+
+            /*
+             * Escaping:
+             *
+             *     If the last character in the input, copy verbatim.
+             *     Otherwise if it escapes a '\n', skip both. Otherwise
+             *     copy the escaped character verbatim.
+             */
+        case '\\':
+            if (p < in->in - 1) {
+                p++;
+                if (*p != '\n')
+                    *q++ = *p++;
+                else {
+                    p++;
+                    in->line++;
+                    in->out = p;
+                    skip_whitespace(in);
+                    p = in->out;
+                }
+            }
+            else
+                *q++ = *p++;
+            in->was_newline = FALSE;
+            break;
+
+            /*
+             * Newline:
+             *
+             *     We don't allow newlines to be quoted. Otherwise
+             *     if the token is not the newline itself, we mark
+             *     the next token to be newline and return the token
+             *     it terminated.
+             */
+        case '\n':
+            if (quote) {
+                mrp_log_error("%s:%d: Unterminated quote (%c) started "
+                              "on line %d.", in->file, in->line, quote,
+                              quote_line);
+                in->error = TRUE;
+
+                return NULL;
+            }
+            else {
+                *q = '\0';
+                p++;
+
+                in->out  = p;
+                in->next = q;
+
+                if (in->token == q) {
+                    in->line++;
+                    in->was_newline = TRUE;
+                    return "\n";
+                }
+                else {
+                    in->next_newline = TRUE;
+                    return in->token;
+                }
+            }
+            break;
+
+            /*
+             * Comments:
+             *
+             *     Attempt to allow and filter out partial-line comments.
+             *
+             *     This has not been thoroughly thought through. Probably
+             *     there are broken border-cases. The whole tokenizing loop
+             *     has not been written so that it could grow the buffer and
+             *     refill it even if even we run out of input and we're not
+             *     sure whehter a full token has been consumed... beware.
+             *     To be sure, we bail out here if it looks like we exhausted
+             *     the input buffer while skipping a comment. This needs to
+             *     be thought through properly.
+             */
+        case MRP_START_COMMENT:
+            skip_rest_of_line(in);
+            if (in->out == in->in) {
+                mrp_log_error("%s:%d Exhausted input buffer while skipping "
+                              "a comment.", in->file, in->line);
+                in->error = TRUE;
+                return NULL;
+            }
+            else {
+                p = in->out;
+                in->line++;
+            }
+            break;
+
+        default:
+            *q++ = *p++;
+            in->was_newline = FALSE;
+        }
+    }
+
+    if (in->fd == -1) {
+        *q = '\0';
+        in->out = p;
+        in->in = q;
+
+        return in->token;
+    }
+    else {
+        mrp_log_error("Input line %d of file %s exceeds allowed length.",
+                      in->line, in->file);
+        return NULL;
+    }
+}
+
+
+/*
+ * bridging to valgrind
+ */
+
+static void valgrind(const char *vg_path, int argc, char **argv, int vg_offs,
+                     int saved_argc, char **saved_argv, char **envp)
+{
+#define VG_ARG(a) vg_argv[vg_argc++] = a
+    char *vg_argv[MAX_ARGS + 1];
+    int   vg_argc, normal_offs, i;
+
+    vg_argc = 0;
+
+    /* set valgrind binary */
+    VG_ARG(vg_path ? (char *)vg_path : "/usr/bin/valgrind");
+
+    /* add valgrind arguments */
+    for (i = vg_offs; i < argc; i++)
+        VG_ARG(argv[i]);
+
+    /* save offset to normal argument list for fallback */
+    normal_offs = vg_argc;
+
+    /* add our binary and our arguments */
+    for (i = 0; i < saved_argc; i++)
+        vg_argv[vg_argc++] = saved_argv[i];
+
+    /* terminate argument list */
+    VG_ARG(NULL);
+
+    /* try executing through valgrind */
+    mrp_log_warning("Executing through valgrind (%s)...", vg_argv[0]);
+    execve(vg_argv[0], vg_argv, envp);
+
+    /* try falling back to normal execution */
+    mrp_log_error("Executing through valgrind failed (error %d: %s), "
+                  "retrying without...", errno, strerror(errno));
+    execve(vg_argv[normal_offs], vg_argv + normal_offs, envp);
+
+    /* can't do either, so just give up */
+    mrp_log_error("Fallback to normal execution failed (error %d: %s).",
+                  errno, strerror(errno));
+    exit(1);
+}
diff --git a/src/daemon/config.h b/src/daemon/config.h
new file mode 100644 (file)
index 0000000..365acca
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CONFIG_H__
+#define __MURPHY_CONFIG_H__
+
+#include <murphy/common/list.h>
+#include <murphy/core/context.h>
+
+#ifndef MRP_DEFAULT_CONFIG_DIR
+#    define MRP_DEFAULT_CONFIG_DIR  SYSCONFDIR"/murphy"
+#endif
+
+#ifndef MRP_DEFAULT_CONFIG_FILE
+#    define MRP_DEFAULT_CONFIG_FILE MRP_DEFAULT_CONFIG_DIR"/murphy.conf"
+#endif
+
+/*
+ * command line processing
+ */
+
+/** Parse the command line and update context accordingly. */
+void mrp_parse_cmdline(mrp_context_t *ctx, int argc, char **argv, char **envp);
+
+
+/*
+ * configuration file processing
+ */
+
+#define MRP_CFG_MAXLINE (16 * 1024)      /* input line length limit */
+#define MRP_CFG_MAXARGS  64              /* command argument limit */
+
+/* configuration keywords */
+#define MRP_KEYWORD_LOAD    "load-plugin"
+#define MRP_KEYWORD_TRYLOAD "try-load-plugin"
+#define MRP_KEYWORD_AS      "as"
+#define MRP_KEYWORD_IF      "if"
+#define MRP_KEYWORD_ELSE    "else"
+#define MRP_KEYWORD_END     "end"
+#define MRP_KEYWORD_EXISTS  "plugin-exists"
+#define MRP_KEYWORD_SETCFG  "set"
+#define MRP_KEYWORD_ERROR   "error"
+#define MRP_KEYWORD_WARNING "warning"
+#define MRP_KEYWORD_INFO    "info"
+#define MRP_START_COMMENT   '#'
+
+/* known configuration variables for 'set' command */
+#define MRP_CFGVAR_RESOLVER "resolver-ruleset"
+
+typedef struct {
+    mrp_list_hook_t actions;
+} mrp_cfgfile_t;
+
+
+/** Parse the given configuration file. */
+mrp_cfgfile_t *mrp_parse_cfgfile(const char *path);
+
+/** Execute the commands of the given parsed configuration file. */
+int mrp_exec_cfgfile(mrp_context_t *ctx, mrp_cfgfile_t *cfg);
+
+/** Free the given parsed configuration file. */
+void mrp_free_cfgfile(mrp_cfgfile_t *cfg);
+
+#endif /* __MURPHY_CONFIG_H__ */
diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c
new file mode 100644 (file)
index 0000000..65dee9b
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <signal.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/utils.h>
+#include <murphy/core/context.h>
+#include <murphy/core/plugin.h>
+#include <murphy/resolver/resolver.h>
+#include <murphy/daemon/config.h>
+#include <murphy/daemon/daemon.h>
+
+
+/*
+ * daemon-related events
+ */
+
+enum {
+    DAEMON_EVENT_LOADING  = 0,           /* daemon loading configuration */
+    DAEMON_EVENT_STARTING,               /* daemon starting plugins */
+    DAEMON_EVENT_RUNNING,                /* daemon entering mainloop */
+    DAEMON_EVENT_STOPPING                /* daemon shutting down */
+};
+
+
+MRP_REGISTER_EVENTS(daemon_events,
+                    MRP_EVENT(MRP_DAEMON_LOADING , DAEMON_EVENT_LOADING ),
+                    MRP_EVENT(MRP_DAEMON_STARTING, DAEMON_EVENT_STARTING),
+                    MRP_EVENT(MRP_DAEMON_RUNNING , DAEMON_EVENT_RUNNING ),
+                    MRP_EVENT(MRP_DAEMON_STOPPING, DAEMON_EVENT_STOPPING));
+
+
+static int emit_daemon_event(mrp_context_t *ctx, int idx)
+{
+    mrp_event_bus_t *bus   = ctx->daemon_bus;
+    uint32_t         id    = daemon_events[idx].id;
+    int              flags = MRP_EVENT_SYNCHRONOUS;
+
+    return mrp_event_emit_msg(bus, id, flags, MRP_MSG_END);
+}
+
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml  = mrp_get_sighandler_mainloop(h);
+    mrp_context_t  *ctx = (mrp_context_t *)user_data;
+
+    MRP_UNUSED(ctx);
+
+    switch (signum) {
+    case SIGINT:
+        mrp_log_info("Got SIGINT, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+
+    case SIGTERM:
+        mrp_log_info("Got SIGTERM, stopping...");
+        mrp_mainloop_quit(ml, 0);
+        break;
+    }
+}
+
+
+static mrp_context_t *create_context(void)
+{
+    mrp_context_t *ctx;
+
+    ctx = mrp_context_create();
+
+    if (ctx != NULL) {
+        ctx->daemon_bus = mrp_event_bus_get(ctx->ml, MRP_DAEMON_BUS);
+        return ctx;
+    }
+    else
+        mrp_log_error("Failed to create murphy main context.");
+
+    exit(1);
+}
+
+
+static void setup_signals(mrp_context_t *ctx)
+{
+    mrp_add_sighandler(ctx->ml, SIGINT , signal_handler, ctx);
+    mrp_add_sighandler(ctx->ml, SIGTERM, signal_handler, ctx);
+}
+
+
+static void parse_cmdline(mrp_context_t *ctx, int argc, char **argv, char **env)
+{
+    mrp_parse_cmdline(ctx, argc, argv, env);
+}
+
+
+static void load_configuration(mrp_context_t *ctx)
+{
+    mrp_cfgfile_t *cfg;
+
+    mrp_context_setstate(ctx, MRP_STATE_LOADING);
+    emit_daemon_event(ctx, DAEMON_EVENT_LOADING);
+
+    cfg = mrp_parse_cfgfile(ctx->config_file);
+
+    if (cfg != NULL) {
+        mrp_log_info("Blacklisted plugins of any type: %s",
+                     ctx->blacklist_plugins ? ctx->blacklist_plugins:"<none>");
+        mrp_log_info("Blacklisted builtin plugins: %s",
+                     ctx->blacklist_builtin ? ctx->blacklist_builtin:"<none>");
+        mrp_log_info("Blacklisted dynamic plugins: %s",
+                     ctx->blacklist_dynamic ? ctx->blacklist_dynamic:"<none>");
+        mrp_log_info("Whitelisted plugins of any type: %s",
+                     ctx->whitelist_plugins ? ctx->whitelist_plugins:"<none>");
+        mrp_log_info("Whitelisted builtin plugins: %s",
+                     ctx->whitelist_builtin ? ctx->whitelist_builtin:"<none>");
+        mrp_log_info("Whitelisted dynamic plugins: %s",
+                     ctx->whitelist_dynamic ? ctx->whitelist_dynamic:"<none>");
+
+        mrp_block_blacklisted_plugins(ctx);
+
+        if (!mrp_exec_cfgfile(ctx, cfg)) {
+            mrp_log_error("Failed to execute configuration.");
+            exit(1);
+        }
+    }
+    else {
+        mrp_log_error("Failed to parse configuration file '%s'.",
+                      ctx->config_file);
+        exit(1);
+    }
+}
+
+
+static void create_ruleset(mrp_context_t *ctx)
+{
+    ctx->r = mrp_resolver_create(ctx);
+}
+
+
+static void load_ruleset(mrp_context_t *ctx)
+{
+    if (ctx->resolver_ruleset != NULL) {
+        if (mrp_resolver_parse(ctx->r, ctx, ctx->resolver_ruleset))
+            mrp_log_info("Loaded resolver ruleset '%s'.",
+                         ctx->resolver_ruleset);
+        else {
+            mrp_log_error("Failed to load resolver ruleset '%s'.",
+                          ctx->resolver_ruleset);
+            exit(1);
+        }
+    }
+}
+
+
+static void start_plugins(mrp_context_t *ctx)
+{
+    mrp_context_setstate(ctx, MRP_STATE_STARTING);
+    emit_daemon_event(ctx, DAEMON_EVENT_STARTING);
+
+    if (mrp_start_plugins(ctx))
+        mrp_log_info("Successfully started all loaded plugins.");
+    else {
+        mrp_log_error("Some plugins failed to start.");
+        exit(1);
+    }
+}
+
+
+static void setup_logging(mrp_context_t *ctx)
+{
+    const char *target;
+
+    target = mrp_log_parse_target(ctx->log_target);
+
+    if (!target)
+        mrp_log_error("invalid log target '%s'", ctx->log_target);
+    else
+        mrp_log_set_target(target);
+}
+
+static void daemonize(mrp_context_t *ctx)
+{
+    if (!ctx->foreground) {
+        mrp_log_info("Switching to daemon mode.");
+
+        if (!mrp_daemonize("/", "/dev/null", "/dev/null")) {
+            mrp_log_error("Failed to daemonize.");
+            exit(1);
+        }
+    }
+}
+
+
+static void prepare_ruleset(mrp_context_t *ctx)
+{
+    if (ctx->r != NULL) {
+        if (mrp_resolver_prepare(ctx->r))
+            mrp_log_info("Ruleset prepared for resolution.");
+        else {
+            mrp_log_error("Failed to prepare ruleset for execution.");
+            exit(1);
+        }
+        if (!mrp_resolver_enable_autoupdate(ctx->r, "autoupdate")) {
+            mrp_log_error("Failed to enable resolver autoupdate.");
+            exit(1);
+        }
+    }
+}
+
+
+static void run_mainloop(mrp_context_t *ctx)
+{
+    mrp_context_setstate(ctx, MRP_STATE_RUNNING);
+    emit_daemon_event(ctx, DAEMON_EVENT_RUNNING);
+    mrp_mainloop_run(ctx->ml);
+}
+
+
+static void stop_plugins(mrp_context_t *ctx)
+{
+    MRP_UNUSED(ctx);
+
+    mrp_context_setstate(ctx, MRP_STATE_STOPPING);
+    emit_daemon_event(ctx, DAEMON_EVENT_STOPPING);
+}
+
+
+static void cleanup_context(mrp_context_t *ctx)
+{
+    mrp_log_info("Shutting down...");
+    mrp_context_destroy(ctx);
+}
+
+
+static void set_linebuffered(FILE *stream)
+{
+    fflush(stream);
+    setvbuf(stream, NULL, _IOLBF, 0);
+}
+
+
+static void set_nonbuffered(FILE *stream)
+{
+    fflush(stream);
+    setvbuf(stream, NULL, _IONBF, 0);
+}
+
+
+int main(int argc, char *argv[], char *envp[])
+{
+    mrp_context_t *ctx;
+
+    ctx = create_context();
+
+    setup_signals(ctx);
+    create_ruleset(ctx);
+    parse_cmdline(ctx, argc, argv, envp);
+    load_configuration(ctx);
+    start_plugins(ctx);
+    load_ruleset(ctx);
+    prepare_ruleset(ctx);
+    setup_logging(ctx);
+    daemonize(ctx);
+    set_linebuffered(stdout);
+    set_nonbuffered(stderr);
+    run_mainloop(ctx);
+    stop_plugins(ctx);
+
+    cleanup_context(ctx);
+
+    return 0;
+}
diff --git a/src/daemon/daemon.h b/src/daemon/daemon.h
new file mode 100644 (file)
index 0000000..20c0d97
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DAEMON_H__
+#define __MURPHY_DAEMON_H__
+
+/*
+ * names of daemon-related events we emit
+ */
+
+#define MRP_DAEMON_BUS      "daemon-bus"        /* bus for daemon events */
+#define MRP_DAEMON_LOADING  "daemon-loading"    /* loading configuration */
+#define MRP_DAEMON_STARTING "daemon-starting"   /* starting up (plugins) */
+#define MRP_DAEMON_RUNNING  "daemon-running"    /* about to run mainloop */
+#define MRP_DAEMON_STOPPING "daemon-stopping"   /* shutting down */
+
+#endif /* __MURPHY_DAEMON_H__ */
diff --git a/src/daemon/murphy-lua.conf b/src/daemon/murphy-lua.conf
new file mode 100644 (file)
index 0000000..39d21cf
--- /dev/null
@@ -0,0 +1,2 @@
+#set resolver-ruleset 'src/resolver/test-input'
+load-plugin lua config="src/daemon/murphy.lua"
diff --git a/src/daemon/murphy.conf b/src/daemon/murphy.conf
new file mode 100644 (file)
index 0000000..60b46cb
--- /dev/null
@@ -0,0 +1,51 @@
+set resolver-ruleset '/u/src/work/murphy/src/resolver/test-input'
+
+# try-load-plugin console
+try-load-plugin console        # address="tcp4:127.0.0.1:3000"
+                        # address="udp4:127.0.0.1:3000"
+                        # address="unxs:@/murphyd"
+                       # address="dbus:[session]@murphy.org/console
+
+# load two instances of the test plugin
+if plugin-exists test
+    load-plugin test string2='this is now string 2' boolean2=TRUE \
+                     int32=-981 string1="and this is string 1" \
+                    double=2.73 \
+                     object='{ "foo": "f o o", "bar": "b a r", "two": 2, \
+                               "array": [ 1, 2, 3, 4, "five", "six", ] }'
+    load-plugin test as test5 # foo=foo foobar=foobar
+    info "Successfully loaded two instances of test..."
+end
+
+# load the dbus and glib plugins if they exist
+if plugin-exists dbus
+    load-plugin dbus
+end
+
+# try loading the glib plugin, ignoring any errors
+try-load-plugin glib
+
+# load the murphy DB plugin if it exists
+if plugin-exists murphydb
+    load-plugin murpydb
+#else
+#    error "Could not find mandatory plugin murphydb, giving up..."
+end
+
+# load the native resource plugin if it exists
+if plugin-exists resource-native
+    load-plugin resource-native
+else
+    info "Could not find resource-native plugin"
+end
+
+#if plugin-exists resource-dbus
+#    try-load-plugin resource-dbus
+#end
+
+if plugin-exists domain-control
+    load-plugin domain-control
+else
+    info "No domain-control plugin found..."
+end
+
diff --git a/src/daemon/murphy.lua b/src/daemon/murphy.lua
new file mode 100644 (file)
index 0000000..d829682
--- /dev/null
@@ -0,0 +1,283 @@
+m = murphy.get()
+
+-- try loading console plugin
+m:try_load_plugin('console')
+
+--[[
+m:try_load_plugin('console', 'dbusconsole' , {
+    address = 'dbus:[session]@org.Murphy/console'
+})
+--]]
+
+m:try_load_plugin('console', 'webconsole', {
+                              address = 'wsck:127.0.0.1:3000/murphy',
+                              httpdir = 'src/plugins/console',
+--                              sslcert = 'src/plugins/console/console.crt',
+--                              sslpkey = 'src/plugins/console/console.key'
+         })
+
+m:try_load_plugin('systemd')
+
+-- load a test plugin
+if m:plugin_exists('test.disabled') then
+    m:load_plugin('test', {
+                       string2  = 'this is now string2',
+                       boolean2 = true,
+                       int32 = -981,
+                       double = 2.73,
+                       object = {
+                           foo = 1,
+                           bar = 'bar',
+                           foobar = 3.141,
+                           barfoo = 'bar foo',
+                           array = { 'one', 'two', 'three',
+                                     { 1, 'two', 3, 'four' } },
+                           yees = true,
+                           noou = false
+                       }
+                 })
+--    m:load_plugin('test', 'test2')
+--    m:info("Successfully loaded two instances of test...")
+end
+
+-- load the dbus plugin if it exists
+-- if m:plugin_exists('dbus') then
+--     m:load_plugin('dbus')
+-- end
+
+-- load glib plugin, ignoring any errors
+m:try_load_plugin('glib')
+
+-- load the native resource plugin
+if m:plugin_exists('resource-native') then
+    m:load_plugin('resource-native')
+    m:info("native resource plugin loaded")
+else
+    m:info("No native resource plugin found...")
+end
+
+-- load the dbus resource plugin
+if m:plugin_exists('resource-dbus') then
+    m:try_load_plugin('resource-dbus', {
+        dbus_bus = "system",
+        dbus_service = "org.Murphy",
+        dbus_track = true,
+        default_zone = "driver",
+        default_class = "implicit"
+      })
+    m:info("dbus resource plugin loaded")
+else
+    m:info("No dbus resource plugin found...")
+end
+
+-- load the WRT resource plugin
+if m:plugin_exists('resource-wrt') then
+    m:try_load_plugin('resource-wrt', {
+                          address = "wsck:127.0.0.1:4000/murphy",
+                          httpdir = "src/plugins/resource-wrt",
+--                          sslcert = 'src/plugins/resource-wrt/resource.crt',
+--                          sslpkey = 'src/plugins/resource-wrt/resource.key'
+                      })
+else
+    m:info("No WRT resource plugin found...")
+end
+
+-- load the domain control plugin if it exists
+if m:plugin_exists('domain-control') then
+    m:load_plugin('domain-control')
+else
+    m:info("No domain-control plugin found...")
+end
+
+-- load the domain control plugin if it exists
+if m:plugin_exists('domain-control') then
+    m:try_load_plugin('domain-control', 'wrt-export', {
+        external_address = '',
+        internal_address = '',
+        wrt_address = "wsck:127.0.0.1:5000/murphy",
+        httpdir     = "src/plugins/domain-control"
+    })
+else
+    m:info("No domain-control plugin found...")
+end
+
+
+-- define application classes
+application_class { name="interrupt", priority=99, modal=true , share=false, order="fifo" }
+application_class { name="navigator", priority=4 , modal=false, share=true , order="fifo" }
+application_class { name="phone"    , priority=3 , modal=false, share=true , order="lifo" }
+application_class { name="game"     , priority=2 , modal=false, share=true , order="lifo" }
+application_class { name="player"   , priority=1 , modal=false, share=true , order="lifo" }
+application_class { name="implicit" , priority=0 , modal=false, share=true , order="lifo" }
+
+-- define zone attributes
+zone.attributes {
+    type = {mdb.string, "common", "rw"},
+    location = {mdb.string, "anywhere", "rw"}
+}
+
+-- define zones
+zone {
+     name = "driver",
+     attributes = {
+         type = "common",
+         location = "front-left"
+     }
+}
+
+zone {
+     name = "passanger1",
+     attributes = {
+         type = "private",
+         location = "front-right"
+     }
+}
+
+zone {
+     name = "passanger2",
+     attributes = {
+         type = "private",
+         location = "back-left"
+     }
+}
+
+zone {
+     name = "passanger3",
+     attributes = {
+         type = "private",
+         location = "back-right"
+     }
+}
+
+zone {
+     name = "passanger4",
+     attributes = {
+         type = "private",
+         location = "back-left"
+     }
+}
+
+
+-- define resource classes
+resource.class {
+     name = "audio_playback",
+     shareable = true,
+     attributes = {
+         role = { mdb.string, "music", "rw" },
+         pid = { mdb.string, "<unknown>", "rw" },
+         policy = { mdb.string, "relaxed", "rw" }
+     }
+}
+
+resource.class {
+     name = "audio_recording",
+     shareable = false,
+     attributes = {
+         role = { mdb.string, "music", "rw" },
+         pid = { mdb.string, "<unknown>", "rw" },
+         policy = { mdb.string, "relaxed", "rw" }
+     }
+}
+
+resource.class {
+     name = "video_playback",
+     shareable = false,
+}
+
+resource.class {
+     name = "video_recording",
+     shareable = false
+}
+
+-- test for creating selections
+mdb.select {
+           name = "audio_owner",
+           table = "audio_playback_owner",
+           columns = {"application_class"},
+           condition = "zone_name = 'driver'",
+}
+
+element.lua {
+   name    = "speed2volume",
+   inputs  = { owner = mdb.select.audio_owner, param = 5 },
+   outputs = {  mdb.table { name = "speedvol",
+                           index = {"zone", "device"},
+                           columns = {{"zone", mdb.string, 16},
+                                      {"device", mdb.string, 16},
+                                      {"value", mdb.floating}},
+                            create = true
+                          }
+            },
+   update  = function(self)
+                if (self.inputs.owner.single_value) then
+                   print("*** element "..self.name.." update "..
+                          self.inputs.owner.single_value)
+                else
+                   print("*** element "..self.name.." update <nil>")
+                end
+            end
+}
+
+
+--
+
+json = m:JSON({ a = 'foo', b = 'bar', foobar = { 1, 2, 3, 5, 6 } })
+
+print(tostring(json))
+
+
+function connect_cb(self, peer, data)
+    print('incoming connection from ' .. peer .. ' on ' .. tostring(self))
+    accepted = self:accept()
+    print('accepted: ' .. tostring(accepted))
+end
+
+function closed_cb(self, error, data)
+    print('connection closed by peer')
+end
+
+function recv_cb(self, msg, data)
+    print('got message ' .. tostring(msg))
+end
+
+t = m:Transport({ connect = connect_cb,
+                  closed  = closed_cb,
+                  recv    = recv_cb,
+                  data    = 'foo',
+                  address = 'wsck:127.0.0.1:18081/ico_syc_protocol' })
+
+print(tostring(t))
+
+t:listen()
+
+print(tostring(t))
+
+print(t.accept)
+print(t.recv)
+
+function test_rs_create(iterations)
+    resourceHandler = function (rset) print(rset) end
+
+    for i = 1, iterations do
+        r = m:ResourceSet({
+            zone = "driver",
+            callback = resourceHandler,
+            application_class = "player"
+        })
+
+        if r then
+
+            r:addResource({
+                resource_name = "audio_playback",
+                mandatory = true
+            })
+
+            r.resources.audio_playback.attributes.pid = tostring(i)
+
+            print("pid: " .. r.resources.audio_playback.attributes.pid)
+            r:acquire()
+            r:release()
+        end
+        r = nil
+    end
+end
diff --git a/src/daemon/sample-config/cgroup-test.rules b/src/daemon/sample-config/cgroup-test.rules
new file mode 100644 (file)
index 0000000..9866992
--- /dev/null
@@ -0,0 +1,33 @@
+-- -*- mode: lua -*-
+
+if not loaded('system-monitor') then
+    return
+end
+
+cg = m:CGroupOpen({ type = 'cpuacct',
+                    name = 'murphy-test',
+                    mode = 'readonly',
+                    foo = 3,
+                    bar = 'foobar',
+                    foobar = 9.81         })
+
+print('cg: ' .. tostring(cg))
+print('cg.mode: ' .. tostring(cg.mode))
+print('cg.foo: ' .. tostring(cg.foo))
+print('cg.bar: ' .. tostring(cg.bar))
+print('cg.foobar: ' .. tostring(cg.foobar))
+
+--status = cg:add_process(4214)
+--cg.tasks = 4214
+print('cg.tasks: ' .. cg.tasks)
+
+
+cg1 = m:CGroupOpen({ type = 'cpu',
+                     name = 'test',
+                     mode = 'readwrite,create',
+                     Shares = 2048              })
+
+print('cg1: ' .. tostring(cg1))
+print('cg1.mode: ' .. cg1.mode)
+print('cg1.shares: ' .. cg1.Shares)
+print('cg1.tasks: ' .. cg1.tasks)
diff --git a/src/daemon/sample-config/common.cfg b/src/daemon/sample-config/common.cfg
new file mode 100644 (file)
index 0000000..a8e88ab
--- /dev/null
@@ -0,0 +1,56 @@
+-- -*- mode: lua -*-
+
+-- plugin optionality constants
+OPTIONAL  = 1                            -- mark a plugin optional
+IFEXISTS  = 2                            -- mark a plugin mandatory if present
+MANDATORY = 3                            -- mark a plugin mandatory
+
+-- function to try loading an optional plugin, ignoring errors
+function try_load(plugin, ...)
+   local a = {...}
+   m:info('* Trying to load (optional) plugin ' .. plugin)
+   if     #a == 0 then return m:try_load_plugin(plugin            )
+   elseif #a == 1 then return m:try_load_plugin(plugin, a[1]      )
+   else                return m:try_load_plugin(plugin, a[1], a[2])
+   end
+end
+
+-- function to load a plugin if it exists
+function load_if_exists(plugin, ...)
+   local a = {...}
+   if m:plugin_exists(plugin) then
+      m:info('* Loading (existing) plugin ' .. plugin)
+   else
+      return true
+   end
+   if     #a == 0 then return m:load_plugin(plugin            )
+   elseif #a == 1 then return m:load_plugin(plugin, a[1]      )
+   else                return m:load_plugin(plugin, a[1], a[2])
+   end
+end
+
+-- function to load a mandatory plugin
+function load(plugin, ...)
+   local a = {...}
+   m:info('* Loading (mandatory) plugin ' .. plugin)
+   if     #a == 0 then return m:load_plugin(plugin            )
+   elseif #a == 1 then return m:load_plugin(plugin, a[1]      )
+   else                return m:load_plugin(plugin, a[1], a[2])
+   end
+end
+
+-- function to check if a plugin has been successfully loaded (and running)
+function loaded(plugin)
+   return m:plugin_loaded(plugin)
+end
+
+-- function to include a file
+function include(file, necessity)
+   if necessity < MANDATORY then
+      m:info('* Trying to include (optional) ' .. file)
+      m:try_include(file)
+   else
+      m:info('* Including (mandatory) ' .. file)
+      m:include(file)
+   end
+end
diff --git a/src/daemon/sample-config/console.cfg b/src/daemon/sample-config/console.cfg
new file mode 100644 (file)
index 0000000..5cd02e2
--- /dev/null
@@ -0,0 +1,14 @@
+-- -*- mode: lua -*-
+
+-- load the oridnary console
+load('console')
+
+-- load a web console
+try_load('console',
+     'webconsole', {
+                address = 'wsck:127.0.0.1:3000/murphy',
+                httpdir = 'src/plugins/console',
+           --[[
+                sslcert = 'src/plugins/console/console.crt',
+                sslpkey = 'src/plugins/console/console.key'
+           --]]                                               })
diff --git a/src/daemon/sample-config/domain-control.cfg b/src/daemon/sample-config/domain-control.cfg
new file mode 100644 (file)
index 0000000..6183a02
--- /dev/null
@@ -0,0 +1,12 @@
+-- -*- mode: lua -*-
+
+-- load the domain control plugin if it exists
+load_if_exists('domain-control')
+
+-- load a domain-control instance for exposing data to the WRT
+try_load('domain-control',
+     'wrt-export', {
+        external_address = '',
+        internal_address = '',
+        wrt_address = "wsck:127.0.0.1:5000/murphy",
+        httpdir     = "src/plugins/domain-control"  })
diff --git a/src/daemon/sample-config/glib.cfg b/src/daemon/sample-config/glib.cfg
new file mode 100644 (file)
index 0000000..c514988
--- /dev/null
@@ -0,0 +1,4 @@
+-- -*- mode: lua -*-
+
+-- try loading the GLIB plugin
+try_load('glib')
diff --git a/src/daemon/sample-config/main.cfg b/src/daemon/sample-config/main.cfg
new file mode 100644 (file)
index 0000000..2419396
--- /dev/null
@@ -0,0 +1,42 @@
+-- -*- mode: lua -*-
+
+--
+-- pull in the common configuration support bits
+--
+m = murphy.get()
+m:include_once('common.cfg')
+
+
+--
+-- configuration files and rulesets to pull in
+--
+config = {
+   { 'console'          , OPTIONAL  },
+   { 'systemd'          , OPTIONAL  },
+   { 'glib'             , OPTIONAL  },
+   { 'resource'         , MANDATORY },
+   { 'domain-control'   , MANDATORY },
+   { 'system-controller', OPTIONAL  },
+   { 'system-monitor'   , OPTIONAL  },
+}
+
+ruleset = {
+   { 'speed-volume'     , MANDATORY },
+   { 'system-monitor'   , OPTIONAL  },
+   { 'cgroup-test'      , OPTIONAL  },
+   { 'timer-test'       , OPTIONAL  },
+
+}
+
+
+--
+-- pull in the given configuration and ruleset files
+--
+
+for idx,cfg in ipairs(config) do
+   include(cfg[1] .. '.cfg', cfg[2])
+end
+
+for idx,rules in ipairs(ruleset) do
+   include(rules[1] .. '.rules', rules[2])
+end
diff --git a/src/daemon/sample-config/murphy.cfg b/src/daemon/sample-config/murphy.cfg
new file mode 100644 (file)
index 0000000..dcdb9aa
--- /dev/null
@@ -0,0 +1 @@
+load-plugin lua config="src/daemon/sample-config/main.cfg"
diff --git a/src/daemon/sample-config/resource.cfg b/src/daemon/sample-config/resource.cfg
new file mode 100644 (file)
index 0000000..07b6890
--- /dev/null
@@ -0,0 +1,136 @@
+-- -*- mode: lua -*-
+
+-- load the native resource plugin
+load_if_exists('resource-native')
+
+-- load the D-Bus resource plugin if we have it and D-Bus
+try_load('resource-dbus', {
+    dbus_bus = "session",
+    dbus_service = "org.Murphy",
+    dbus_track = true,
+    default_zone = "driver",
+    default_class = "implicit"
+})
+
+-- try loading the WRT resource plugin
+try_load('resource-wrt', {
+    address = "wsck:127.0.0.1:4000/murphy",
+    httpdir = "src/plugins/resource-wrt",
+    --[[
+        sslcert = 'src/plugins/resource-wrt/resource.crt',
+        sslpkey = 'src/plugins/resource-wrt/resource.key',
+    --]]
+})
+
+-- load the IVI resource manager if it is available
+load_if_exists('ivi-resource-manager')
+
+--
+-- define application classes
+--
+application_class { name="interrupt", priority=99,
+                    modal=true , share=false, order="fifo" }
+application_class { name="navigator", priority=4 ,
+                    modal=false, share=true , order="fifo" }
+application_class { name="phone"    , priority=3 ,
+                    modal=false, share=true , order="lifo" }
+application_class { name="game"     , priority=2 ,
+                    modal=false, share=true , order="lifo" }
+application_class { name="player"   , priority=1 ,
+                    modal=false, share=true , order="lifo" }
+application_class { name="implicit" , priority=0 ,
+                    modal=false, share=true , order="lifo" }
+
+--
+-- define zones
+--
+zone.attributes {
+    type = {mdb.string, "common", "rw"},
+    location = {mdb.string, "anywhere", "rw"}
+}
+
+zone {
+     name = "driver",
+     attributes = {
+         type = "common",
+         location = "front-left"
+     }
+}
+
+zone {
+     name = "passanger1",
+     attributes = {
+         type = "private",
+         location = "front-right"
+     }
+}
+
+zone {
+     name = "passanger2",
+     attributes = {
+         type = "private",
+         location = "back-left"
+     }
+}
+
+zone {
+     name = "passanger3",
+     attributes = {
+         type = "private",
+         location = "back-right"
+     }
+}
+
+zone {
+     name = "passanger4",
+     attributes = {
+         type = "private",
+         location = "back-left"
+     }
+}
+
+
+--
+-- define resource classes
+--
+if not m:plugin_loaded('ivi-resource-manager') then
+   resource.class {
+        name = "audio_playback",
+        shareable = true,
+        attributes = {
+            role = { mdb.string, "music", "rw" },
+            pid = { mdb.string, "<unknown>", "rw" },
+            policy = { mdb.string, "relaxed", "rw" }
+        }
+   }
+end
+
+resource.class {
+     name = "audio_recording",
+     shareable = false,
+     attributes = {
+         role = { mdb.string, "music", "rw" },
+         pid = { mdb.string, "<unknown>", "rw" },
+         policy = { mdb.string, "relaxed", "rw" }
+     }
+}
+
+resource.class {
+     name = "video_playback",
+     shareable = false,
+}
+
+resource.class {
+     name = "video_recording",
+     shareable = false,
+}
+
+resource.class {
+     name = "speech_synthesis",
+     shareable = true,
+}
+
+resource.class {
+     name = "speech_recognition",
+     shareable = true,
+}
diff --git a/src/daemon/sample-config/speed-volume.rules b/src/daemon/sample-config/speed-volume.rules
new file mode 100644 (file)
index 0000000..3038ff2
--- /dev/null
@@ -0,0 +1,28 @@
+-- test for creating selections
+mdb.select {
+           name = "audio_owner",
+           table = "audio_playback_owner",
+           columns = {"application_class"},
+           condition = "zone_name = 'driver'",
+}
+
+element.lua {
+   name    = "speed2volume",
+   inputs  = { owner = mdb.select.audio_owner, param = 5 },
+   outputs = {  mdb.table { name = "speedvol",
+                           index = {"zone", "device"},
+                           columns = {{"zone", mdb.string, 16},
+                                      {"device", mdb.string, 16},
+                                      {"value", mdb.floating}},
+                            create = true
+                          }
+            },
+   update  = function(self)
+                if (self.inputs.owner.single_value) then
+                   print("*** element "..self.name.." update "..
+                          self.inputs.owner.single_value)
+                else
+                   print("*** element "..self.name.." update <nil>")
+                end
+            end
+}
diff --git a/src/daemon/sample-config/system-controller.cfg b/src/daemon/sample-config/system-controller.cfg
new file mode 100644 (file)
index 0000000..d575ebb
--- /dev/null
@@ -0,0 +1,3 @@
+-- -*- mode: lua -*-
+
+load_if_exists('system-controller')
diff --git a/src/daemon/sample-config/system-monitor.cfg b/src/daemon/sample-config/system-monitor.cfg
new file mode 100644 (file)
index 0000000..266a94f
--- /dev/null
@@ -0,0 +1,4 @@
+-- -*- mode: lua -*-
+
+-- try loading the system-monitor plugin
+load_if_exists('system-monitor')
diff --git a/src/daemon/sample-config/system-monitor.rules b/src/daemon/sample-config/system-monitor.rules
new file mode 100644 (file)
index 0000000..1e69662
--- /dev/null
@@ -0,0 +1,67 @@
+-- -*- mode: lua -*-
+
+if not loaded('system-monitor') then
+   return
+end
+
+-- get and configure system-monitor
+sm = m:get_system_monitor()
+
+sm.polling = 1000                        -- poll 1 / second
+
+--
+-- monitor overall CPU load (load of the virtual combined CPU)
+--
+sm:CpuWatch({
+         cpu    = 'cpu',                          -- virtual 'combined' CPU
+         sample = 'load',                         -- monitor 'load'
+         limits = {                               -- load threshold %'s
+             [1] = { label = 'idle'    , limit =  5 },
+             [2] = { label = 'low'     , limit = 20 },
+             [3] = { label = 'moderate', limit = 40 },
+             [4] = { label = 'medium'  , limit = 50 },
+             [5] = { label = 'high'    , limit = 80 },
+             [6] = { label = 'critical'             }
+         },
+         window = 15000,                          -- use an EWMA of 15 secs
+         notify = function (w, prev, curr)        -- threshold change callback
+            print('CPU load change: ' .. prev .. ' -> ' .. curr)
+         end
+    })
+
+
+--
+-- monitor length of the writeback queue
+--
+sm:MemWatch({
+         sample = 'Writeback',
+         limits = {                               -- pressure thresholds
+             [1] = { label = 'none'    , limit =  1024 },
+             [2] = { label = 'low'     , limit =  8192 },
+             [3] = { label = 'medium'  , limit =  '1M' },
+             [4] = { label = 'high'    , limit =  '4M' },
+             [5] = { label = 'critical', limit = '16M' }
+         },
+         window = 0,                              -- don't average/integrate
+         notify = function (w, prev, curr)        -- threshold change callback
+            print(w.sample .. ' change: ' .. prev .. ' -> ' .. curr)
+         end
+     })
+
+--
+-- monitor the amount of dirty memory
+--
+sm:MemWatch({
+         sample = 'Dirty',
+         limits = {                               -- pressure thresholds
+             [1] = { label = 'none'    , limit =  1024 },
+             [2] = { label = 'low'     , limit =  8192 },
+             [3] = { label = 'medium'  , limit =  '1M' },
+             [4] = { label = 'high'    , limit =  '4M' },
+             [5] = { label = 'critical', limit = '16M' }
+         },
+         window = 10000,                           -- use an EWMA of 10 secs
+         notify = function (w, prev, curr)         -- threshold change callback
+            print(w.sample .. ' change: ' .. prev .. ' -> ' .. curr)
+         end
+     })
diff --git a/src/daemon/sample-config/systemd.cfg b/src/daemon/sample-config/systemd.cfg
new file mode 100644 (file)
index 0000000..e21409d
--- /dev/null
@@ -0,0 +1,4 @@
+-- -*- mode: lua -*-
+
+-- try loading the systemd plugin (for systemd-aware logging)
+try_load('systemd')
diff --git a/src/daemon/sample-config/timer-test.rules b/src/daemon/sample-config/timer-test.rules
new file mode 100644 (file)
index 0000000..81b8ade
--- /dev/null
@@ -0,0 +1,65 @@
+timer = m:Timer({
+   interval = 1000,
+   callback = function (t)
+       print('timer-test<'..t.data..'> #' .. tostring(t.count))
+       print('    foo = ' .. t.foo .. ', bar = ' .. t.bar)
+
+       t.count = t.count + 1
+
+       if t.count == 5 then
+          t.interval = 500
+       else
+          if t.count == 10 then
+             t.interval = 250
+          else
+             if t.count == 20 then
+                t:stop()
+             end
+          end
+       end
+   end,
+   oneshot = false,
+   data    = 'timer-data',
+   count   = 0,
+   foo     = 'bar',
+   bar     = 'foo'
+})
+
+
+def = m:Deferred({
+    disabled = true,
+    data     = 'test-deferred',
+    callback = function (d)
+       print('deferred <'..d.data..'> callback #'.. tostring(d.count))
+       print('    xyzzy = ' .. d.xyzzy .. ', what = ' .. d.what)
+
+       if d.count == 10 then
+          d.disabled = true
+       else
+          if d.count == 20 then
+             d:disable()
+             d.count = 1
+          end
+       end
+
+       d.count = d.count + 1
+    end,
+    count = 1,
+    xyzzy = 'blah',
+    what  = 'ever...'
+
+})
+
+
+sh = m:SigHandler({
+    signal = 17,
+    data     = 'test-sighandler',
+    callback = function (sh)
+       print('SigHandler <'..sh.data..'> callback #'.. tostring(sh.count))
+       print('    xyzzy = ' .. sh.attr1 .. ', what = ' .. sh.attr2)
+       sh.count = sh.count + 1
+    end,
+    count = 1,
+    attr1 = 'sh-attr-1',
+    attr2 = 'sh-attr-2'
+})
diff --git a/src/daemon/tests/Makefile.am b/src/daemon/tests/Makefile.am
new file mode 100644 (file)
index 0000000..b47500b
--- /dev/null
@@ -0,0 +1 @@
+AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir)
diff --git a/src/murphy-db/Makefile.am b/src/murphy-db/Makefile.am
new file mode 100644 (file)
index 0000000..38d4a94
--- /dev/null
@@ -0,0 +1,21 @@
+if HAVE_CHECK
+TESTDIR = tests
+else
+TESTDIR =
+endif
+
+SUBDIRS = mdb mqi mql include $(TESTDIR)
+
+pkgconfigdir = $(libdir)/pkgconfig
+nodist_pkgconfig_DATA = murphy-db.pc
+
+murphy-db.pc: murphy-db.pc.in
+       sed -e 's![@]prefix[@]!$(prefix)!g' \
+            -e 's![@]exec_prefix[@]!$(exec_prefix)!g' \
+            -e 's![@]includedir[@]!$(includedir)!g' \
+            -e 's![@]libdir[@]!$(libdir)!g' \
+            -e 's![@]PACKAGE_VERSION[@]!$(PACKAGE_VERSION)!g' \
+        $< > $@
+
+clean-local:
+       rm -f *~ murphy-db.pc
diff --git a/src/murphy-db/include/Makefile.am b/src/murphy-db/include/Makefile.am
new file mode 100644 (file)
index 0000000..9d20679
--- /dev/null
@@ -0,0 +1,6 @@
+nobase_include_HEADERS = murphy-db/mqi.h \
+                         murphy-db/mqi-types.h \
+                         murphy-db/mql.h \
+                         murphy-db/mql-statement.h \
+                         murphy-db/mql-result.h \
+                         murphy-db/mql-trigger.h
diff --git a/src/murphy-db/include/murphy-db/assert.h b/src/murphy-db/include/murphy-db/assert.h
new file mode 100644 (file)
index 0000000..e3afc79
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_ASSERT_H__
+#define __MDB_ASSERT_H__
+
+
+#define MDB_ASSERT(cond, errcode, retval) \
+    do {                                  \
+        if (!(cond)) {                    \
+            errno = errcode;              \
+            return retval;                \
+        }                                 \
+    } while(0)
+
+#define MDB_CHECKARG(cond, retval)      MDB_ASSERT(cond, EINVAL, retval)
+#define MDB_PREREQUISITE(cond, retval)  MDB_ASSERT(cond, EIO, retval)
+
+
+#endif  /* __MDB_ASSERT_H__ */
diff --git a/src/murphy-db/include/murphy-db/handle.h b/src/murphy-db/include/murphy-db/handle.h
new file mode 100644 (file)
index 0000000..3a75ce6
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_HANDLE_H__
+#define __MDB_HANDLE_H__
+
+#include <stdint.h>
+
+#define MDB_HANDLE_INVALID (~((mdb_handle_t)0))
+
+#define MDB_HANDLE_MAP_CREATE  mdb_handle_map_create
+#define MDB_HANDLE_MAP_DESTROY mdb_handle_map_destroy
+
+typedef uint32_t  mdb_handle_t;
+typedef struct mdb_handle_map_s   mdb_handle_map_t;
+
+mdb_handle_map_t *mdb_handle_map_create(void);
+int mdb_handle_map_destroy(mdb_handle_map_t *);
+
+mdb_handle_t mdb_handle_add(mdb_handle_map_t *, void *);
+void *mdb_handle_delete(mdb_handle_map_t *, mdb_handle_t);
+void *mdb_handle_get_data(mdb_handle_map_t *, mdb_handle_t);
+int mdb_handle_print(mdb_handle_map_t *, char *, int);
+
+
+#endif /* __MDB_HANDLE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/hash.h b/src/murphy-db/include/murphy-db/hash.h
new file mode 100644 (file)
index 0000000..2c35e3c
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_HASH_H__
+#define __MDB_HASH_H__
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/list.h>
+
+
+#define MDB_HASH_TABLE_CREATE(type, max_entries)         \
+    mdb_hash_table_create(max_entries,                   \
+                          mdb_hash_function_##type,      \
+                          mqi_data_compare_##type,       \
+                          mqi_data_print_##type)
+
+#define MDB_HASH_TABLE_DESTROY(h)                        \
+    mdb_hash_table_destroy(h)
+
+#define MDB_HASH_TABLE_FOR_EACH_WITH_KEY(htbl, data, key, cursor)       \
+    for (cursor = NULL;                                                 \
+        (data = mdb_hash_table_iterate(htbl, (void **)&key, &cursor)); )
+#define MDB_HASH_TABLE_FOR_EACH_WITH_KEY_SAFE(htbl, data, key, cursor)  \
+    MDB_HASH_TABLE_FOR_EACH_WITH_KEY(htbl, data, key, cursor)
+
+#define MDB_HASH_TABLE_FOR_EACH(htbl, data, cursor)                     \
+    for (cursor = NULL;  (data = mdb_hash_table_iterate(htbl, NULL, &cursor));)
+#define MDB_HASH_TABLE_FOR_EACH_SAFE(htbl, data, cursor)                \
+    MDB_HASH_TABLE_FOR_EACH(htbl, data, key, cursor)
+
+typedef struct mdb_hash_s mdb_hash_t;
+
+typedef int  (*mdb_hash_function_t)(int, int, int, void *);
+typedef int  (*mdb_hash_compare_t)(int, void *, void *);
+typedef int  (*mdb_hash_print_t)(void *, char *, int);
+
+
+mdb_hash_t *mdb_hash_table_create(int, mdb_hash_function_t, mdb_hash_compare_t,
+                                  mdb_hash_print_t);
+int mdb_hash_table_destroy(mdb_hash_t *);
+int mdb_hash_table_reset(mdb_hash_t *);
+void *mdb_hash_table_iterate(mdb_hash_t *, void **, void **);
+int mdb_hash_table_print(mdb_hash_t *, char *, int);
+
+int mdb_hash_add(mdb_hash_t *, int, void *, void *);
+void *mdb_hash_delete(mdb_hash_t *, int, void *);
+void *mdb_hash_get_data(mdb_hash_t *, int, void *);
+
+int mdb_hash_function_integer(int, int, int, void *);
+int mdb_hash_function_unsignd(int, int, int, void *);
+int mdb_hash_function_string(int, int, int, void *);
+int mdb_hash_function_pointer(int, int, int, void *);
+int mdb_hash_function_varchar(int, int, int, void *);
+int mdb_hash_function_blob(int, int, int, void *);
+
+
+#endif /* __MDB_HASH_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/list.h b/src/murphy-db/include/murphy-db/list.h
new file mode 100644 (file)
index 0000000..8c2668b
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_LIST_H__
+#define __MDB_LIST_H__
+
+#include <murphy-db/mqi-types.h>
+
+#define MDB_LIST_RELOCATE(structure, member, ptr)                       \
+    ((structure *)((char *)ptr - MQI_OFFSET(structure, member)))
+
+
+#define MDB_DLIST_HEAD(name)   mdb_dlist_t name = { &(name), &(name) }
+
+#define MDB_DLIST_INIT(self)                                            \
+    do {                                                                \
+        (&(self))->prev = &(self);                                      \
+        (&(self))->next = &(self);                                      \
+    } while(0)
+
+#define MDB_DLIST_EMPTY(name)  ((&(name))->next == &(name))
+
+#define MDB_DLIST_FOR_EACH(structure, member, pos, head)                \
+    for (pos = MDB_LIST_RELOCATE(structure, member, (head)->next);      \
+         &pos->member != (head);                                        \
+         pos = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_FOR_EACH_SAFE(structure, member, pos, n, head)        \
+    for (pos = MDB_LIST_RELOCATE(structure, member, (head)->next),      \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next);  \
+         &pos->member != (head);                                        \
+         pos = n,                                                       \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_FOR_EACH_NOHEAD(structure, member, pos, start)        \
+    for (pos = start;                                                   \
+         &(pos)->member != &(start)->member;                            \
+         pos = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_FOR_EACH_NOHEAD_SAFE(structure, member, pos,n, start) \
+    for (pos = start,                                                   \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next);  \
+         &pos->member != &(start)->member;                              \
+         pos = n,                                                       \
+           n = MDB_LIST_RELOCATE(structure, member, pos->member.next))
+
+#define MDB_DLIST_INSERT_BEFORE(structure, member, new, before)         \
+    do {                                                                \
+        mdb_dlist_t *after = (before)->prev;                            \
+        after->next = &(new)->member;                                   \
+        (new)->member.next = before;                                    \
+        (before)->prev = &(new)->member;                                \
+        (new)->member.prev = after;                                     \
+    } while(0)
+#define MDB_DLIST_APPEND(structure, member, new, head)                  \
+    MDB_DLIST_INSERT_BEFORE(structure, member, new, head)
+
+#define MDB_DLIST_INSERT_AFTER(structure, member, new, after)           \
+    do {                                                                \
+        mdb_dlist_t *before = (after)->next;                            \
+        (after)->next = &((new)->member);                               \
+        (new)->member.next = before;                                    \
+        before->prev = &((new)->member);                                \
+        (new)->member.prev = after;                                     \
+    } while(0)
+#define MDB_DLIST_PREPEND(structure, member, new, head)                 \
+    MDB_DLIST_INSERT_AFTER(structure, member, new, head)
+
+
+#define MDB_DLIST_UNLINK(structure, member, elem)                       \
+    do {                                                                \
+        mdb_dlist_t *after  = (elem)->member.prev;                      \
+        mdb_dlist_t *before = (elem)->member.next;                      \
+        after->next = before;                                           \
+        before->prev = after;                                           \
+        (elem)->member.prev = (elem)->member.next = &(elem)->member;    \
+    } while(0)
+
+
+typedef struct mdb_dlist_s {
+    struct mdb_dlist_s *prev;
+    struct mdb_dlist_s *next;
+} mdb_dlist_t;
+
+
+
+
+#endif /* __MDB_LIST_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mdb.h b/src/murphy-db/include/murphy-db/mdb.h
new file mode 100644 (file)
index 0000000..1b53a41
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_MDB_H__
+#define __MDB_MDB_H__
+
+#include <murphy-db/mqi-types.h>
+
+typedef struct mdb_table_s mdb_table_t;
+
+
+int mdb_trigger_add_column_callback(mdb_table_t *, int, mqi_trigger_cb_t,
+                                  void *, mqi_column_desc_t *);
+int mdb_trigger_delete_column_callback(mdb_table_t *, int,
+                                     mqi_trigger_cb_t, void *);
+int mdb_trigger_add_row_callback(mdb_table_t *, mqi_trigger_cb_t, void *,
+                               mqi_column_desc_t *);
+int mdb_trigger_delete_row_callback(mdb_table_t *, mqi_trigger_cb_t, void *);
+int mdb_trigger_add_table_callback(mqi_trigger_cb_t, void *);
+int mdb_trigger_delete_table_callback(mqi_trigger_cb_t, void *);
+int mdb_trigger_add_transaction_callback(mqi_trigger_cb_t, void *);
+int mdb_trigger_delete_transaction_callback(mqi_trigger_cb_t, void *);
+
+uint32_t mdb_transaction_begin(void);
+int mdb_transaction_commit(uint32_t);
+int mdb_transaction_rollback(uint32_t);
+uint32_t mdb_transaction_get_depth(void);
+
+
+mdb_table_t *mdb_table_create(char *, char **, mqi_column_def_t *);
+int mdb_table_register_handle(mdb_table_t *, mqi_handle_t);
+int mdb_table_drop(mdb_table_t *);
+int mdb_table_create_index(mdb_table_t *, char **);
+int mdb_table_describe(mdb_table_t *, mqi_column_def_t *, int);
+int mdb_table_insert(mdb_table_t *, int, mqi_column_desc_t *, void **);
+int mdb_table_select(mdb_table_t *, mqi_cond_entry_t *,
+                     mqi_column_desc_t *, void *, int, int);
+int mdb_table_select_by_index(mdb_table_t *, mqi_variable_t *,
+                              mqi_column_desc_t *, void *);
+int mdb_table_update(mdb_table_t *, mqi_cond_entry_t *,
+                     mqi_column_desc_t *, void *);
+int mdb_table_delete(mdb_table_t *, mqi_cond_entry_t *);
+
+
+mdb_table_t *mdb_table_find(char *);
+int mdb_table_get_column_index(mdb_table_t *, char *);
+int mdb_table_get_size(mdb_table_t *);
+char *mdb_table_get_column_name(mdb_table_t *, int);
+mqi_data_type_t mdb_table_get_column_type(mdb_table_t *, int);
+int mdb_table_get_column_size(mdb_table_t *, int);
+uint32_t mdb_table_get_stamp(mdb_table_t *);
+int mdb_table_print_rows(mdb_table_t *, char *, int);
+
+
+#endif /* __MDB_MDB_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mqi-types.h b/src/murphy-db/include/murphy-db/mqi-types.h
new file mode 100644 (file)
index 0000000..8ad595e
--- /dev/null
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQI_TYPES_H__
+#define __MQI_TYPES_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/** macro to tag a variable unused */
+#define MQI_UNUSED(var) ((void)var)
+
+/** maximum number of rows a query can produce */
+#define MQI_QUERY_RESULT_MAX   8192
+/** the maximum number columns a table can have */
+#define MQI_COLUMN_MAX         ((int)(sizeof(mqi_bitfld_t) * 8))
+/** maximum length of a condition table (i.e. array of mqi_cond_entry_t) */
+#define MQI_COND_MAX           64
+#define MQL_PARAMETER_MAX      16
+/** maximum depth for nested transactions */
+#define MQI_TXDEPTH_MAX        16
+
+/**
+ * mqi_handle_t value for nonexisting handle. Zero is a valid handle
+ * thus casting a zero to mqi_handle_t will produce a valid handle
+ * (remeber this when using static mqi_handle_t).
+ */
+#define MQI_HANDLE_INVALID     (~((mqi_handle_t)0))
+
+/**
+ * Stamp for a non-existing table or a table without any inserts ever.
+ */
+#define MQI_STAMP_NONE ((uint32_t)0)
+
+
+#define MQI_DIMENSION(array)  \
+    (sizeof(array) / sizeof(array[0]))
+
+#define MQI_OFFSET(structure, member)  \
+    ((int)((char *)((&((structure *)0)->member)) - (char *)0))
+
+#define MQI_BIT(b)            (((mqi_bitfld_t)1) << (b))
+
+#define MQL_BIND_INDEX_BITS   8
+#define MQL_BIND_INDEX_MAX    (1 << MQL_BIND_INDEX_BITS)
+#define MQL_BIND_INDEX_MASK   (MQL_BIND_INDEX_MAX - 1)
+
+#define MQL_BINDABLE          (1 << (MQL_BIND_INDEX_BITS + 0))
+#define MQL_BIND_INDEX(v)     ((v) & MQL_BIND_INDEX_MASK)
+
+#define MQI_COLUMN_KEY        (1UL << 0)
+#define MQI_COLUMN_AUTOINCR   (1UL << 1)
+
+enum mqi_data_type_e {
+    mqi_error = -1,    /* not a data type; used to return error conditions */
+    mqi_unknown = 0,
+    mqi_varchar,
+    mqi_string = mqi_varchar,
+    mqi_integer,
+    mqi_unsignd,
+    mqi_floating,
+    mqi_blob,
+};
+
+enum mqi_operator_e {
+    mqi_done = 0,
+    mqi_end  = mqi_done,
+    mqi_begin,                  /* expression start */
+    mqi_and,
+    mqi_or,
+    mqi_less,
+    mqi_leq,
+    mqi_eq,
+    mqi_geq,
+    mqi_gt,
+    mqi_not,
+    mqi_operator_max
+};
+
+enum mqi_cond_entry_type_e {
+    mqi_operator,
+    mqi_variable,
+    mqi_column
+};
+
+enum mqi_event_type_e {
+    mqi_event_unknown = 0,
+    mqi_column_changed,
+    mqi_row_inserted,
+    mqi_row_deleted,
+    mqi_table_created,
+    mqi_table_dropped,
+    mqi_transaction_start,
+    mqi_transaction_end
+};
+
+
+
+typedef uint32_t  mqi_handle_t;
+typedef uint32_t  mqi_bitfld_t;
+
+typedef enum mqi_data_type_e         mqi_data_type_t;
+typedef struct mqi_column_def_s      mqi_column_def_t;
+typedef struct mqi_column_desc_s     mqi_column_desc_t;
+
+typedef enum mqi_operator_e          mqi_operator_t;
+typedef struct mqi_variable_s        mqi_variable_t;
+typedef enum mqi_cond_entry_type_e   mqi_cond_entry_type_t;
+typedef struct mqi_cond_entry_s      mqi_cond_entry_t;
+
+typedef enum mqi_event_type_e        mqi_event_type_t;
+typedef union mqi_event_u            mqi_event_t;
+
+typedef struct mqi_change_table_s    mqi_change_table_t;
+typedef struct mqi_change_select_s   mqi_change_select_t;
+typedef struct mqi_change_coldsc_s   mqi_change_coldsc_t;
+typedef union mqi_change_data_u      mqi_change_data_t;
+typedef struct mqi_change_value_s    mqi_change_value_t;
+
+typedef struct mqi_column_event_s    mqi_column_event_t;
+typedef struct mqi_row_event_s       mqi_row_event_t;
+typedef struct mqi_table_event_s     mqi_table_event_t;
+typedef struct mqi_transact_event_s  mqi_transact_event_t;
+
+typedef void (*mqi_trigger_cb_t)(mqi_event_t *, void *);
+
+
+
+struct mqi_column_def_s {
+    const char      *name;
+    mqi_data_type_t  type;
+    int              length;
+    uint32_t         flags;
+};
+
+struct mqi_column_desc_s {
+    int     cindex;              /* column index */
+    int     offset;              /* offset within the data struct */
+};
+
+struct mqi_variable_s {
+    mqi_data_type_t  type;
+    uint32_t         flags;
+    union {
+        char       **varchar;
+        int32_t     *integer;
+        uint32_t    *unsignd;
+        double      *floating;
+        void       **blob;
+        void        *generic;
+    } v;
+};
+
+
+struct mqi_cond_entry_s {
+    mqi_cond_entry_type_t  type;
+    union {
+        mqi_operator_t     operator_;
+        mqi_variable_t     variable;
+        int                column;     /* column index actually */
+    } u;
+};
+
+
+struct mqi_change_table_s {
+    mqi_handle_t  handle;
+    const char   *name;
+};
+
+struct mqi_change_select_s {
+    int   length;
+    void *data;
+};
+
+struct mqi_change_coldsc_s {
+    int         index;
+    const char *name;
+};
+
+union mqi_change_data_u {
+    char    *varchar;
+    char    *string;
+    int32_t  integer;
+    uint32_t unsignd;
+    double   floating;
+    void    *generic;
+};
+
+struct mqi_change_value_s {
+    mqi_data_type_t   type;
+    mqi_change_data_t old;
+    mqi_change_data_t new_;
+};
+
+
+struct mqi_column_event_s {
+    mqi_event_type_t    event;
+    mqi_change_table_t  table;
+    mqi_change_coldsc_t column;
+    mqi_change_value_t  value;
+    mqi_change_select_t select;
+};
+
+struct mqi_row_event_s {
+    mqi_event_type_t    event;
+    mqi_change_table_t  table;
+    mqi_change_select_t select;
+};
+
+struct mqi_table_event_s {
+    mqi_event_type_t   event;
+    mqi_change_table_t table;
+};
+
+struct mqi_transact_event_s {
+    mqi_event_type_t  event;
+    uint32_t          depth;
+};
+
+
+union mqi_event_u {
+    mqi_event_type_t     event;
+    mqi_column_event_t   column;
+    mqi_row_event_t      row;
+    mqi_table_event_t    table;
+    mqi_transact_event_t transact;
+};
+
+
+const char *mqi_data_type_str(mqi_data_type_t);
+
+int mqi_data_compare_integer(int, void *, void *);
+int mqi_data_compare_unsignd(int, void *, void *);
+int mqi_data_compare_string(int, void *, void *);
+int mqi_data_compare_pointer(int, void *, void *);
+int mqi_data_compare_varchar(int, void *, void *);
+int mqi_data_compare_blob(int, void *, void *);
+
+int mqi_data_print_integer(void *, char *, int);
+int mqi_data_print_unsignd(void *, char *, int);
+int mqi_data_print_string(void *, char *, int);
+int mqi_data_print_pointer(void *, char *, int);
+int mqi_data_print_varchar(void *, char *, int);
+int mqi_data_print_blob(void *, char *, int);
+
+
+#endif /* __MQI_TYPES_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mqi.h b/src/murphy-db/include/murphy-db/mqi.h
new file mode 100644 (file)
index 0000000..7d165a5
--- /dev/null
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQI_MQI_H__
+#define __MQI_MQI_H__
+
+#include <murphy-db/mqi-types.h>
+
+#define MQI_ALL               NULL
+#define MQI_NO_INDEX          NULL
+
+/* table flags */
+#define MQI_PERSISTENT        (1 << 0)
+#define MQI_TEMPORARY         (1 << 1)
+#define MQI_ANY               (MQI_PERSISTENT | MQI_TEMPORARY)
+#define MQI_TABLE_TYPE_MASK   (MQI_PERSISTENT | MQI_TEMPORARY)
+
+
+#define MQI_COLUMN_DEFINITION(name, type...)  \
+    {name, type, 0}
+
+#define MQI_COLUMN_SELECTOR(column_index, result_structure, result_member) \
+    {column_index, MQI_OFFSET(result_structure, result_member)}
+
+#define MQI_VARCHAR(s)    mqi_varchar, s
+#define MQI_INTEGER       mqi_integer, 0
+#define MQI_UNSIGNED      mqi_unsignd, 0
+#define MQI_BLOB(s)       mqi_blob,    s
+
+#define MQI_COLUMN(column_index)  \
+    {.type=mqi_column, .u.column=column_index}
+
+#define MQI_VALUE(typ, val)  \
+    {.type=mqi_##typ, .v.typ=val}
+
+#define MQI_VARIABLE(typ, val) \
+    {.type=mqi_variable, .u.variable=MQI_VALUE(typ, val)}
+
+#define MQI_OPERATOR(op) \
+    {.type=mqi_operator, .u.operator_=mqi_##op}
+
+
+#define MQI_EXPRESSION(seq)        MQI_OPERATOR(begin), seq, MQI_OPERATOR(end),
+
+
+#define MQI_STRING_VAL(val)        MQI_VALUE(varchar, (char **)&val),
+#define MQI_INTEGER_VAL(val)       MQI_VALUE(integer, (int32_t *)&val),
+#define MQI_UNSIGNED_VAL(val)      MQI_VALUE(unsignd, (uint32_t *)&val),
+#define MQI_BLOB_VAL(val)          MQI_VALUE(blob,    (void **)&val),
+
+#define MQI_STRING_VAR(val)        MQI_VARIABLE(varchar, (char **)&val)
+#define MQI_INTEGER_VAR(val)       MQI_VARIABLE(integer, (int32_t *)&val)
+#define MQI_UNSIGNED_VAR(val)      MQI_VARIABLE(unsignd, (uint32_t *)&val)
+#define MQI_BLOB_VAR(val)          MQI_VARIABLE(blob,    (void **)&val)
+
+
+#define MQI_AND                    MQI_OPERATOR(and),
+#define MQI_OR                     MQI_OPERATOR(or),
+
+#define MQI_LESS(a,b)              a, MQI_OPERATOR(less), b,
+#define MQI_LESS_OR_EQUAL(a,b)     a, MQI_OPERATOR(leq),  b,
+#define MQI_EQUAL(a,b)             a, MQI_OPERATOR(eq),   b,
+#define MQI_GREATER_OR_EQUAL(a,b)  a, MQI_OPERATOR(geq),  b,
+#define MQI_GREATER(a,b)           a, MQI_OPERATOR(gt),   b,
+
+#define MQI_NOT(val)               MQI_OPERATOR(not), val,
+
+#define MQI_COLUMN_DEFINITION_LIST(name, columns...)            \
+    static mqi_column_def_t name[] = {                          \
+        columns,                                                \
+        {NULL, mqi_unknown, 0, 0}                               \
+    }
+
+#define MQI_INDEX_COLUMN(column_name)  column_name,
+
+#define MQI_INDEX_DEFINITION(name, column_names...)             \
+    static char *name[] = {                                     \
+        column_names                                            \
+        NULL                                                    \
+    }
+
+#define MQI_INDEX_VALUE(name, varlist...)                       \
+    static mqi_variable_t name[] = {varlist}
+
+#define MQI_COLUMN_SELECTION_LIST(name, columns...)             \
+    static mqi_column_desc_t name[] = {                         \
+        columns,                                                \
+        {-1, 1}                                                 \
+    }
+#define MQI_WHERE_CLAUSE(name, seq...)                          \
+    static mqi_cond_entry_t  name[] = {                         \
+        seq                                                     \
+        MQI_OPERATOR(end)                                       \
+    }
+
+#define MQI_BEGIN                                               \
+    mqi_begin_transaction()
+
+#define MQI_COMMIT(id)                                          \
+    mqi_commit_transaction(id)
+
+#define MQI_ROLLBACK(id)                                        \
+    mqi_rollback_transaction(id)
+
+#define MQI_CREATE_TABLE(name, type, column_defs, index_def)    \
+    mqi_create_table(name, type, index_def, column_defs)
+
+#define MQI_DESCRIBE(table, coldefs)                            \
+    mqi_describe(table, coldefs, MQI_DIMENSION(coldefs))
+
+#define MQI_INSERT_INTO(table, column_descs, data)              \
+    mqi_insert_into(table, 0, column_descs, (void **)data)
+
+#define MQI_REPLACE(table, column_descs, data)                  \
+    mqi_insert_into(table, 1, column_descs, (void **)data)
+
+#define MQI_SELECT(columns, table, where, result)               \
+    mqi_select(table, where, columns, result,                   \
+               sizeof(result[0]), MQI_DIMENSION(result))
+
+#define MQI_SELECT_BY_INDEX(columns, table, idxvars, result)    \
+    mqi_select_by_index(table, idxvars, columns, result)
+
+#define MQI_UPDATE(table, column_descs, data, where)            \
+    mqi_update(table, where, column_descs, data)
+
+#define MQI_DELETE(table, where)                                \
+    mqi_delete_from(table, where)
+
+
+
+int mqi_open(void);
+int mqi_close(void);
+
+int mqi_show_tables(uint32_t, char **, int);
+
+int mqi_create_transaction_trigger(mqi_trigger_cb_t, void *);
+int mqi_create_table_trigger(mqi_trigger_cb_t, void *);
+int mqi_create_row_trigger(mqi_handle_t, mqi_trigger_cb_t, void *,
+                           mqi_column_desc_t *);
+int mqi_create_column_trigger(mqi_handle_t, int, mqi_trigger_cb_t, void *,
+                              mqi_column_desc_t *);
+int mqi_drop_transaction_trigger(mqi_trigger_cb_t, void *);
+int mqi_drop_table_trigger(mqi_trigger_cb_t, void *);
+int mqi_drop_row_trigger(mqi_handle_t, mqi_trigger_cb_t,void *);
+int mqi_drop_column_trigger(mqi_handle_t, int, mqi_trigger_cb_t, void *);
+mqi_handle_t mqi_begin_transaction(void);
+int mqi_commit_transaction(mqi_handle_t);
+int mqi_rollback_transaction(mqi_handle_t);
+mqi_handle_t mqi_get_transaction_handle(void);
+uint32_t mqi_get_transaction_depth(void);
+mqi_handle_t mqi_create_table(char *, uint32_t, char **, mqi_column_def_t *);
+int mqi_create_index(mqi_handle_t, char **);
+int mqi_drop_table(mqi_handle_t);
+int mqi_describe(mqi_handle_t, mqi_column_def_t *, int);
+int mqi_insert_into(mqi_handle_t, int, mqi_column_desc_t *, void **);
+int mqi_delete_from(mqi_handle_t, mqi_cond_entry_t *);
+int mqi_update(mqi_handle_t, mqi_cond_entry_t *, mqi_column_desc_t *, void *);
+int mqi_select(mqi_handle_t, mqi_cond_entry_t *, mqi_column_desc_t *,
+               void *, int, int);
+int mqi_select_by_index(mqi_handle_t, mqi_variable_t *,
+                        mqi_column_desc_t *, void *);
+
+mqi_handle_t mqi_get_table_handle(char *);
+int mqi_get_column_index(mqi_handle_t, char *);
+int mqi_get_table_size(mqi_handle_t);
+char *mqi_get_column_name(mqi_handle_t, int);
+mqi_data_type_t mqi_get_column_type(mqi_handle_t, int);
+int mqi_get_column_size(mqi_handle_t, int);
+uint32_t mqi_get_table_stamp(mqi_handle_t);
+int mqi_print_rows(mqi_handle_t, char *, int);
+
+
+#endif /* __MQI_MQI_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mql-result.h b/src/murphy-db/include/murphy-db/mql-result.h
new file mode 100644 (file)
index 0000000..e0bf10d
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQL_RESULT_H__
+#define __MQL_RESULT_H__
+
+#include <murphy-db/mqi-types.h>
+
+
+/** types of mql_result_t structure */
+enum mql_result_type_e {
+    mql_result_error = -1, /**< error code + error message */
+    mql_result_unknown = 0,
+    mql_result_dontcare = mql_result_unknown, /**< will default */
+    mql_result_event,
+    mql_result_columns,    /**< column description of a table */
+    mql_result_rows,       /**< select'ed rows */
+    mql_result_string,     /**< zero terminated ASCII string  */
+    mql_result_list,       /**< array of basic types, (integer, string, etc) */
+};
+
+typedef enum mql_result_type_e  mql_result_type_t;
+typedef struct mql_result_s     mql_result_t;
+
+
+/**
+ * @brief generic return type of MQL opertaions.
+ *
+ * mql_result_type_t is the generic return type  of mql_exec_string()
+ * and mql_exec_statement(). It is either the return status of the
+ * MQL operation or the resulting data. For instance, executing an
+ * insert statement will return a status (ie. mql_result_error type),
+ * while the execution of a select statement will return the selected
+ * rows (ie. mql_result_rows or mql_result_string depending on what
+ * type was requested by mql_exec_string() or mql_exec_statement())
+ *
+ * To access the opaque data use the mql_result_xxx() functions
+ */
+struct mql_result_s {
+    mql_result_type_t  type;       /**< type of the result */
+    uint8_t            data[0];    /**< opaque result data */
+};
+
+
+
+int              mql_result_is_success(mql_result_t *);
+
+int              mql_result_error_get_code(mql_result_t *);
+const char      *mql_result_error_get_message(mql_result_t *);
+
+int              mql_result_columns_get_column_count(mql_result_t *);
+const char      *mql_result_columns_get_name(mql_result_t *, int);
+mqi_data_type_t  mql_result_columns_get_type(mql_result_t *, int);
+int              mql_result_columns_get_length(mql_result_t *, int);
+
+int              mql_result_rows_get_row_column_count(mql_result_t *);
+mqi_data_type_t  mql_result_rows_get_row_column_type(mql_result_t *, int);
+int              mql_result_rows_get_row_column_index(mql_result_t *, int);
+int              mql_result_rows_get_row_count(mql_result_t *);
+const char      *mql_result_rows_get_string(mql_result_t*, int,int, char*,int);
+int32_t          mql_result_rows_get_integer(mql_result_t *, int,int);
+uint32_t         mql_result_rows_get_unsigned(mql_result_t *, int,int);
+double           mql_result_rows_get_floating(mql_result_t *, int,int);
+
+const char      *mql_result_string_get(mql_result_t *);
+
+int              mql_result_list_get_length(mql_result_t *);
+const char      *mql_result_list_get_string(mql_result_t *, int, char *, int);
+int32_t          mql_result_list_get_integer(mql_result_t *, int);
+int32_t          mql_result_list_get_unsigned(mql_result_t *, int);
+double           mql_result_list_get_floating(mql_result_t *, int);
+
+mqi_event_type_t mql_result_event_get_type(mql_result_t *);
+mql_result_t    *mql_result_event_get_changed_rows(mql_result_t *);
+
+void             mql_result_free(mql_result_t *);
+
+
+#endif /* __MQL_RESULT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mql-statement.h b/src/murphy-db/include/murphy-db/mql-statement.h
new file mode 100644 (file)
index 0000000..3e843bf
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQL_STATEMENT_H__
+#define __MQL_STATEMENT_H__
+
+#include <murphy-db/mql-result.h>
+
+
+typedef enum {
+    mql_statement_unknown = 0,
+    mql_statement_show_tables,
+    mql_statement_describe,
+    mql_statement_create_table,
+    mql_statement_create_index,
+    mql_statement_drop_table,
+    mql_statement_drop_index,
+    mql_statement_begin,
+    mql_statement_commit,
+    mql_statement_rollback,
+    mql_statement_insert,
+    mql_statement_update,
+    mql_statement_delete,
+    mql_statement_select,
+    /* do not add anything after this */
+    mql_statement_last
+} mql_statement_type_t;
+
+typedef struct mql_statement_s {
+    mql_statement_type_t  type;
+    uint8_t               data[0];
+} mql_statement_t;
+
+
+mql_result_t *mql_exec_statement(mql_result_type_t, mql_statement_t *);
+int mql_bind_value(mql_statement_t *, int, mqi_data_type_t, ...);
+void mql_statement_free(mql_statement_t *);
+
+
+#endif /* __MQL_STATEMENT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mql-trigger.h b/src/murphy-db/include/murphy-db/mql-trigger.h
new file mode 100644 (file)
index 0000000..19c4a76
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQL_TRIGGER_H__
+#define __MQL_TRIGGER_H__
+
+#include <murphy-db/mql-result.h>
+
+
+typedef void (*mql_trigger_cb_t)(mql_result_t *, void *);
+
+int mql_register_callback(const char *, mql_result_type_t,
+                          mql_trigger_cb_t, void *);
+int mql_unregister_callback(const char *);
+
+
+#endif /* __MQL_TRIGGER_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/mql.h b/src/murphy-db/include/murphy-db/mql.h
new file mode 100644 (file)
index 0000000..4809dcc
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQL_MQL_H__
+#define __MQL_MQL_H__
+
+#include <murphy-db/mql-statement.h>
+#include <murphy-db/mql-trigger.h>
+
+/**
+ * @brief execute a series of MQL statements stored in a file
+ *
+ * This function is to execute a series of MQL statements stored in a file.
+ * The MQL statements supposed to separated with ';'. No ';' shall follow
+ * the last statement. In case of an error the the execution of the statements
+ * will stop, errno is set and the function will return -1. Error messages
+ * will be written to stderr. Currently there is no programmatic way to figure
+ * out what statement failed and how many statement were sucessfully executed.
+ *
+ * @param [in] file  is the path of the file, eg. "~/mql/create_table.mql"
+ *
+ * @return If the execution failed mql_exec_file() returns -1 and errno is
+ *         set. It returns 0 if all the statements in the file were
+ *         successfully executed.
+ */
+int mql_exec_file(const char *file);
+
+/**
+ * @brief execute an MQL statement
+ *
+ * This function is to execute a single MQL statement. The result of the
+ * operation is returned in a data structure. The returned result can be
+ * either the type of the requested result or an error result. It is
+ * recommended that the returned result will be checked first by calling
+ * mql_result_is_success() function.
+ *
+ * To process a result the relevant mql_result_xxx() functions are to use.
+ * Values returned by the mql_result_xxx() functions are valid till the
+ * next MQL commit or rollback. For instance accessing the returned strings
+ * after a commit might lead to segfaults.
+ *
+ * The returned result should be freed by mql_result_free().
+ *
+ * @param [in] result_type   specifies the expected type of the returned
+ *                           result. However, if the execution failed for
+ *                           some reason the returned result will have
+ *                           mql_result_error type.
+ *
+ * @param [in] statement     is the string of the MQL statement to execute
+ *
+ * @code
+ *    #include <stdio.h>
+ *    #include <murphy-db/mql.h>
+ *
+ *    const char *statement = "SELECT * FROM persons WHERE name = 'murphy'";
+ *    mql_result_t *r = mql_exec_string(mql_result_type_string, statement);
+ *
+ *    if (mql_result_is_success(r))
+ *      printf("the result of the query:\n%s\n", r->mql_result_string_get(r));
+ * @endcode
+ */
+mql_result_t *mql_exec_string(mql_result_type_t result_type,
+                              const char *statement);
+
+/**
+ * @brief precompile an MQL statement
+ *
+ * @param [in] statement     is the string of the MQL statement to execute
+ *
+ * For performance optimisation purposes the execution of MQL statements
+ * can be done in a precompilation and an execution phase. This allows
+ * a single first phase for frequently executed MQL statements followed
+ * by a series of second phase.
+ *
+ * Precompilation is the parsing of the ASCII MQL statement, making all the
+ * necessary lookups, generating the data structures what the the underlying
+ * MQI interface needs for the execution and packing all these information
+ * into a dynamically allocated memory block.
+ *
+ * mql_bind_value() can be used to assign values for parameters of the
+ * preecompiled statement, if any.
+ *
+ * A precompiled statement can be executed by mql_exec_statement().
+ *
+ * Precompiled statements should be freed by mql_statement_free() when they
+ * are not needed any more.
+ *
+ * @return mql_precompile() returns a pointer to the precompile statement in
+ *         case the precompilation succeeded or NULL if the precompilation
+ *         failed. In the later case errno is set to give a clue what went
+ *         wrong.
+ *
+ *         Note that a successfull precompilation do not garantie the
+ *         successfull execution of the precompiled statement. For instance
+ *         a successfully precompiled of a SELECT statement will fail
+ *         if the table, it tries to operate on, was meanwhile deleted.
+ * @code
+ *    #include <stdio.h>
+ *    #include <string.h>
+ *    #include <errno.h>
+ *    #include <murphy-db/mql.h>
+ *
+ *    const char *group[] = {"joe", "jack", "murphy", NULL};
+ *    const char *query = "SELECT name, email FROM persons WHERE name = %s";
+ *    mql_statement_t *stmnt;
+ *    mql_result_t *r;
+ *    char *person;
+ *    int i;
+ *
+ *    if ((stmnt = mql_precompilation(query)) == NULL)
+ *      printf("precompilation failed (%d): %s\n", errno, strerror(errno));
+ *    else {
+ *      for (i = 0;  (person = group[i]) != NULL;  i++) {
+ *        if (mql_bind_value(stmnt, 0,mqi_varchar, person) < 0)
+ *          printf("bindig failed (%d): %s\n", errno, strerror(errno));
+ *        else {
+ *          r = mql_exec_statement(mql_result_string, stmnt);
+ *          if (!mql_result_is_success(r))
+ *            printf("exec failed %d: %s\n",
+ *                   mql_result_error_get_code(r),
+ *                   mql_result_error_get_message(r));
+ *          else
+ *            printf("query %d\n%s\n", i, mql_result_string_get(r));
+ *        }
+ *      }
+ *    }
+ *
+ *    mql_statement_free(stmnt);
+ * @endcode
+ */
+mql_statement_t *mql_precompile(const char *statement);
+
+
+#endif  /* __MQL_MQL_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/include/murphy-db/sequence.h b/src/murphy-db/include/murphy-db/sequence.h
new file mode 100644 (file)
index 0000000..3929b72
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_SEQUENCE_H__
+#define __MDB_SEQUENCE_H__
+
+#include <murphy-db/mqi-types.h>
+
+
+#define MDB_SEQUENCE_TABLE_CREATE(type, alloc)              \
+    mdb_sequence_table_create(alloc,                        \
+                              mqi_data_compare_##type,      \
+                              mqi_data_print_##type)
+
+#define MDB_SEQUENCE_FOR_EACH(seq, data, cursor)            \
+    for (cursor = NULL;  (data = mdb_sequence_iterate(seq, &cursor)); )
+
+#define MDB_SEQUENCE_FOR_EACH_SAFE(seq, data, cursor)       \
+    MDB_SEQUENCE_FOR_EACH(seq, data, cursor)
+
+
+typedef struct mdb_sequence_s mdb_sequence_t;
+
+typedef int  (*mdb_sequence_compare_t)(int, void *, void *);
+typedef int  (*mdb_sequence_print_t)(void *, char *, int);
+
+
+mdb_sequence_t *mdb_sequence_table_create(int, mdb_sequence_compare_t,
+                                          mdb_sequence_print_t);
+int mdb_sequence_table_destroy(mdb_sequence_t *);
+int mdb_sequence_table_get_size(mdb_sequence_t *);
+int mdb_sequence_table_reset(mdb_sequence_t *);
+int mdb_sequence_table_print(mdb_sequence_t *, char *, int);
+
+int mdb_sequence_add(mdb_sequence_t *, int, void *, void *);
+void *mdb_sequence_delete(mdb_sequence_t *, int, void *);
+void *mdb_sequence_iterate(mdb_sequence_t *, void **);
+void mdb_sequence_cursor_destroy(mdb_sequence_t *, void **);
+
+
+
+#endif /* __MDB_SEQUENCE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/Makefile.am b/src/murphy-db/mdb/Makefile.am
new file mode 100644 (file)
index 0000000..a53c763
--- /dev/null
@@ -0,0 +1,48 @@
+pkglib_LTLIBRARIES = libmdb.la
+
+LINKER_SCRIPT = linker-script.mdb
+QUIET_GEN     = $(Q:@=@echo '  GEN   '$@;)
+
+libmdb_la_CFLAGS = -I../include
+
+libmdb_ladir     = \
+               $(includedir)/murphy-db
+
+libmdb_la_HEADERS = \
+               ../include/murphy-db/assert.h \
+               ../include/murphy-db/list.h \
+               ../include/murphy-db/handle.h \
+               ../include/murphy-db/hash.h \
+               ../include/murphy-db/sequence.h \
+               ../include/murphy-db/mqi-types.h \
+               ../include/murphy-db/mdb.h
+
+libmdb_la_SOURCES = \
+               $(libmdb_la_HEADERS) \
+                list.h handle.c hash.c sequence.c mqi-types.c \
+                column.h column.c \
+                cond.h cond.c \
+                index.h index.c \
+                log.h log.c \
+                row.h row.c \
+                table.h table.c \
+                transaction.h transaction.c \
+                trigger.h trigger.c
+
+libmdb_la_LDFLAGS =            \
+               -Wl,-version-script=$(LINKER_SCRIPT)
+#              -version-info @MURPHYDB_VERSION_INFO@
+
+libmdb_la_DEPENDENCIES = $(LINKER_SCRIPT)
+
+# linker script generation
+$(LINKER_SCRIPT): $(libmdb_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+           -P "$(CC)" -c "$(libmdb_la_CFLAGS)" -p "^(mdb_)|(mqi_)" -o $@ $^
+
+clean-$(LINKER_SCRIPT):
+       -rm -f $(LINKER_SCRIPT)
+
+# cleanup
+clean-local:: # clean-$(LINKER_SCRIPT)
+       -rm -f *~
diff --git a/src/murphy-db/mdb/column.c b/src/murphy-db/mdb/column.c
new file mode 100644 (file)
index 0000000..9553a9b
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/list.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include "column.h"
+#include "index.h"
+#include "table.h"
+
+static int print_blob(uint8_t *, int data, char *, int);
+
+int mdb_column_write(mdb_column_t      *dst_desc, void *dst_data,
+                     mqi_column_desc_t *src_desc, void *src_data)
+{
+    int lgh;
+    void *dst, *src;
+    static char *empty = "";
+
+    if (dst_desc && dst_data && src_desc && src_desc->offset >= 0 && src_data){
+        dst = dst_data + dst_desc->offset;
+        src = src_data + src_desc->offset;
+        lgh = dst_desc->length;
+
+        switch (dst_desc->type) {
+
+        case mqi_varchar:
+            if(__builtin_expect(*((char**)src) == NULL, 0))
+                src = &empty;
+
+            if (!**(char **)src && !*(char *)dst)
+                goto identical;
+
+            if (!strncmp(*(char **)src, dst, strlen(*(char **)src) + 1))
+                goto identical;
+
+            memset(dst, 0, lgh);
+            strncpy((char *)dst, *(const char **)src, lgh-1);
+            break;
+
+        case mqi_integer:
+            if (*(int32_t *)dst == *(int32_t *)src)
+                goto identical;
+            else
+                *(int32_t *)dst = *(int32_t *)src;
+            break;
+
+        case mqi_unsignd:
+            if (*(uint32_t *)dst == *(uint32_t *)src)
+                goto identical;
+            else
+                *(uint32_t *)dst = *(uint32_t *)src;
+            break;
+
+        case mqi_floating:
+            if (*(double *)dst == *(double *)src)
+                goto identical;
+            else
+                *(double *)dst = *(double *)src;
+            break;
+
+        case mqi_blob:
+            if (!memcmp(dst, src, lgh))
+                goto identical;
+            else
+                memcpy(dst, src, lgh);
+            break;
+
+        default:
+            /* we do not knopw what this is,
+               so we silently ignore it */
+            goto identical;
+        }
+    }
+
+    return 1;
+
+ identical:
+    return 0;
+}
+
+
+void mdb_column_read(mqi_column_desc_t *dst_desc, void *dst_data,
+                     mdb_column_t      *src_desc, void *src_data)
+{
+    int lgh;
+    void *dst, *src;
+
+    if (dst_desc && dst_data && src_desc && src_data) {
+        dst = dst_data + dst_desc->offset;
+        src = src_data + src_desc->offset;
+        lgh = src_desc->length;
+
+        switch (src_desc->type) {
+
+        case mqi_varchar:
+            *(char **)dst = (char *)src;
+            break;
+
+        case mqi_integer:
+            *(int32_t *)dst = *(int32_t *)src;
+            break;
+
+        case mqi_unsignd:
+            *(uint32_t *)dst = *(uint32_t *)src;
+            break;
+
+        case mqi_floating:
+            *(double *)dst = *(double *)src;
+            break;
+
+        case mqi_blob:
+            memcpy(dst, src, lgh);
+            break;
+
+        default:
+            /* we do not know what this is,
+               so we silently ignore it */
+            break;
+        }
+    }
+}
+
+
+int mdb_column_print_header(mdb_column_t *cdesc, char *buf, int len)
+{
+    int r;
+    int l;
+
+    if (!cdesc || !buf || len < 1)
+        r = 0;
+    else {
+        switch (cdesc->type) {
+        case mqi_varchar:  l = cdesc->length;                       break;
+        case mqi_integer:  l = 11;                                  break;
+        case mqi_unsignd:  l = 11;                                  break;
+        case mqi_blob:     l = cdesc->length > 0 ? (cdesc->length * 3) - 1 : 0;
+                                                                    break;
+        default:           l = 0;                                   break;
+        }
+
+        r = (l > 0) ? snprintf(buf,len, "%*s", l, cdesc->name) : 0;
+    }
+
+    return r;
+}
+
+
+int mdb_column_print(mdb_column_t *cdesc, void *data, char *buf, int len)
+{
+    int   r;
+    int   l;
+    void *d;
+
+    if (!cdesc || !data || !buf || len < 1)
+        r = 0;
+    else {
+        d = data + cdesc->offset;
+        l = cdesc->length;
+
+        switch (cdesc->type) {
+        case mqi_varchar:  r = snprintf(buf,len, "%*s", l, (char *)d);   break;
+        case mqi_integer:  r = snprintf(buf,len, "%11d", *(int32_t *)d); break;
+        case mqi_unsignd:  r = snprintf(buf,len, " %10u",*(uint32_t*)d); break;
+        case mqi_blob:     r = print_blob(d,cdesc->length, buf,len);     break;
+        default:           r = 0;                                        break;
+        }
+    }
+
+    return r;
+}
+
+static int print_blob(uint8_t *data, int data_len, char *buf, int buflen)
+{
+    MQI_UNUSED(data);
+    MQI_UNUSED(data_len);
+    MQI_UNUSED(buf);
+    MQI_UNUSED(buflen);
+
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/column.h b/src/murphy-db/mdb/column.h
new file mode 100644 (file)
index 0000000..5d9dda7
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_COLUMN_H__
+#define __MDB_COLUMN_H__
+
+#include <stdint.h>
+
+#define MDB_COLUMN_LENGTH_MAX   1024
+
+
+#include <murphy-db/mqi-types.h>
+
+typedef struct {
+    char            *name;
+    mqi_data_type_t  type;
+    int              length;
+    int              offset;
+    uint32_t         flags;
+} mdb_column_t;
+
+int mdb_column_write(mdb_column_t *, void *, mqi_column_desc_t *, void *);
+void mdb_column_read(mqi_column_desc_t *, void *, mdb_column_t *, void *);
+int  mdb_column_print_header(mdb_column_t *, char *, int);
+int  mdb_column_print(mdb_column_t *, void *, char *, int);
+
+#endif /* __MDB_COLUMN_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/cond.c b/src/murphy-db/mdb/cond.c
new file mode 100644 (file)
index 0000000..5d7450f
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/list.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include "column.h"
+#include "index.h"
+#include "table.h"
+#include "cond.h"
+
+
+typedef struct {
+    mqi_data_type_t  type;
+    union {
+        char        *varchar;
+        int32_t      integer;
+        uint32_t     unsignd;
+        void        *blob;
+        void        *data;
+    } v;
+} cond_data_t;
+
+#define PRECEDENCE_DATA 256
+
+typedef struct {
+    int                precedence; /* 256 => data, precedence otherwise */
+    union {
+        cond_data_t    data;
+        mqi_operator_t operator;
+    };
+} cond_stack_t;
+
+static int cond_get_data(cond_stack_t*,mqi_cond_entry_t*,mdb_column_t*,void*);
+static int cond_eval(cond_stack_t *, cond_stack_t *, int);
+static int cond_relop(mqi_operator_t, cond_stack_t *, cond_stack_t *);
+static int cond_binary_logicop(mqi_operator_t, cond_stack_t *,
+                               cond_stack_t *);
+static int cond_unary_logicop(mqi_operator_t, cond_stack_t *);
+
+int mdb_cond_evaluate(mdb_table_t *tbl, mqi_cond_entry_t **cond_ptr,void *data)
+{
+    static int precedence[mqi_operator_max] = {
+        [ mqi_done  ] = 0,
+        [ mqi_begin ] = 1,
+        [ mqi_and   ] = 2,
+        [ mqi_or    ] = 3,
+        [ mqi_less  ] = 4,
+        [ mqi_leq   ] = 4,
+        [ mqi_eq    ] = 4,
+        [ mqi_geq   ] = 4,
+        [ mqi_gt    ] = 4,
+        [ mqi_not   ] = 5
+    };
+
+    mqi_cond_entry_t *cond       = *cond_ptr;
+    cond_stack_t      stack[256] = {
+        [0] = { precedence[mqi_begin], { .operator = mqi_begin } }
+    };
+    cond_stack_t     *sp         = stack + 1;
+    cond_stack_t     *lastop     = stack;
+    int               result;
+    int               pr;
+
+    MDB_CHECKARG(cond && data, -1);
+
+    for (;;) {
+        switch (cond->type) {
+
+        case mqi_operator:
+            pr  = precedence[cond->u.operator_];
+            sp += cond_eval(sp, lastop, pr);
+
+            switch (cond->u.operator_) {
+
+            case mqi_begin:
+                cond++;
+                result = mdb_cond_evaluate(tbl, &cond, data);
+                cond++;
+
+                sp->data.v.integer = result >= 0 ? result : 0;
+                sp->precedence   = PRECEDENCE_DATA;
+                sp->data.type    = mqi_integer;
+                sp++;
+                break;
+
+            case mqi_end:
+                *cond_ptr = cond+1;
+                sp--;
+                if (sp->precedence < PRECEDENCE_DATA ||
+                    sp->data.type != mqi_integer)
+                {
+                    errno = ENOENT;
+                    return -1;
+                }
+                return sp->data.v.integer ? 1 : 0;
+
+            case mqi_and:
+            case mqi_or:
+            case mqi_less:
+            case mqi_leq:
+            case mqi_eq:
+            case mqi_geq:
+            case mqi_gt:
+            case mqi_not:
+                lastop = sp++;
+                lastop->precedence = pr;
+                lastop->operator = cond->u.operator_;
+                cond++;
+                break;
+
+            default:
+                break;
+            }
+            break;
+
+        case mqi_variable:
+        case mqi_column:
+            sp += cond_get_data(sp, cond, tbl->columns, data);
+            cond++;
+            break;
+
+        default:
+            errno = EINVAL;
+            return -1;
+        }
+
+    } /* for ;; */
+}
+
+static int cond_get_data(cond_stack_t     *sp,
+                         mqi_cond_entry_t *cond,
+                         mdb_column_t     *columns,
+                         void             *data)
+{
+    mqi_column_desc_t  sp_desc[2];
+    cond_data_t       *sd;
+    mdb_column_t      *col_desc;
+    mqi_variable_t    *var;
+    int                ok;
+
+    switch (cond->type) {
+
+    case mqi_variable:
+        sd  = &sp->data;
+        var = &cond->u.variable;
+
+        if (!var->v.generic)
+            ok = 0;
+        else {
+            switch ((sp->data.type = var->type)) {
+            case mqi_varchar:  sd->v.varchar = *var->v.varchar;  ok = 1; break;
+            case mqi_integer:  sd->v.integer = *var->v.integer;  ok = 1; break;
+            case mqi_unsignd:  sd->v.unsignd = *var->v.unsignd;  ok = 1; break;
+            case mqi_blob:     sd->v.blob    = *var->v.blob;     ok = 1; break;
+            default:                                             ok = 0; break;
+            }
+        }
+        break;
+
+    case mqi_column: {
+        col_desc = columns + cond->u.column;
+        sp_desc[0].cindex  = cond->u.column;
+        sp_desc[0].offset = 0;
+        sp_desc[1].cindex  = -1;
+        sp_desc[1].offset = -1;
+        mdb_column_read(sp_desc, &sp->data.v.data, col_desc, data);
+        sp->data.type = col_desc->type;
+        ok = 1;
+        }
+        break;
+
+    default:
+        ok = 0;
+        break;
+    }
+
+    sp->precedence = PRECEDENCE_DATA * ok;
+
+    return ok;
+}
+
+static int cond_eval(cond_stack_t *sp,cond_stack_t *lastop,int new_precedence)
+{
+    cond_stack_t *result;
+    cond_stack_t *newsp = sp;
+    int value;
+    int stack_advance = 0;
+
+    while (new_precedence < lastop->precedence) {
+        switch (lastop->operator) {
+
+        case mqi_begin:
+            /* stack: (0)begin, (1)operand => (0)result */
+            newsp = (result = lastop) + 1;
+            value = (lastop+1)->data.v.integer;
+            new_precedence = INT_MAX;
+            goto store_on_stack;
+
+        case mqi_and:
+        case mqi_or:
+            /* stack: (-1)operand1, (0)operator, (1)operand2 => (-1)result */
+            result = (newsp = lastop) - 1;
+            value = cond_binary_logicop(lastop->operator, lastop-1,lastop+1);
+            goto find_new_lastop_and_store_on_stack;
+
+        case mqi_less:
+        case mqi_leq:
+        case mqi_eq:
+        case mqi_geq:
+        case mqi_gt:
+            /* stack: (-1)operand1, (0)operator, (1)operand2 => (-1)result */
+            result = (newsp = lastop) - 1;
+            value = cond_relop(lastop->operator, lastop-1,lastop+1);
+            goto find_new_lastop_and_store_on_stack;
+
+        case mqi_not:
+            /* stack: (0)operator, (1)operand => (0)result */
+            newsp = (result = lastop) + 1;
+            value = cond_unary_logicop(lastop->operator, lastop+1);
+            goto find_new_lastop_and_store_on_stack;
+
+        find_new_lastop_and_store_on_stack:
+            for (lastop--;  lastop->precedence >= PRECEDENCE_DATA;  lastop--)
+                ;
+            /* intentional fall over */
+
+        store_on_stack:
+            result->precedence   = PRECEDENCE_DATA;
+            result->data.type    = mqi_integer;
+            result->data.v.integer = value;
+            /* intentional fall over */
+
+        default:
+            stack_advance = newsp - sp;
+            break;
+        }
+    }
+
+    return stack_advance;
+}
+
+
+static int cond_relop(mqi_operator_t op, cond_stack_t *v1, cond_stack_t *v2)
+{
+    cond_data_t *d1 = &v1->data;
+    cond_data_t *d2 = &v2->data;
+    int cmp;
+
+    if (v1->precedence >= PRECEDENCE_DATA &&
+        v2->precedence >= PRECEDENCE_DATA &&
+        d1->type == d2->type)
+    {
+        switch (d1->type) {
+        case mqi_varchar:
+            if (!d1->v.varchar && !d2->v.varchar)
+                cmp = 0;
+            else if (!d1->v.varchar)
+                cmp = -1;
+            else if (!d2->v.varchar)
+                cmp = 1;
+            else
+                cmp = strcmp(d1->v.varchar, d2->v.varchar);
+            break;
+
+        case mqi_integer:
+            if (d1->v.integer > d2->v.integer)
+                cmp = 1;
+            else if (d1->v.integer == d2->v.integer)
+                cmp = 0;
+            else
+                cmp = -1;
+            break;
+
+        case mqi_unsignd:
+            if (d1->v.unsignd > d2->v.unsignd)
+                cmp = 1;
+            else if (d1->v.unsignd == d2->v.unsignd)
+                cmp = 0;
+            else
+                cmp = -1;
+            break;
+
+        default:
+            return 0;
+        }
+
+        switch (op) {
+        case mqi_less:  return cmp <  0;
+        case mqi_leq:   return cmp <= 0;
+        case mqi_eq:    return cmp == 0;
+        case mqi_geq:   return cmp >= 0;
+        case mqi_gt:    return cmp >  0;
+        default:        return 0;
+        }
+    }
+
+    return 0;
+}
+
+static int cond_binary_logicop(mqi_operator_t op,
+                               cond_stack_t  *v1,
+                               cond_stack_t  *v2)
+{
+    cond_data_t *d1 = &v1->data;
+    cond_data_t *d2 = &v2->data;
+
+    if (v1->precedence >= PRECEDENCE_DATA &&
+        v2->precedence >= PRECEDENCE_DATA &&
+        d1->type == d2->type)
+    {
+        switch (op) {
+
+        case mqi_and:
+            switch (d1->type) {
+            case mqi_integer:   return d1->v.integer && d2->v.integer;
+            case mqi_unsignd:   return d1->v.unsignd && d2->v.unsignd;
+            default:            return 0;
+            }
+            break;
+
+        case mqi_or:
+            switch (d1->type) {
+            case mqi_integer:   return d1->v.integer || d2->v.integer;
+            case mqi_unsignd:   return d1->v.unsignd || d2->v.unsignd;
+            default:            return 0;
+            }
+            break;
+
+        default:
+            return 0;
+        }
+    }
+
+    return 0;
+}
+
+static int cond_unary_logicop(mqi_operator_t op, cond_stack_t *v)
+{
+    cond_data_t *d = &v->data;
+
+    if (v->precedence >= PRECEDENCE_DATA && op == mqi_not) {
+        switch (d->type) {
+        case mqi_varchar:  return d->v.varchar && d->v.varchar[0] ? 0 : 1;
+        case mqi_integer:  return d->v.integer ? 0 : 1;
+        case mqi_unsignd:  return d->v.unsignd ? 0 : 1;
+        default:           return 0;
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/cond.h b/src/murphy-db/mdb/cond.h
new file mode 100644 (file)
index 0000000..82462a5
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_COND_H__
+#define __MDB_COND_H__
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/mdb.h>
+
+
+int mdb_cond_evaluate(mdb_table_t *, mqi_cond_entry_t **, void *);
+
+
+#endif /* __MDB_COND_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/handle.c b/src/murphy-db/mdb/handle.c
new file mode 100644 (file)
index 0000000..04d0479
--- /dev/null
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define __USE_GNU
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+
+#define HANDLE_INDEX_INVALID -1
+#define HANDLE_USEID_INVALID -1
+
+#define HANDLE_USEID_BITS 16
+#define HANDLE_INDEX_BITS ((sizeof(mdb_handle_t) * 8) - HANDLE_USEID_BITS)
+#define HANDLE_USEID_MAX  (((mdb_handle_t)1) << HANDLE_USEID_BITS)
+#define HANDLE_INDEX_MAX  (((mdb_handle_t)1) << HANDLE_INDEX_BITS)
+#define HANDLE_USEID_MASK (HANDLE_USEID_MAX - 1)
+#define HANDLE_INDEX_MASK (HANDLE_INDEX_MAX - 1)
+
+#define HANDLE_MAKE(useid, index) (                                      \
+    (((mdb_handle_t)(useid) & HANDLE_USEID_MASK) << HANDLE_INDEX_BITS) | \
+    (((mdb_handle_t)(index) & HANDLE_INDEX_MASK))                        \
+)
+
+#define HANDLE_USEID(h) (((h) >> HANDLE_INDEX_BITS) & HANDLE_USEID_MASK)
+#define HANDLE_INDEX(h) ((h) & HANDLE_INDEX_MASK)
+
+
+typedef long long int bucket_t;
+
+
+typedef struct {
+    int       nbucket;
+    bucket_t *buckets;
+} freemap_t;
+
+typedef struct {
+    uint32_t   useid;
+    void      *data;
+} indextbl_entry_t;
+
+
+typedef struct {
+    int               nentry;
+    indextbl_entry_t *entries;
+} indextbl_t;
+
+struct mdb_handle_map_s {
+    freemap_t   freemap;
+    indextbl_t  indextbl;
+};
+
+
+static mdb_handle_t index_alloc(indextbl_t *, int, void *);
+static void *index_realloc(indextbl_t *, uint32_t, int, void *);
+static void *index_free(indextbl_t *, uint32_t, int);
+static int freemap_alloc(freemap_t *);
+static int freemap_free(freemap_t *, int);
+
+static bucket_t  empty_bucket    = ~((bucket_t)0);
+static int       bits_per_bucket = sizeof(bucket_t) * 8;
+
+
+mdb_handle_map_t *mdb_handle_map_create(void)
+{
+    mdb_handle_map_t *hmap;
+
+    if (!(hmap = calloc(1, sizeof(mdb_handle_map_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    return hmap;
+}
+
+int mdb_handle_map_destroy(mdb_handle_map_t *hmap)
+{
+    MDB_CHECKARG(hmap, -1);
+
+    free(hmap->freemap.buckets);
+    free(hmap->indextbl.entries);
+
+    free(hmap);
+
+    return 0;
+}
+
+
+mdb_handle_t mdb_handle_add(mdb_handle_map_t *hmap, void *data)
+{
+    int index;
+
+    MDB_CHECKARG(hmap && data, MDB_HANDLE_INVALID);
+
+    if ((index = freemap_alloc(&hmap->freemap)) == HANDLE_INDEX_INVALID) {
+        return MDB_HANDLE_INVALID;
+    }
+
+    return index_alloc(&hmap->indextbl, index, data);
+}
+
+void *mdb_handle_delete(mdb_handle_map_t *hmap, mdb_handle_t h)
+{
+    uint32_t  useid = HANDLE_USEID(h);
+    int       index = HANDLE_INDEX(h);
+    void     *old_data;
+
+    MDB_CHECKARG(hmap && h != MDB_HANDLE_INVALID, NULL);
+
+
+    if (!(old_data = index_free(&hmap->indextbl, useid,index))) {
+        /* errno has been set by index_free() */
+        return NULL;
+    }
+
+    if (freemap_free(&hmap->freemap, index) < 0) {
+        /* errno has been set by freemap_free() */
+        return NULL;
+    }
+
+    return old_data;
+}
+
+void *mdb_handle_get_data(mdb_handle_map_t *hmap, mdb_handle_t h)
+{
+    uint32_t          useid = HANDLE_USEID(h);
+    int               index = HANDLE_INDEX(h);
+    indextbl_t       *indextbl;
+    indextbl_entry_t *entry;
+
+    MDB_CHECKARG(hmap && h != MDB_HANDLE_INVALID, NULL);
+
+    indextbl = &hmap->indextbl;
+
+    if (index >= indextbl->nentry) {
+        errno = EKEYREJECTED;
+        return NULL;
+    }
+
+    entry = indextbl->entries + index;
+
+    if (entry->useid != useid) {
+        errno = ENOANO;
+        return NULL;
+    }
+
+    if (!entry->data)
+        errno = ENODATA;
+
+    return entry->data;
+}
+
+
+int mdb_handle_print(mdb_handle_map_t *hmap, char *buf, int len)
+{
+    indextbl_t *it;
+    char *p, *e;
+    int i;
+
+    MDB_CHECKARG(hmap && buf && len > 0, -1);
+
+    it = &hmap->indextbl;
+    e = (p = buf) + len;
+
+    p += snprintf(p, e-p, "   useid index data\n");
+
+    for (i = 0;   i < it->nentry && e > p;   i++) {
+        indextbl_entry_t *en = it->entries + i;
+
+        if (en->data)
+            p += snprintf(p, e-p, "   %5u %5d %p\n",en->useid, i, en->data);
+    }
+
+    return p - buf;
+}
+
+
+static mdb_handle_t index_alloc(indextbl_t *indextbl, int index, void *data)
+{
+#define ALIGN(i,a) ((((i) + (a)-1) / a) * a)
+
+    mdb_handle_t handle;
+    int nentry;
+    indextbl_entry_t *entries, *entry;
+    size_t size;
+
+    MDB_CHECKARG(index >= 0 && (mdb_handle_t)index < HANDLE_INDEX_MAX && data,
+                 MDB_HANDLE_INVALID);
+
+    if (index >= indextbl->nentry) {
+        nentry  = ALIGN(index + 1, bits_per_bucket);
+        size    = sizeof(indextbl_entry_t) * nentry;
+        entries = realloc(indextbl->entries, size);
+
+        if (!entries) {
+            errno = ENOMEM;
+            return MDB_HANDLE_INVALID;
+        }
+
+        size = sizeof(indextbl_entry_t) * (nentry - indextbl->nentry);
+        memset(entries + indextbl->nentry, 0, size);
+
+        indextbl->nentry  = nentry;
+        indextbl->entries = entries;
+    }
+
+    entry = indextbl->entries + index;
+
+    if (entry->data && entry->data != data) {
+        errno = EBUSY;
+        return MDB_HANDLE_INVALID;
+    }
+
+    entry->useid += 1;
+    entry->data   = data;
+
+    handle = HANDLE_MAKE(entry->useid, index);
+
+    return handle;
+
+#undef ALIGN
+}
+
+
+static void *index_realloc(indextbl_t *indextbl,
+                           uint32_t    useid,
+                           int         index,
+                           void       *data)
+{
+    indextbl_entry_t *entry;
+    void *old_data;
+
+    MDB_CHECKARG(indextbl, NULL);
+
+    if (index < 0 || index >= indextbl->nentry) {
+        errno = EKEYREJECTED;
+        return NULL;
+    }
+
+    entry = indextbl->entries + index;
+
+    if (entry->useid != useid) {
+        errno = ENOKEY;
+        return NULL;
+    }
+
+    if (!(old_data = entry->data)) {
+        errno = ENOENT;
+        return NULL;
+    }
+
+
+    entry->data = data;
+
+    return old_data;
+}
+
+static void *index_free(indextbl_t *indextbl, uint32_t useid, int index)
+{
+    return index_realloc(indextbl, useid,index, NULL);
+}
+
+static int freemap_alloc(freemap_t *freemap)
+{
+    bucket_t  mask;
+    bucket_t *bucket;
+    int       nbucket;
+    bucket_t *buckets;
+    int       bucket_idx;
+    int       bit_idx;
+    int       index;
+    size_t    size;
+
+    for (bucket_idx = 0;   bucket_idx < freemap->nbucket;   bucket_idx++) {
+        bucket = freemap->buckets + bucket_idx;
+
+        if (*bucket && (bit_idx = ffsll(*bucket) - 1) >= 0) {
+            index = bucket_idx * bits_per_bucket + bit_idx;
+            mask  = ~(((bucket_t)1) << bit_idx);
+            *bucket &= mask;
+            return index;
+        }
+    }
+
+    index   = bucket_idx * bits_per_bucket;
+    nbucket = bucket_idx + 1;
+    size    = sizeof(bucket_t) * nbucket;
+    buckets = realloc(freemap->buckets, size);
+
+    if (!buckets) {
+        errno = ENOMEM;
+        return HANDLE_INDEX_INVALID;
+    }
+
+    buckets[bucket_idx] = ~((bucket_t)1);
+
+    freemap->nbucket = nbucket;
+    freemap->buckets = buckets;
+
+    return index;
+}
+
+
+static int freemap_free(freemap_t *freemap, int index)
+{
+    int       bucket_idx = index / bits_per_bucket;
+    int       bit_idx    = index % bits_per_bucket;
+    int       nbucket;
+    bucket_t *buckets;
+    size_t    size;
+
+    if (freemap && index >= 0 && bucket_idx < freemap->nbucket) {
+        freemap->buckets[bucket_idx] |= ((bucket_t)1) << bit_idx;
+
+        if ((bucket_idx + 1 == freemap->nbucket) &&
+            freemap->buckets[bucket_idx] == empty_bucket) {
+            if (freemap->nbucket == 1) {
+                free(freemap->buckets);
+                freemap->nbucket = 0;
+                freemap->buckets = NULL;
+            }
+            else {
+                nbucket = bucket_idx;
+                size    = sizeof(bucket_t) * nbucket;
+                buckets = realloc(freemap->buckets, size);
+
+                if (!buckets) {
+                    errno = ENOMEM;
+                    return -1;
+                }
+
+                freemap->buckets = buckets;
+            }
+        }
+
+        return 0;
+    }
+
+    errno = EINVAL;
+    return -1;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/hash.c b/src/murphy-db/mdb/hash.c
new file mode 100644 (file)
index 0000000..89ad450
--- /dev/null
@@ -0,0 +1,574 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/list.h>
+
+#ifndef HASH_STATISTICS
+#define HASH_STATISTICS
+#endif
+
+typedef struct mdb_hash_entry_s {
+    mdb_dlist_t  clink;         /* hash link, ie. chaining */
+    mdb_dlist_t  elink;         /* entry link, ie. linking all entries */
+    void        *key;
+    void        *data;
+} hash_entry_t;
+
+typedef struct {
+    mdb_dlist_t  head;
+#ifdef HASH_STATISTICS
+    struct {
+        int curr;
+        int max;
+    }            entries;
+#endif
+} hash_chain_t;
+
+
+struct mdb_hash_s {
+    int                  bits;
+    mdb_hash_function_t  hfunc;
+    mdb_hash_compare_t   hcomp;
+    mdb_hash_print_t     hprint;
+    struct {
+        mdb_dlist_t head;
+#ifdef HASH_STATISTICS
+        int         curr;
+        int         max;
+#endif
+    }                    entries;
+    int                  nchain;
+    hash_chain_t         chains[0];
+};
+
+
+typedef struct {
+    int  nchain;
+    int  bits;
+} table_size_t;
+
+static table_size_t  sizes[]          = {
+    {    2,  2}, {    3,  2}, {    5,  3}, {    7,  3}, {   11,  4},
+    {   13,  4}, {   17,  5}, {   19,  5}, {   23,  5}, {   29,  5},
+    {   31,  5}, {   37,  6}, {   41,  6}, {   43,  6}, {   47,  6},
+    {   53,  6}, {   59,  6}, {   61,  6}, {   67,  7}, {   71,  7},
+    {   73,  7}, {   79,  7}, {   83,  7}, {   89,  7}, {   97,  7},
+    {  101,  7}, {  103,  7}, {  107,  7}, {  109,  7}, {  113,  7},
+    {  127,  7}, {  131,  8}, {  137,  8}, {  139,  8}, {  149,  8},
+    {  151,  8}, {  157,  8}, {  163,  8}, {  167,  8}, {  173,  8},
+    {  179,  8}, {  181,  8}, {  191,  8}, {  193,  8}, {  197,  8},
+    {  199,  8}, {  211,  8}, {  223,  8}, {  227,  8}, {  229,  8},
+    {  233,  8}, {  239,  8}, {  241,  8}, {  251,  8}, {  257,  9},
+    {  263,  9}, {  269,  9}, {  271,  9}, {  277,  9}, {  281,  9},
+    {  283,  9}, {  293,  9}, {  307,  9}, {  311,  9}, {  313,  9},
+    {  317,  9}, {  331,  9}, {  337,  9}, {  347,  9}, {  349,  9},
+    {  353,  9}, {  359,  9}, {  367,  9}, {  373,  9}, {  379,  9},
+    {  383,  9}, {  389,  9}, {  397,  9}, {  401,  9}, {  409,  9},
+    {  419,  9}, {  421,  9}, {  431,  9}, {  433,  9}, {  439,  9},
+    {  443,  9}, {  449,  9}, {  457,  9}, {  461,  9}, {  463,  9},
+    {  467,  9}, {  479,  9}, {  487,  9}, {  491,  9}, {  499,  9},
+    {  503,  9}, {  509,  9}, {  521, 10}, {  523, 10}, {  541, 10},
+    {  547, 10}, {  557, 10}, {  563, 10}, {  569, 10}, {  571, 10},
+    {  577, 10}, {  587, 10}, {  593, 10}, {  599, 10}, {  601, 10},
+    {  607, 10}, {  613, 10}, {  617, 10}, {  619, 10}, {  631, 10},
+    {  641, 10}, {  643, 10}, {  647, 10}, {  653, 10}, {  659, 10},
+    {  661, 10}, {  673, 10}, {  677, 10}, {  683, 10}, {  691, 10},
+    {  701, 10}, {  709, 10}, {  719, 10}, {  727, 10}, {  733, 10},
+    {  739, 10}, {  743, 10}, {  751, 10}, {  757, 10}, {  761, 10},
+    {  769, 10}, {  773, 10}, {  787, 10}, {  797, 10}, {  809, 10},
+    {  811, 10}, {  821, 10}, {  823, 10}, {  827, 10}, {  829, 10},
+    {  839, 10}, {  853, 10}, {  857, 10}, {  859, 10}, {  863, 10},
+    {  877, 10}, {  881, 10}, {  883, 10}, {  887, 10}, {  907, 10},
+    {  911, 10}, {  919, 10}, {  929, 10}, {  937, 10}, {  941, 10},
+    {  947, 10}, {  953, 10}, {  967, 10}, {  971, 10}, {  977, 10},
+    {  983, 10}, {  991, 10}, {  997, 10}, {65535, 16}
+};
+static uint32_t  charmap[256] = {
+    /*        00  01  02  03  04  05  06  07  08  09  0a  0b  0c  0d  0e  0f */
+    /* 00 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* 10 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* 20 */   0,  0,  0,  0,  0,  0,  0,  0, 52, 53, 54, 55, 56, 37, 40, 50,
+    /* 30 */   1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 41,  0, 42, 43, 44, 45,
+    /* 40 */  46, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+    /* 50 */  26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 47, 48, 49, 51, 38,
+    /* 60 */   0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+    /* 70 */  26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 57, 58, 59, 60,  0,
+    /* 80 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* 90 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* a0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* b0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* c0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* d0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* e0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+    /* f0 */   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0
+};
+
+static void htable_reset(mdb_hash_t *, int);
+static table_size_t *get_table_size(int);
+static int print_chain(mdb_hash_t *, int, char *, int);
+
+
+mdb_hash_t *mdb_hash_table_create(int                  max_entries,
+                                  mdb_hash_function_t  hfunc,
+                                  mdb_hash_compare_t   hcomp,
+                                  mdb_hash_print_t     hprint)
+{
+    mdb_hash_t   *htbl;
+    table_size_t *ts;
+    size_t        size;
+    int           i;
+
+    MDB_CHECKARG(hfunc && hcomp && hprint &&
+                 max_entries > 1 && max_entries < 65536, NULL);
+
+    if ((ts = get_table_size(max_entries)) == NULL) {
+        errno = EOVERFLOW;
+        return NULL;
+    }
+
+    size = sizeof(mdb_hash_t) + sizeof(hash_chain_t) * ts->nchain;
+    htbl = calloc(1, size);
+
+    if (!htbl) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    htbl->bits   = ts->bits;
+    htbl->nchain = ts->nchain;
+    htbl->hfunc  = hfunc;
+    htbl->hcomp  = hcomp;
+    htbl->hprint = hprint;
+
+    MDB_DLIST_INIT(htbl->entries.head);
+
+    for (i = 0; i < htbl->nchain; i++)
+        MDB_DLIST_INIT(htbl->chains[i].head);
+
+    return htbl;
+}
+
+int mdb_hash_table_destroy(mdb_hash_t *htbl)
+{
+    MDB_CHECKARG(htbl, -1);
+
+    htable_reset(htbl, 0);
+    free(htbl);
+
+    return 0;
+}
+
+int mdb_hash_table_reset(mdb_hash_t *htbl)
+{
+    MDB_CHECKARG(htbl, -1);
+
+    htable_reset(htbl, 1);
+
+    return 0;
+}
+
+void *mdb_hash_table_iterate(mdb_hash_t *htbl,void **key_ret,void **cursor_ptr)
+{
+    mdb_dlist_t  *head, *link;
+    hash_entry_t *entry;
+
+    MDB_CHECKARG(htbl && cursor_ptr, NULL);
+
+    head = &htbl->entries.head;
+
+    if (!(link = *cursor_ptr))
+        *cursor_ptr = link = head->next;
+
+    if (link == head)
+        return NULL;
+
+    *cursor_ptr = link->next;
+
+    entry = MDB_LIST_RELOCATE(hash_entry_t, elink, link);
+
+    if (key_ret)
+        *key_ret = entry->key;
+
+    return entry->data;
+}
+
+int mdb_hash_table_print(mdb_hash_t *htbl, char *buf, int len)
+{
+    char *p, *e;
+    int   i;
+
+    MDB_CHECKARG(htbl && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+    *buf = '\0';
+
+    for (i = 0;  i < htbl->nchain;  i++) {
+        if (!MDB_DLIST_EMPTY(htbl->chains[i].head)
+#ifdef HASH_STATISTICS
+            || htbl->chains[i].entries.max > 0
+#endif
+            )
+            p += print_chain(htbl, i, p, e-p);
+    }
+
+    return p - buf;
+}
+
+int mdb_hash_add(mdb_hash_t *htbl, int klen, void *key, void *data)
+{
+    hash_entry_t *entry;
+    hash_chain_t *chain;
+    int           index;
+
+    MDB_CHECKARG(htbl && key && klen >= 0 && data, -1);
+
+    index = htbl->hfunc(htbl->bits, htbl->nchain, klen, key);
+    chain = htbl->chains + index;
+
+    MDB_DLIST_FOR_EACH(hash_entry_t, clink, entry, &chain->head) {
+        if (htbl->hcomp(klen, key, entry->key) == 0) {
+            if (data == entry->data)
+                return 0;
+            else {
+                errno = EEXIST;
+                return -1;
+            }
+        }
+    }
+
+    if (!(entry = calloc(1, sizeof(hash_entry_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+    entry->key  = key;
+    entry->data = data;
+
+    MDB_DLIST_APPEND(hash_entry_t, clink, entry, &chain->head);
+    MDB_DLIST_APPEND(hash_entry_t, elink, entry, &htbl->entries.head);
+
+#ifdef HASH_STATISTICS
+    if (++chain->entries.curr > chain->entries.max)
+        chain->entries.max = chain->entries.curr;
+
+    if (++htbl->entries.curr > htbl->entries.max)
+        htbl->entries.max = htbl->entries.curr;
+#endif
+
+    return 0;
+}
+
+void *mdb_hash_delete(mdb_hash_t *htbl, int klen, void *key)
+{
+    hash_entry_t *entry;
+    hash_entry_t *n;
+    hash_chain_t *chain;
+    int           index;
+    void         *data;
+
+    MDB_CHECKARG(htbl && klen >= 0 && key, NULL);
+
+    index = htbl->hfunc(htbl->bits, htbl->nchain, klen, key);
+    chain = htbl->chains + index;
+
+    MDB_DLIST_FOR_EACH_SAFE(hash_entry_t, clink, entry,n, &chain->head) {
+        if (htbl->hcomp(klen, key, entry->key) == 0) {
+            if (!(data = entry->data))
+                break;
+
+            MDB_DLIST_UNLINK(hash_entry_t, clink, entry);
+            MDB_DLIST_UNLINK(hash_entry_t, elink, entry);
+            free(entry);
+
+#ifdef HASH_STATISTICS
+            if (--chain->entries.curr < 0)
+                chain->entries.curr = 0;
+
+            if (--htbl->entries.curr < 0)
+                htbl->entries.curr = 0;
+#endif
+            return data;
+        }
+    }
+
+    errno = ENOENT;
+    return NULL;
+}
+
+void *mdb_hash_get_data(mdb_hash_t *htbl, int klen, void *key)
+{
+    hash_entry_t *entry;
+    hash_chain_t *chain;
+    int           index;
+
+    MDB_CHECKARG(htbl && klen >= 0 && key, NULL);
+
+    index = htbl->hfunc(htbl->bits, htbl->nchain, klen, key);
+    chain = htbl->chains + index;
+
+    MDB_DLIST_FOR_EACH(hash_entry_t, clink, entry, &chain->head) {
+        if (htbl->hcomp(klen, key, entry->key) == 0)
+            return entry->data;
+    }
+
+    errno = ENOENT;
+    return NULL;
+}
+
+
+int mdb_hash_function_integer(int bits, int nchain, int klen, void *key)
+{
+    return mdb_hash_function_unsignd(bits, nchain, klen, key);
+}
+
+
+int mdb_hash_function_unsignd(int bits, int nchain, int klen, void *key)
+{
+    uint32_t unsignd;
+
+    if (klen != sizeof(unsignd) || !key ||
+        bits < 1 || bits > 16 ||
+        nchain < (1 << (bits-1)) || nchain >= (1 << bits))
+        return 0;
+
+    unsignd = *(uint32_t *)key;
+
+    return (int)(unsignd % nchain);
+}
+
+
+int mdb_hash_function_string(int bits, int nchain, int klen, void *key)
+{
+    typedef union {
+        uint64_t wide;
+        uint8_t  narrow[8];
+    } hash_t;
+
+    (void)klen;
+
+    uint8_t *varchar = (uint8_t *)key;
+    int      hashval = 0;
+    hash_t   h;
+    int      shift;
+
+    if (varchar && bits >= 1 && bits <= 16 &&
+        nchain > (1 << (bits-1)) && nchain < (1 << bits))
+    {
+        uint8_t s;
+        int     i;
+
+        for (h.wide = 0; (s = *varchar); varchar++)
+            h.wide = 33ULL * h.wide + (uint64_t)charmap[s];
+
+        if (bits <= 8) {
+            hashval = h.narrow[0] ^ h.narrow[1] ^ h.narrow[2] ^ h.narrow[3] ^
+                      h.narrow[4] ^ h.narrow[5] ^ h.narrow[6] ^ h.narrow[7];
+        }
+        else {
+            shift = (nchain + 7) / 8;
+            for (hashval = h.narrow[0], i = 1;   i < 8;  i++)
+                hashval ^= h.narrow[i] << (i * shift);
+        }
+
+        hashval %= nchain;
+    }
+
+    return hashval;
+}
+
+int mdb_hash_function_pointer(int bits, int nchain, int klen, void *key)
+{
+#define MASK(t)  ((((uint##t##_t)1) << (sizeof(int) * 8 - 3)) - 1)
+    int hash;
+
+    MQI_UNUSED(bits);
+    MQI_UNUSED(klen);
+
+#if __SIZEOF_POINTER__ == 8
+    hash = (int)(((uint64_t)key >> 2) & MASK(64)) % nchain;
+#else
+    hash = ((int)key >> 2) & MASK(32) % nchain;
+#endif
+
+    return hash;
+#undef MASK
+}
+
+int mdb_hash_function_varchar(int bits, int nchain, int klen, void *key)
+{
+    return mdb_hash_function_string(bits, nchain, klen, key);
+}
+
+int mdb_hash_function_blob(int bits, int nchain, int klen, void *key)
+{
+    typedef union {
+        uint64_t wide;
+        uint8_t  narrow[8];
+    } hash_t;
+
+    uint8_t *data  = (uint8_t *)key;
+    int      hashval = 0;
+    hash_t   h;
+    int      shift;
+    int      i;
+
+    if (klen > 0 && data && bits >= 1 && bits <= 16 &&
+        nchain > (1 << (bits-1)) && nchain < (1 << bits))
+    {
+        for (i = 0, h.wide = 0;   i < klen;   i++)
+            h.wide = 33ULL * h.wide + (uint64_t)data[i];
+
+        if (bits <= 8) {
+            hashval = h.narrow[0] ^ h.narrow[1] ^ h.narrow[2] ^ h.narrow[3] ^
+                      h.narrow[4] ^ h.narrow[5] ^ h.narrow[6] ^ h.narrow[7];
+        }
+        else {
+            shift = (nchain + 7) / 8;
+            for (hashval = h.narrow[0], i = 1;   i < 8;  i++)
+                hashval ^= h.narrow[i] << (i * shift);
+        }
+
+        hashval %= nchain;
+    }
+
+    return hashval;
+}
+
+
+
+static void htable_reset(mdb_hash_t *htbl, int do_chain_statistics)
+{
+    hash_entry_t *entry;
+    hash_entry_t *n;
+#ifdef HASH_STATISTICS
+    int i;
+#else
+    (void)do_statistics;
+#endif
+
+    MDB_DLIST_FOR_EACH_SAFE(hash_entry_t, elink, entry,n, &htbl->entries.head){
+        MDB_DLIST_UNLINK(hash_entry_t, clink, entry);
+        MDB_DLIST_UNLINK(hash_entry_t, elink, entry);
+        free(entry);
+    }
+
+#ifdef HASH_STATISTICS
+    if (do_chain_statistics) {
+        for (i = 0;   i < htbl->nchain;   i++) {
+            htbl->chains[i].entries.curr = 0;
+        }
+    }
+
+    htbl->entries.curr = 0;
+#endif
+}
+
+static table_size_t *get_table_size(int max_entries)
+{
+    int dim = sizeof(sizes)/sizeof(sizes[0]);
+    int min = 0;
+    int max = dim - 1;
+    int idx;
+#ifdef DEBUG
+    int iterations = 0;
+#endif
+
+    for (;;) {
+#ifdef DEBUG
+        iterations++;
+#endif
+        idx = (min + max) / 2;
+
+        if (max_entries == sizes[idx].nchain)
+            break;
+
+        if (idx == min) {
+            idx = max;
+            break;
+        }
+
+        if (max_entries < sizes[idx].nchain)
+            max = idx;
+        else
+            min = idx;
+    }
+
+#ifdef DEBUG
+    printf("%s(%d) => {%d,%d} @ %d\n", __FUNCTION__, max_entries,
+           sizes[idx].nchain, sizes[idx].bits, iterations);
+#endif
+
+    return sizes + idx;
+}
+
+static int print_chain(mdb_hash_t *htbl, int index, char *buf, int len)
+{
+    hash_chain_t *chain = htbl->chains + index;
+    hash_entry_t *entry;
+    char *p, *e;
+    char key[256];
+
+    e = (p = buf) + len;
+
+#ifdef HASH_STATISTICS
+    p += snprintf(p, e-p, "   %05d: %d/%d\n",
+                  index, chain->entries.curr, chain->entries.max);
+#else
+    p += snprintf(p, e-p, "   %05d\n", index);
+#endif
+
+    MDB_DLIST_FOR_EACH(hash_entry_t, clink, entry, &chain->head) {
+        if (p >= e)
+            break;
+
+        htbl->hprint(entry->key, key, sizeof(key));
+
+        p += snprintf(p, e-p, "      '%s' / %p\n", key, entry->data);
+    }
+
+    return p - buf;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/index.c b/src/murphy-db/mdb/index.c
new file mode 100644 (file)
index 0000000..6940378
--- /dev/null
@@ -0,0 +1,345 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "index.h"
+#include "row.h"
+#include "column.h"
+#include "table.h"
+
+#include "transaction.h"
+
+#define INDEX_HASH_CREATE(t)        MDB_HASH_TABLE_CREATE(t,100)
+#define INDEX_SEQUENCE_CREATE(t)    MDB_SEQUENCE_TABLE_CREATE(t,16)
+
+#define INDEX_HASH_DROP(ix)         mdb_hash_table_destroy(ix->hash)
+#define INDEX_SEQUENCE_DROP(ix)     mdb_sequence_table_destroy(ix->sequence)
+
+#define INDEX_HASH_RESET(ix)        mdb_hash_table_reset(ix->hash)
+#define INDEX_SEQUENCE_RESET(ix)    mdb_sequence_table_reset(ix->sequence)
+
+
+
+int mdb_index_create(mdb_table_t *tbl, char **index_columns)
+{
+    mdb_index_t     *ix;
+    mdb_column_t    *col;
+    mqi_data_type_t  type;
+    int              beg, end;
+    int             *idxcols;
+    int              i,j, idx;
+
+    MDB_CHECKARG(tbl && index_columns && index_columns[0], -1);
+
+    ix = &tbl->index;
+
+    beg = end = 0;
+    type = mqi_unknown;
+    idxcols = NULL;
+
+    for (i = 0;    index_columns[i];    i++) {
+        if (!(idx = mdb_hash_get_data(tbl->chash,0,index_columns[i]) - NULL)) {
+            errno = ENOENT;
+            return -1;
+        }
+
+        col = tbl->columns + --idx;
+        col->flags |= MQI_COLUMN_KEY;
+
+        if (i == 0) {
+            type = col->type;
+            beg  = col->offset;
+            end  = beg + col->length;
+        }
+        else {
+            type = mqi_blob;
+
+            if (col->offset == end)
+                end += col->length;
+            else if (col->offset == beg - col->length)
+                beg = col->offset;
+            else {
+                type = mqi_unknown;
+                break; /* not an adjacent column */
+            }
+        }
+
+        if (!(idxcols = realloc(idxcols, sizeof(int) * (i+1)))) {
+            errno = ENOMEM;
+            return -1;
+        }
+
+        for (j = 0;  j < i;  j++) {
+            if (idx == idxcols[j])
+                break;
+
+            if (idx < idxcols[j]) {
+                memmove(idxcols + j+1, idxcols + j, sizeof(*idxcols) * (i-j));
+                break;
+            }
+        }
+
+        idxcols[j] = idx;
+    }
+
+    if (type == mqi_unknown || beg < 0 || end <= beg ||
+        end - beg > MDB_INDEX_LENGTH_MAX)
+    {
+        free(idxcols);
+        errno = EIO;
+        return -1;
+    }
+
+    ix->type    = type;
+    ix->length  = end - beg;
+    ix->offset  = beg;
+    ix->ncolumn = i;
+    ix->columns = idxcols;
+
+    switch (type) {
+    case mqi_varchar:
+        ix->hash = INDEX_HASH_CREATE(varchar);
+        ix->sequence = INDEX_SEQUENCE_CREATE(varchar);
+        break;
+    case mqi_integer:
+        ix->hash = INDEX_HASH_CREATE(integer);
+        ix->sequence = INDEX_SEQUENCE_CREATE(integer);
+        break;
+    case mqi_unsignd:
+        ix->hash = INDEX_HASH_CREATE(unsignd);
+        ix->sequence = INDEX_SEQUENCE_CREATE(unsignd);
+        break;
+    case mqi_blob:
+        ix->hash = INDEX_HASH_CREATE(blob);
+        ix->sequence = INDEX_SEQUENCE_CREATE(blob);
+        break;
+    default:
+        free(idxcols);
+        memset(ix, 0, sizeof(*ix));
+        break;
+    }
+
+    return 0;
+}
+
+void mdb_index_drop(mdb_table_t *tbl)
+{
+    mdb_index_t *ix;
+
+    MDB_CHECKARG(tbl,);
+
+    ix = &tbl->index;
+
+    if (MDB_INDEX_DEFINED(ix)) {
+        INDEX_HASH_DROP(ix);
+        INDEX_SEQUENCE_DROP(ix);
+
+        free(ix->columns);
+
+        memset(ix, 0, sizeof(*ix));
+
+        ix->type = mqi_unknown;
+    }
+}
+
+void mdb_index_reset(mdb_table_t *tbl)
+{
+    mdb_index_t *ix;
+
+    MDB_CHECKARG(tbl,);
+
+    ix = &tbl->index;
+
+    if (MDB_INDEX_DEFINED(ix)) {
+        INDEX_HASH_RESET(ix);
+        INDEX_SEQUENCE_RESET(ix);
+    }
+}
+
+
+int mdb_index_insert(mdb_table_t   *tbl,
+                     mdb_row_t     *row,
+                     mqi_bitfld_t   cmask,
+                     int            ignore)
+{
+    mdb_index_t    *ix;
+    int             lgh;
+    void           *key;
+    mdb_hash_t     *hash;
+    mdb_sequence_t *seq;
+    mdb_row_t      *old;
+    uint32_t        txdepth;
+
+    MDB_CHECKARG(tbl && row, -1);
+
+    ix = &tbl->index;
+
+    if (!MDB_INDEX_DEFINED(ix))
+        return 1;               /* fake a sucessful insertion */
+
+    hash = ix->hash;
+    seq  = ix->sequence;
+    lgh  = ix->length;
+    key  = (void *)row->data + ix->offset;
+
+    if (mdb_hash_add(hash, lgh,key, row) == 0) {
+        mdb_sequence_add(seq, lgh,key, row);
+        return 1;
+    }
+
+    /*
+     * we have a duplicate at hand
+     */
+
+    if (ignore) { /* replace the duplicate with the new row */
+
+        /* TODO: move the transaction & log related stuff to table,
+           ie. here deal with indexes only */
+        if ((txdepth = mdb_transaction_get_depth()) < 1) {
+            errno = EIO;
+            return -1;
+        }
+
+        if (!(old = mdb_hash_delete(hash, lgh,key)) ||
+            (old != mdb_sequence_delete(seq, lgh,key)))
+        {
+            /* something is really broken: get out quickly */
+            errno = EIO;
+            return -1;
+        }
+        else {
+            if (mdb_row_delete(tbl, old, 0,0) < 0 ||
+                mdb_log_change(tbl, txdepth, mdb_log_update,cmask,old,row) < 0)
+            {
+                /* errno is either EEXIST or ENOMEM set by mdb_hash_add */
+                return -1;
+            }
+
+            mdb_hash_add(hash, lgh,key, row);
+            mdb_sequence_add(seq, lgh,key, row);
+        }
+    }
+    else { /* duplicate insertion is an error. keep the original row */
+        mdb_row_delete(tbl, row, 0, 1);
+        /* errno is either EEXIST or ENOMEM set by mdb_hash_add */
+        return -1;
+    }
+
+    return 0;
+}
+
+int mdb_index_delete(mdb_table_t *tbl, mdb_row_t *row)
+{
+    mdb_index_t    *ix;
+    int             lgh;
+    void           *key;
+    mdb_hash_t     *hash;
+    mdb_sequence_t *seq;
+
+    MDB_CHECKARG(tbl && row, -1);
+
+    ix = &tbl->index;
+
+    if (!MDB_INDEX_DEFINED(ix))
+        return 0;
+
+    hash = ix->hash;
+    seq  = ix->sequence;
+    lgh  = ix->length;
+    key  = (void *)row->data + ix->offset;
+
+    if (mdb_hash_delete(hash, lgh,key)    != row ||
+        mdb_sequence_delete(seq, lgh,key) != row)
+    {
+        errno = EIO;
+        return -1;
+    }
+
+    return 0;
+}
+
+mdb_row_t *mdb_index_get_row(mdb_table_t *tbl, int idxlen, void *idxval)
+{
+    mdb_index_t *ix;
+
+    MDB_CHECKARG(tbl && idxlen >= 0 && idxval, NULL);
+
+    ix = &tbl->index;
+
+    return mdb_hash_get_data(ix->hash, idxlen, idxval);
+}
+
+int mdb_index_print(mdb_table_t *tbl, char *buf, int len)
+{
+#define PRINT(args...)  if (e > p) p += snprintf(p, e-p, args)
+    mdb_index_t *ix;
+    const char  *sep;
+    char        *p, *e;
+    int          i;
+
+    MDB_CHECKARG(tbl && buf && len > 0, 0);
+
+    ix = &tbl->index;
+
+    MDB_PREREQUISITE(MDB_INDEX_DEFINED(ix), 0);
+
+    e = (p = buf) + len;
+
+    PRINT("index columns: ");
+
+    for (i = 0, sep = "";   i < ix->ncolumn;   i++, sep = ",")
+        PRINT("%s%02d", sep, ix->columns[i]);
+
+    PRINT("\n    type    offset length\n    ---------------------"
+          "\n    %-7s   %4d   %4d\n",
+          mqi_data_type_str(ix->type), ix->offset, ix->length);
+
+    return p - buf;
+
+#undef PRINT
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/index.h b/src/murphy-db/mdb/index.h
new file mode 100644 (file)
index 0000000..3bdf930
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_INDEX_H__
+#define __MDB_INDEX_H__
+
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include <murphy-db/mdb.h>
+
+#include "row.h"
+
+#define MDB_INDEX_LENGTH_MAX 8192
+
+#define MDB_INDEX_DEFINED(ix) ((ix)->type != mqi_unknown)
+
+typedef struct {
+    mqi_data_type_t  type;
+    int              length;
+    int              offset;
+    mdb_hash_t      *hash;
+    mdb_sequence_t  *sequence;
+    int              ncolumn;
+    int             *columns;   /* sorted */
+} mdb_index_t;
+
+
+int mdb_index_create(mdb_table_t *, char **);
+void mdb_index_drop(mdb_table_t *);
+void mdb_index_reset(mdb_table_t *);
+int mdb_index_insert(mdb_table_t *, mdb_row_t *, mqi_bitfld_t, int);
+int mdb_index_delete(mdb_table_t *, mdb_row_t *);
+mdb_row_t *mdb_index_get_row(mdb_table_t *, int, void *);
+int mdb_index_print(mdb_table_t *, char *, int);
+
+
+#endif /* __MDB_INDEX_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/log.c b/src/murphy-db/mdb/log.c
new file mode 100644 (file)
index 0000000..815575b
--- /dev/null
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/sequence.h>
+#include "log.h"
+#include "row.h"
+#include "table.h"
+
+#ifndef LOG_STATISTICS
+#define LOG_STATISTICS
+#endif
+
+#define LOG_COMMON_FIELDS   \
+    mdb_dlist_t     vlink;  \
+    mdb_dlist_t     hlink;  \
+    uint32_t        depth
+
+typedef struct {
+    LOG_COMMON_FIELDS;
+} log_t;
+
+typedef struct {
+    LOG_COMMON_FIELDS;
+} tx_log_t;
+
+typedef struct {
+    LOG_COMMON_FIELDS;
+    mdb_table_t *table;
+    mdb_dlist_t  changes;
+} tbl_log_t;
+
+typedef struct {
+    mdb_dlist_t     link;
+    mdb_log_type_t  type;
+    mqi_bitfld_t    colmask;
+    union {
+        mdb_row_t   *before;
+        mdb_opcnt_t *cnt;
+    };
+    mdb_row_t      *after;
+} change_t;
+
+
+
+static inline log_t *new_log(mdb_dlist_t *, mdb_dlist_t *, uint32_t, int);
+static inline void delete_log(log_t *);
+static inline log_t *get_last_vlog(mdb_dlist_t *);
+static tx_log_t *get_tx_log(uint32_t);
+static tbl_log_t *get_tbl_log(mdb_dlist_t *, mdb_dlist_t *, uint32_t,
+                              mdb_table_t *);
+static void delete_tx_log(uint32_t);
+
+static MDB_DLIST_HEAD(tx_head);
+
+int mdb_log_create(mdb_table_t *tbl)
+{
+    MDB_CHECKARG(tbl, -1);
+
+    MDB_DLIST_INIT(tbl->logs);
+
+    return 0;
+}
+
+
+int mdb_log_change(mdb_table_t    *tbl,
+                   uint32_t        depth,
+                   mdb_log_type_t  type,
+                   mqi_bitfld_t    colmask,
+                   mdb_row_t      *before,
+                   mdb_row_t      *after)
+{
+    tx_log_t  *txlog;
+    tbl_log_t *tblog;
+    change_t  *change;
+
+    MDB_CHECKARG(tbl, -1);
+
+    if (!depth)
+        return 0;
+
+    if (!(txlog = get_tx_log(depth)) ||
+        !(tblog = get_tbl_log(&tbl->logs, &txlog->hlink, depth, tbl)))
+    {
+        return -1;
+    }
+
+    if (!(change = calloc(1, sizeof(change_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    change->type    = type;
+    change->colmask = colmask;
+    change->before  = before;
+    change->after   = after;
+
+    switch (type) {
+    case mdb_log_insert: tbl->cnt.inserts++; break;
+    case mdb_log_delete: tbl->cnt.deletes++; break;
+    case mdb_log_update: tbl->cnt.updates++; break;
+    default:                                 break;
+    }
+
+    MDB_DLIST_PREPEND(change_t, link, change, &tblog->changes);
+
+    return 0;
+}
+
+mdb_log_entry_t *mdb_log_transaction_iterate(uint32_t   depth,
+                                             void     **cursor_ptr,
+                                             bool       forward,
+                                             int        delete)
+{
+    typedef struct {
+        uint32_t         depth;
+        mdb_dlist_t     *hhead;
+        mdb_dlist_t     *chead;
+        mdb_dlist_t     *hlink;
+        mdb_dlist_t     *clink;
+        mdb_log_entry_t  entry;
+    } cursor_t;
+
+    static cursor_t  empty_cursor;
+
+    cursor_t        *cursor;
+    tx_log_t        *txlog;
+    tbl_log_t       *tblog;
+    mdb_dlist_t     *hhead;
+    mdb_dlist_t     *chead;
+    change_t        *change;
+    mdb_log_entry_t *entry;
+
+    MDB_CHECKARG(cursor_ptr, NULL);
+
+    if (!depth)
+        return NULL;
+
+    if ((cursor = *cursor_ptr)) {
+        if (cursor == &empty_cursor)
+            return NULL;
+
+        entry = &cursor->entry;
+    }
+    else {
+        if (!(txlog = (tx_log_t *)get_last_vlog(&tx_head)))
+            return NULL;
+
+        if (depth > txlog->depth)
+            return NULL;
+
+        hhead = &txlog->hlink;
+
+        if (MDB_DLIST_EMPTY(*hhead)) {
+            if (delete)
+                delete_log((log_t *)txlog);
+            return NULL;
+        }
+
+        tblog = MDB_LIST_RELOCATE(tbl_log_t, hlink, hhead->next);
+
+        if (MDB_DLIST_EMPTY(tblog->changes))
+            return NULL;
+
+        chead = &tblog->changes;
+
+        if (!(*cursor_ptr = cursor = calloc(1, sizeof(cursor_t))))
+            return NULL;
+        else {
+            entry = &cursor->entry;
+
+            cursor->depth = txlog->depth;
+            cursor->hhead = hhead;
+            cursor->chead = chead;
+            cursor->hlink = tblog->hlink.next;
+            cursor->clink = forward ? chead->next : chead->prev;
+
+            entry->table = tblog->table;
+        }
+    }
+
+    for (;;) {
+        if (cursor->clink == cursor->chead) {
+            if (delete) {
+                tblog = MDB_LIST_RELOCATE(tbl_log_t, changes, cursor->chead);
+                delete_log((log_t *)tblog);
+            }
+        }
+        else {
+            change = MDB_LIST_RELOCATE(change_t, link, cursor->clink);
+
+            cursor->clink = forward ? change->link.next : change->link.prev;
+
+            entry->change  = change->type;
+            entry->colmask = change->colmask;
+            entry->before  = change->before;
+            entry->after   = change->after;
+
+            if (delete) {
+                MDB_DLIST_UNLINK(change_t, link, change);
+                free(change);
+            }
+
+            return entry;
+        }
+
+        if (cursor->hlink == cursor->hhead) {
+            if (cursor != &empty_cursor) {
+                if (delete)
+                    delete_tx_log(cursor->depth);
+                *cursor_ptr = &empty_cursor;
+                free(cursor);
+            }
+            return NULL;
+        }
+        else {
+            tblog = MDB_LIST_RELOCATE(tbl_log_t, hlink, cursor->hlink);
+            chead = &tblog->changes;
+
+            cursor->hlink = tblog->hlink.next;
+            cursor->chead = chead;
+            cursor->clink = forward ? chead->next : chead->prev;
+
+            entry->table  = tblog->table;
+        }
+    }
+}
+
+
+
+mdb_log_entry_t *mdb_log_table_iterate(mdb_table_t  *tbl,
+                                       void        **cursor_ptr,
+                                       int           delete)
+{
+    typedef struct {
+        mdb_dlist_t     *vhead;
+        mdb_dlist_t     *chead;
+        mdb_dlist_t     *vlink;
+        mdb_dlist_t     *clink;
+        mdb_log_entry_t  entry;
+    } cursor_t;
+
+    static cursor_t  empty_cursor;
+
+    cursor_t        *cursor;
+    tbl_log_t       *tblog;
+    mdb_dlist_t     *vhead;
+    mdb_dlist_t     *chead;
+    change_t        *change;
+    mdb_log_entry_t *entry;
+
+    MDB_CHECKARG(tbl && cursor_ptr, NULL);
+
+    if ((cursor = *cursor_ptr))
+        entry = &cursor->entry;
+    else {
+        vhead = &tbl->logs;
+
+        if (MDB_DLIST_EMPTY(*vhead))
+            return NULL;
+
+        if (!(tblog = (tbl_log_t *)get_last_vlog(vhead)))
+            return NULL;
+
+        if (tblog->table != tbl || MDB_DLIST_EMPTY(tblog->changes))
+            return NULL;
+
+        chead = &tblog->changes;
+
+        if (!(*cursor_ptr = cursor = calloc(1, sizeof(cursor_t))))
+            return NULL;
+        else {
+            entry = &cursor->entry;
+
+            cursor->vhead = vhead;
+            cursor->chead = chead;
+            cursor->vlink = tblog->vlink.prev;
+            cursor->clink = chead->next;
+
+            entry->table = tblog->table;
+        }
+    }
+
+    for (;;) {
+        if (cursor->clink == cursor->chead) {
+            if (delete) {
+                tblog = MDB_LIST_RELOCATE(tbl_log_t, changes, cursor->chead);
+                delete_log((log_t *)tblog);
+            }
+        }
+        else {
+            change = MDB_LIST_RELOCATE(change_t, link, cursor->clink);
+
+            cursor->clink = change->link.next;
+
+            entry->change  = change->type;
+            entry->colmask = change->colmask;
+            entry->before  = change->before;
+            entry->after   = change->after;
+
+            if (delete) {
+                MDB_DLIST_UNLINK(change_t, link, change);
+                free(change);
+            }
+
+            return entry;
+        }
+
+        if (cursor->vlink == cursor->vhead) {
+            if (cursor != &empty_cursor) {
+                *cursor_ptr = &empty_cursor;
+                free(cursor);
+            }
+            return NULL;
+        }
+        else {
+            tblog = MDB_LIST_RELOCATE(tbl_log_t, vlink, cursor->vlink);
+            chead = &tblog->changes;
+
+            cursor->vlink = tblog->vlink.prev;
+            cursor->chead = chead;
+            cursor->clink = chead->next;
+
+            if (tbl != tblog->table)
+                return NULL;
+        }
+    }
+}
+
+
+
+static inline log_t *new_log(mdb_dlist_t *vhead,
+                             mdb_dlist_t *hhead,
+                             uint32_t     depth,
+                             int          size)
+{
+    log_t *log;
+
+    if ((log = calloc(1, size))) {
+        MDB_DLIST_APPEND(mdb_log_t, vlink, log, vhead);
+
+        if (hhead)
+            MDB_DLIST_APPEND(mdb_log_t, hlink, log, hhead);
+        else
+            MDB_DLIST_INIT(log->hlink);
+
+        log->depth = depth;
+    }
+
+    return log;
+}
+
+static inline void delete_log(log_t *log)
+{
+    MDB_DLIST_UNLINK(log_t, vlink, log);
+    MDB_DLIST_UNLINK(log_t, hlink, log);
+
+    free(log);
+}
+
+
+static inline log_t *get_last_vlog(mdb_dlist_t *vhead)
+{
+    if (MDB_DLIST_EMPTY(*vhead))
+        return NULL;
+
+    return MDB_LIST_RELOCATE(log_t, vlink, vhead->prev);
+}
+
+
+static tx_log_t *get_tx_log(uint32_t depth)
+{
+    tx_log_t *log;
+
+    if (!(log = (tx_log_t *)get_last_vlog(&tx_head)) || depth > log->depth) {
+        return (tx_log_t *)new_log(&tx_head, NULL, depth, sizeof(*log));
+    }
+
+    if (depth < log->depth) {
+        errno = ENOKEY;
+        return NULL;
+    }
+
+    return log;
+}
+
+static tbl_log_t *get_tbl_log(mdb_dlist_t *vhead,
+                              mdb_dlist_t *hhead,
+                              uint32_t     depth,
+                              mdb_table_t *tbl)
+{
+    tbl_log_t *log;
+    change_t  *change;
+
+    if (!(log = (tbl_log_t *)get_last_vlog(vhead)) || depth > log->depth) {
+        if ((log = (tbl_log_t *)new_log(vhead, hhead, depth, sizeof(*log)))) {
+            log->table = tbl;
+            MDB_DLIST_INIT(log->changes);
+
+            if (!(change = calloc(1, sizeof(change_t)))) {
+                errno = ENOMEM;
+                return NULL;
+            }
+
+            if (!(change->cnt = calloc(1, sizeof(*change->cnt)))) {
+                free(change);
+                errno = ENOMEM;
+                return NULL;
+            }
+
+            change->type = mdb_log_start;
+            *change->cnt = tbl->cnt;
+            tbl->cnt.stamp++;
+
+            MDB_DLIST_PREPEND(change_t, link, change, &log->changes);
+        }
+    }
+
+    if (!log) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    if (tbl != log->table) {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if (depth < log->depth) {
+        errno = ENOKEY;
+        return NULL;
+    }
+
+    return log;
+}
+
+static void delete_tx_log(uint32_t depth)
+{
+    log_t *log;
+
+    if ((log = get_last_vlog(&tx_head)) && depth == log->depth)
+        delete_log(log);
+}
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/log.h b/src/murphy-db/mdb/log.h
new file mode 100644 (file)
index 0000000..e40fc85
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_LOG_H__
+#define __MDB_LOG_H__
+
+#include <murphy-db/list.h>
+#include <murphy-db/mdb.h>
+#include "row.h"
+
+typedef struct {
+    uint32_t stamp;
+    uint32_t inserts;
+    uint32_t deletes;
+    uint32_t updates;
+} mdb_opcnt_t;
+
+#define MDB_FORWARD  true
+#define MDB_BACKWARD false
+
+#define MDB_TRANSACTION_LOG_FOR_EACH(depth, entry, fw, curs)            \
+    for (curs = NULL; (entry = mdb_log_transaction_iterate(depth,&curs,fw,0));)
+
+#define MDB_TRANSACTION_LOG_FOR_EACH_DELETE(depth, entry, fw, curs)      \
+    for (curs = NULL; (entry = mdb_log_transaction_iterate(depth,&curs,fw,1));)
+
+#define MDB_TABLE_LOG_FOR_EACH(table, entry, curs)                 \
+    for (curs = NULL;  (entry = mdb_log_table_iterate(table, &curs, 0));)
+
+#define MDB_TABLE_LOG_FOR_EACH_DELETE(table, entry, curs)       \
+    for (curs = NULL;  (entry = mdb_log_table_iterate(table, &curs, 1));)
+
+typedef enum {
+    mdb_log_unknown = 0,
+    mdb_log_insert,
+    mdb_log_delete,
+    mdb_log_update,
+    mdb_log_start
+} mdb_log_type_t;
+
+typedef struct {
+    mdb_table_t    *table;
+    mdb_log_type_t  change;
+    mqi_bitfld_t    colmask;
+    union {
+        mdb_row_t   *before;
+        mdb_opcnt_t *cnt;
+    };
+    mdb_row_t      *after;
+} mdb_log_entry_t;
+
+
+int mdb_log_create(mdb_table_t *);
+int mdb_log_change(mdb_table_t *, uint32_t, mdb_log_type_t,
+                   mqi_bitfld_t, mdb_row_t *, mdb_row_t *);
+mdb_log_entry_t *mdb_log_transaction_iterate(uint32_t, void **, bool, int);
+mdb_log_entry_t *mdb_log_table_iterate(mdb_table_t *, void **, int);
+
+
+#endif /* __MDB_LOG_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/mqi-types.c b/src/murphy-db/mdb/mqi-types.c
new file mode 100644 (file)
index 0000000..2496102
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <murphy-db/mqi-types.h>
+
+const char *mqi_data_type_str(mqi_data_type_t type)
+{
+    switch (type) {
+    case mqi_integer:  return "integer";
+    case mqi_unsignd:  return "unsigned";
+    case mqi_varchar:  return "varchar";
+    case mqi_floating: return "floating";
+    case mqi_blob:     return "blob";
+    default:           return "unknown";
+    }
+}
+
+int mqi_data_compare_integer(int datalen, void *data1, void *data2)
+{
+    int32_t integer1;
+    int32_t integer2;
+
+    if (datalen != sizeof(int32_t) || !data1 || !data2)
+        return 0;
+
+    integer1 = *(int32_t *)data1;
+    integer2 = *(int32_t *)data2;
+
+
+    return (integer1 - integer2);
+}
+
+int mqi_data_compare_unsignd(int datalen, void *data1, void *data2)
+{
+    uint32_t unsigned1;
+    uint32_t unsigned2;
+
+    if (datalen != sizeof(uint32_t) || !data1 || !data2)
+        return 0;
+
+    unsigned1 = *(uint32_t *)data1;
+    unsigned2 = *(uint32_t *)data2;
+
+    if (unsigned1 < unsigned2)
+        return -1;
+
+    if (unsigned1 > unsigned2)
+        return 1;
+
+    return 0;
+}
+
+int mqi_data_compare_string(int datalen, void *data1, void *data2)
+{
+    const char *varchar1 = (const char *)data1;
+    const char *varchar2 = (const char *)data2;
+
+    (void)datalen;
+
+    if (!varchar1 || !varchar1[0] || !varchar2 || !varchar2[0])
+        return 0;
+
+    return strcmp(varchar1, varchar2);
+}
+
+int mqi_data_compare_pointer(int datalen, void *data1, void *data2)
+{
+    (void)datalen;
+
+    return ((char *)data1 - (char *)data2);
+}
+
+int mqi_data_compare_varchar(int datalen, void *data1, void *data2)
+{
+    return mqi_data_compare_string(datalen, data1, data2);
+}
+
+int mqi_data_compare_blob(int datalen, void *data1, void *data2)
+{
+    if (!datalen || !data1 || !data2)
+        return 0;
+
+    return memcmp(data1, data2, datalen);
+}
+
+int mqi_data_print_integer(void *data, char *buf, int len)
+{
+    int32_t integer = *(int32_t *)data;
+
+    return snprintf(buf, len, "%d", integer);
+}
+
+int mqi_data_print_unsignd(void *data, char *buf, int len)
+{
+    uint32_t unsignd = *(uint32_t *)data;
+
+    return snprintf(buf, len, "%u", unsignd);
+}
+
+int mqi_data_print_string(void *data, char *buf, int len)
+{
+    const char *varchar = (const char *)data;
+
+    return snprintf(buf, len, "%s", varchar);
+}
+
+int mqi_data_print_pointer(void *data, char *buf, int len)
+{
+    return snprintf(buf, len, "%p", data);
+}
+
+int mqi_data_print_varchar(void *data, char *buf, int len)
+{
+    const char *varchar = (const char *)data;
+
+    return snprintf(buf, len, "%s", varchar);
+}
+
+int mqi_data_print_blob(void *data, char *buf, int len)
+{
+    MQI_UNUSED(data);
+    MQI_UNUSED(buf);
+    MQI_UNUSED(len);
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/row.c b/src/murphy-db/mdb/row.c
new file mode 100644 (file)
index 0000000..6d5e0a2
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "row.h"
+#include "table.h"
+#include "index.h"
+#include "column.h"
+
+
+
+mdb_row_t *mdb_row_create(mdb_table_t *tbl)
+{
+    mdb_row_t *row;
+
+    MDB_CHECKARG(tbl, NULL);
+
+    if (!(row = calloc(1, sizeof(mdb_row_t) + tbl->dlgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    MDB_DLIST_APPEND(mdb_row_t, link, row, &tbl->rows);
+
+    return row;
+}
+
+mdb_row_t *mdb_row_duplicate(mdb_table_t *tbl, mdb_row_t *row)
+{
+    mdb_row_t *dup;
+
+    MDB_CHECKARG(tbl && row, NULL);
+
+    if (!(dup = calloc(1, sizeof(mdb_row_t) + tbl->dlgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    MDB_DLIST_INIT(dup->link);
+    memcpy(dup->data, row->data, tbl->dlgh);
+
+    return dup;
+}
+
+int mdb_row_delete(mdb_table_t *tbl,
+                   mdb_row_t   *row,
+                   int          index_update,
+                   int          free_it)
+{
+    int sts = 0;
+
+    (void)tbl;
+
+    MDB_CHECKARG(row, -1);
+
+    if (index_update && mdb_index_delete(tbl, row) < 0)
+        sts = -1;
+
+    if (!MDB_DLIST_EMPTY(row->link))
+        MDB_DLIST_UNLINK(mdb_row_t, link, row);
+
+    if (free_it)
+        free(row);
+    else
+        MDB_DLIST_INIT(row->link);
+
+    return sts;
+}
+
+int mdb_row_update(mdb_table_t       *tbl,
+                   mdb_row_t         *row,
+                   mqi_column_desc_t *cds,
+                   void              *data,
+                   int                index_update,
+                   mqi_bitfld_t      *cmask_ret)
+{
+    mdb_column_t      *columns;
+    mqi_column_desc_t *source_dsc;
+    int                cidx, cmod;
+    mqi_bitfld_t       cmask;
+    int                i;
+
+    MDB_CHECKARG(tbl && row && cds && data, -1);
+
+    columns = tbl->columns;
+
+    if (index_update)
+        mdb_index_delete(tbl, row);
+
+    cmod = 0;
+    for (cmask = i = 0;  (cidx = (source_dsc = cds + i)->cindex) >= 0;  i++) {
+        cmask |= (((mqi_bitfld_t)1) << cidx);
+        cmod |= mdb_column_write(columns + cidx, row->data, source_dsc, data);
+    }
+
+    if (index_update) {
+        if (mdb_index_insert(tbl, row, cmask, 0) < 0) {
+            if (cmask_ret)
+                *cmask_ret = 0;
+            return -1;
+        }
+    }
+
+    if (cmask_ret)
+        *cmask_ret = cmask;
+
+    if (!cmod)
+        return 0;
+    else
+        return 1;
+}
+
+int mdb_row_copy_over(mdb_table_t *tbl, mdb_row_t *dst, mdb_row_t *src)
+{
+    MDB_CHECKARG(tbl && dst && src, -1);
+
+    if (mdb_index_delete(tbl, dst) < 0)
+        return -1;
+
+    memcpy(dst->data, src->data, tbl->dlgh);
+
+    if (mdb_index_insert(tbl, dst, 0, 0) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/row.h b/src/murphy-db/mdb/row.h
new file mode 100644 (file)
index 0000000..1018ad1
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_ROW_H__
+#define __MDB_ROW_H__
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/list.h>
+#include <murphy-db/mdb.h>
+
+typedef struct mdb_row_s mdb_row_t;
+
+struct mdb_row_s {
+    mdb_dlist_t  link;
+    uint8_t      data[0];
+};
+
+mdb_row_t *mdb_row_create(mdb_table_t *);
+mdb_row_t *mdb_row_duplicate(mdb_table_t *, mdb_row_t *);
+int mdb_row_delete(mdb_table_t *, mdb_row_t *, int, int);
+int mdb_row_update(mdb_table_t *, mdb_row_t *, mqi_column_desc_t *,
+                   void *, int, mqi_bitfld_t *);
+int mdb_row_copy_over(mdb_table_t *, mdb_row_t *, mdb_row_t *);
+
+#endif /* __MDB_ROW_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/sequence.c b/src/murphy-db/mdb/sequence.c
new file mode 100644 (file)
index 0000000..e69e269
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+
+#ifndef SEQUENCE_STATISTICS
+#define SEQUENCE_STATISTICS
+#endif
+
+#include <murphy-db/assert.h>
+#include <murphy-db/sequence.h>
+
+typedef struct mdb_sequence_entry_s {
+    void         *key;
+    void         *data;
+} sequence_entry_t;
+
+
+struct mdb_sequence_s {
+    int                     alloc;
+    mdb_sequence_compare_t  scomp;
+    mdb_sequence_print_t    sprint;
+#ifdef SEQUENCE_STATISTICS
+    int                     max_entry;
+#endif
+    int                     size;
+    int                     nentry;
+    sequence_entry_t       *entries;
+};
+
+
+
+mdb_sequence_t *mdb_sequence_table_create(int                    alloc,
+                                          mdb_sequence_compare_t scomp,
+                                          mdb_sequence_print_t   sprint)
+{
+    mdb_sequence_t *seq;
+
+    MDB_CHECKARG(scomp && sprint && alloc > 0 && alloc < 65536, NULL);
+
+    if (!(seq = calloc(1, sizeof(mdb_sequence_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    seq->alloc  = alloc;
+    seq->scomp  = scomp;
+    seq->sprint = sprint;
+
+    return seq;
+}
+
+int mdb_sequence_table_destroy(mdb_sequence_t *seq)
+{
+    MDB_CHECKARG(seq, -1);
+
+    free(seq->entries);
+    free(seq);
+
+    return 0;
+}
+
+int mdb_sequence_table_get_size(mdb_sequence_t *seq)
+{
+    MDB_CHECKARG(seq, -1);
+
+    return seq->nentry;
+}
+
+int mdb_sequence_table_reset(mdb_sequence_t *seq)
+{
+    MDB_CHECKARG(seq, -1);
+
+    free(seq->entries);
+
+    seq->size    = 0;
+    seq->nentry  = 0;
+    seq->entries = NULL;
+
+    return 0;
+}
+
+int mdb_sequence_table_print(mdb_sequence_t *seq, char *buf, int len)
+{
+    sequence_entry_t *entry;
+    char             *p, *e;
+    int               i;
+    char              key[256];
+
+    MDB_CHECKARG(seq && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+    *buf = '\0';
+
+    for (i = 0;  i < seq->nentry && p < e;  i++) {
+        entry = seq->entries + i;
+
+        seq->sprint(entry->key, key, sizeof(key));
+
+        p += snprintf(p, e-p, "   %05d: '%s' / %p\n", i, key, entry->data);
+    }
+
+    return p - buf;
+}
+
+int mdb_sequence_add(mdb_sequence_t *seq, int klen, void *key, void *data)
+{
+    sequence_entry_t *entry;
+    int               nentry;
+    size_t            old_size;
+    size_t            length;
+    int               cmp;
+    int               min, max, i;
+
+    MDB_CHECKARG(seq && key && data, -1);
+
+    nentry = seq->nentry++;
+
+    if ((nentry + 1) > seq->size) {
+        old_size = seq->size;
+        seq->size += seq->alloc;
+        length = sizeof(sequence_entry_t) * seq->size;
+
+        if (!(seq->entries = realloc(seq->entries, length))) {
+            seq->nentry = 0;
+            errno = ENOMEM;
+            return 0;
+        }
+
+
+        memset(seq->entries + old_size, 0, 
+               length - (old_size * sizeof(sequence_entry_t)));
+    }
+
+    for (min = 0,  i = (max = nentry)/2;   ;    i = (min+max)/2) {
+        if (!(cmp = seq->scomp(klen, key, seq->entries[i].key)))
+            break;
+
+        if (i == min) {
+            if (cmp > 0)
+                i = max;
+            break;
+        }
+
+        if (cmp < 0)
+            max = i;
+        else
+            min = i;
+    }
+
+    entry = seq->entries + i;
+
+    if (i < nentry) {
+        memmove(entry+1, entry, sizeof(sequence_entry_t) * (nentry - i));
+    }
+
+    entry->key  = key;
+    entry->data = data;
+
+#ifdef SEQUENCE_STATISTICS
+    if (seq->nentry > seq->max_entry)
+        seq->max_entry = seq->nentry;
+#endif
+
+    return 0;
+}
+
+void *mdb_sequence_delete(mdb_sequence_t *seq, int klen, void *key)
+{
+    sequence_entry_t *entry;
+    int               i;
+    int               min, max;
+    int               cmp;
+    int               found;
+    void             *data;
+    size_t            length;
+
+    MDB_CHECKARG(seq && key, NULL);
+
+    for (found = 0, min = 0, i = (max = seq->nentry)/2;  ;  i = (min+max)/2) {
+        entry = seq->entries + i;
+
+        if (!(cmp = seq->scomp(klen, key, entry->key))) {
+            found = 1;
+            break;
+        }
+
+        if (i == min) {
+            if (i != max) {
+                entry = seq->entries + max;
+                if (!seq->scomp(klen, key, entry->key))
+                    found = 1;
+            }
+            break;
+        }
+
+        if (cmp < 0)
+            max = i;
+        else
+            min = i;
+    }
+
+    if (!found) {
+        errno = ENOENT;
+        return NULL;
+    }
+
+    data = entry->data;
+
+    if (--seq->nentry <= 0) {
+        free(seq->entries);
+
+        seq->size    = 0;
+        seq->nentry  = 0;
+        seq->entries = NULL;
+    }
+    else {
+        if (i < seq->nentry) {
+            length = sizeof(sequence_entry_t) * (seq->nentry - i);
+            memmove(entry, entry+1, length);
+        }
+
+        if (seq->nentry <= (seq->size - seq->alloc)) {
+            length = sizeof(sequence_entry_t) * (seq->size -= seq->alloc);
+
+            if (!(seq->entries = realloc(seq->entries, length))) {
+                seq->nentry = 0;
+                errno = ENOMEM;
+            }
+        }
+    }
+
+    return data;
+}
+
+
+
+void *mdb_sequence_iterate(mdb_sequence_t *seq, void **cursor_ptr)
+{
+    typedef struct {
+        int   index;
+        int   nentry;
+        void *entries[];
+    } cursor_t;
+
+    static cursor_t   empty_cursor;
+
+    size_t            length;
+    cursor_t         *cursor;
+    int               i;
+
+    MDB_CHECKARG(seq && cursor_ptr, NULL);
+
+    if (!(cursor = *cursor_ptr)) {
+        length = sizeof(cursor_t) + sizeof(void *) * seq->nentry;
+
+        if (!(cursor = malloc(length)))
+            return NULL;
+
+        cursor->index = 0;
+        cursor->nentry = seq->nentry;
+
+        for (i = 0;  i < seq->nentry;  i++)
+            cursor->entries[i] = seq->entries[i].data;
+
+        *cursor_ptr = cursor;
+    }
+
+    if (cursor->index >= cursor->nentry) {
+        if (*cursor_ptr != &empty_cursor) {
+            *cursor_ptr = &empty_cursor;
+            free(cursor);
+        }
+        return NULL;
+    }
+
+    return (void *)cursor->entries[cursor->index++];
+}
+
+
+void mdb_sequence_cursor_destroy(mdb_sequence_t *seq, void **cursor)
+{
+    (void)seq;
+
+    if (cursor)
+        free(*cursor);
+}
+
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/table.c b/src/murphy-db/mdb/table.c
new file mode 100644 (file)
index 0000000..427c70b
--- /dev/null
@@ -0,0 +1,837 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/sequence.h>
+#include "table.h"
+#include "row.h"
+#include "table.h"
+#include "cond.h"
+#include "transaction.h"
+
+#define TABLE_STATISTICS
+
+
+typedef struct {
+    int          indexed;
+    void        *cursor;
+} table_iterator_t;
+
+
+static mdb_hash_t *table_hash;
+static int         table_count;
+
+static void destroy_table(mdb_table_t *);
+static mdb_row_t *table_iterator(mdb_table_t *, table_iterator_t *);
+#if 0
+static int table_print_info(mdb_table_t *, char *, int);
+#endif
+static int select_conditional(mdb_table_t *, mqi_cond_entry_t *,
+                              mqi_column_desc_t *,void *, int, int);
+static int select_all(mdb_table_t *, mqi_column_desc_t  *, void *, int, int);
+static int select_by_index(mdb_table_t*, int,void *, mqi_column_desc_t*,void*);
+static int update_conditional(mdb_table_t *, mqi_cond_entry_t *,
+                              mqi_column_desc_t *, void *, int);
+static int update_all(mdb_table_t *, mqi_column_desc_t *, void *, int);
+static int update_single_row(mdb_table_t *, mdb_row_t *, mqi_column_desc_t *,
+                             void *, int);
+static int delete_conditional(mdb_table_t *, mqi_cond_entry_t *);
+static int delete_all(mdb_table_t *);
+static int delete_single_row(mdb_table_t *, mdb_row_t *, int);
+
+
+mdb_table_t *mdb_table_create(char *name,
+                              char **index_columns,
+                              mqi_column_def_t *cdefs)
+{
+    mdb_table_t      *tbl;
+    mdb_hash_t       *chash;
+    mqi_data_type_t   type;
+    int               length;
+    int               align;
+    int               ncolumn;
+    mdb_column_t     *columns;
+    mdb_column_t     *col;
+    mqi_column_def_t *cdef;
+    int               dlgh;
+    int               i;
+
+    MDB_CHECKARG(name && cdefs, NULL);
+
+    if (!table_hash && !(table_hash = MDB_HASH_TABLE_CREATE(varchar, 256))) {
+        errno = EIO;
+        return NULL;
+    }
+
+    for (ncolumn = 0;  cdefs[ncolumn].name;  ncolumn++) {
+        cdef   = cdefs + ncolumn;
+        type   = cdef->type;
+        length = cdef->length;
+
+        if (!cdef->name[0]) {
+            ncolumn = 0;
+            break;
+        }
+
+        if (type == mqi_varchar) {
+            if (length < 1 || length > MDB_COLUMN_LENGTH_MAX) {
+                ncolumn = 0;
+                break;
+            }
+        }
+        else if (type != mqi_integer &&
+                 type != mqi_unsignd &&
+                 type != mqi_floating )
+        {
+            ncolumn = 0;
+            break;
+        }
+    }
+
+    if (!ncolumn) {
+        errno = EINVAL;
+        return NULL;
+    }
+
+
+    length = sizeof(mdb_table_t) + sizeof(mdb_dlist_t) * ncolumn;
+
+    if (!(tbl = calloc(1, length)) ||
+        !(columns = calloc(ncolumn, sizeof(mdb_column_t))))
+    {
+        free(tbl);
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    if (!(chash = MDB_HASH_TABLE_CREATE(varchar, 16))) {
+        free(tbl);
+        free(columns);
+        return NULL;
+    }
+
+    for (i = 0, dlgh = 0;  i < ncolumn;  i++) {
+        cdef = cdefs  + i;
+        col  = columns + i;
+
+        switch (cdef->type) {
+        case mqi_varchar:  length = cdef->length + 1;  align = 1;    break;
+        case mqi_integer:  length = sizeof(int32_t);   align = 4;    break;
+        case mqi_unsignd:  length = sizeof(uint32_t);  align = 4;    break;
+        case mqi_floating: length = sizeof(double);    align = 4;    break;
+        default:           length = cdef->length;      align = 2;    break;
+        }
+
+        col->name   = strdup(cdef->name);
+        col->type   = cdef->type;
+        col->length = length;
+        col->offset = (dlgh + (align - 1)) & ~(align - 1);
+
+        dlgh = col->offset + col->length;
+
+        mdb_hash_add(chash, 0,col->name, NULL + (i+1));
+    }
+
+    dlgh = (dlgh + 3) & ~3;
+
+    tbl->handle    = MQI_HANDLE_INVALID;
+    tbl->name      = strdup(name);
+    tbl->cnt.stamp = 1;
+    tbl->chash     = chash;
+    tbl->ncolumn   = ncolumn;
+    tbl->columns   = columns;
+    tbl->dlgh      = dlgh;
+
+    MDB_DLIST_INIT(tbl->rows);
+    mdb_log_create(tbl);
+    mdb_trigger_init(&tbl->trigger, ncolumn);
+
+    if (mdb_hash_add(table_hash, 0,tbl->name, tbl) < 0) {
+        destroy_table(tbl);
+        return NULL;
+    }
+
+    if (index_columns)
+        mdb_index_create(tbl, index_columns);
+
+    table_count++;
+
+    return tbl;
+}
+
+int mdb_table_register_handle(mdb_table_t *tbl, mqi_handle_t handle)
+{
+    MDB_CHECKARG(tbl && handle != MQI_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(tbl->handle == MQI_HANDLE_INVALID, -1);
+
+    tbl->handle = handle;
+
+    mdb_trigger_table_create(tbl);
+
+    return 0;
+}
+
+int mdb_table_drop(mdb_table_t *tbl)
+{
+    MDB_CHECKARG(tbl, -1);
+
+    mdb_trigger_table_drop(tbl);
+    mdb_trigger_reset(&tbl->trigger, tbl->ncolumn);
+
+    mdb_transaction_drop_table(tbl);
+
+    mdb_hash_delete(table_hash, 0,tbl->name);
+
+    destroy_table(tbl);
+
+    if (table_count > 1)
+        table_count--;
+    else {
+        MDB_HASH_TABLE_DESTROY(table_hash);
+        table_hash  = NULL;
+        table_count = 0;
+    }
+
+    return 0;
+}
+
+int mdb_table_create_index(mdb_table_t *tbl, char **index_columns)
+{
+    mdb_row_t *row, *n;
+    int        error = 0;
+
+    MDB_CHECKARG(tbl && index_columns && index_columns[0], -1);
+
+    if (MDB_TABLE_HAS_INDEX(tbl)) {
+        errno = EEXIST;
+        return -1;
+    }
+
+    if (mdb_index_create(tbl, index_columns) < 0)
+        return -1;
+
+    MDB_DLIST_FOR_EACH_SAFE(mdb_row_t, link, row,n, &tbl->rows) {
+        if (mdb_index_insert(tbl, row, 0, 0) < 0) {
+            if ((error = errno) != EEXIST)
+                return -1;
+        }
+    }
+
+    if (error) {
+        errno = error;
+        return -1;
+    }
+
+    return 0;
+}
+
+
+int mdb_table_describe(mdb_table_t *tbl, mqi_column_def_t *defs, int len)
+{
+    mdb_column_t *col;
+    mqi_column_def_t *def;
+    int i,n;
+
+    MDB_CHECKARG(tbl && defs && len > 0 && len >= (n = tbl->ncolumn), -1);
+
+    for (i = 0;    i < n;    i++) {
+        col = tbl->columns + i;
+        def = defs + i;
+
+        def->name   = col->name;
+        def->type   = col->type;
+        def->length = col->length;
+        def->flags  = col->flags;
+
+        if (def->type == mqi_varchar && def->length > 0)
+            def->length--;
+    }
+
+    return n;
+}
+
+int mdb_table_insert(mdb_table_t        *tbl,
+                     int                 ignore,
+                     mqi_column_desc_t  *cds,
+                     void              **data)
+{
+    uint32_t   txdepth = mdb_transaction_get_depth();
+    mdb_row_t    *row;
+    int           error;
+    int           nrow;
+    int           ninsert;
+    mqi_bitfld_t  cmask;
+    int           i;
+
+    MDB_CHECKARG(tbl && cds && data && data[0], -1);
+
+    for (i = 0, error = 0, ninsert = 0;    data[i];    i++) {
+        if (!(row = mdb_row_create(tbl))) {
+            errno = ENOMEM;
+            return -1;
+        }
+
+        mdb_row_update(tbl, row, cds, data[i], 0, &cmask);
+
+        if ((nrow = mdb_index_insert(tbl, row, cmask, ignore)) < 0) {
+            if ((error = errno) != EEXIST)
+                return -1;
+
+            ninsert = -1;
+        }
+        else if (nrow > 0) {
+            tbl->nrow++;
+
+            if (mdb_log_change(tbl,txdepth,mdb_log_insert,cmask,NULL,row) < 0)
+                ninsert = -1;
+            else
+                ninsert += (ninsert >= 0) ? 1 : 0;
+        }
+
+    }
+
+    if (error) {
+        errno = error;
+        return -1;
+    }
+
+    return ninsert;
+}
+
+int mdb_table_select(mdb_table_t       *tbl,
+                     mqi_cond_entry_t  *cond,
+                     mqi_column_desc_t *cds,
+                     void              *results,
+                     int                size,
+                     int                dim)
+{
+    int  ndata;
+
+    MDB_CHECKARG(tbl, -1);
+
+    if (dim > MQI_QUERY_RESULT_MAX)
+        dim = MQI_QUERY_RESULT_MAX;
+
+    if (cond)
+        ndata = select_conditional(tbl, cond, cds, results, size, dim);
+    else
+        ndata = select_all(tbl, cds, results, size, dim);
+
+    return ndata;
+}
+
+int mdb_table_select_by_index(mdb_table_t *tbl,
+                              mqi_variable_t *idxvars,
+                              mqi_column_desc_t *cds,
+                              void *result)
+{
+    mqi_variable_t    *var;
+    mdb_index_t       *ix;
+    mdb_column_t      *col;
+    void              *data;
+    mqi_column_desc_t  src;
+    int                idxlen;
+    char               idxval[MDB_INDEX_LENGTH_MAX];
+    int                i;
+
+    MDB_CHECKARG(tbl && idxvars && cds && result, -1);
+    MDB_PREREQUISITE(MDB_TABLE_HAS_INDEX(tbl), -1);
+
+    ix = &tbl->index;
+    data = idxval - ix->offset;
+    src.offset = 0;
+    idxlen = ix->length;
+
+    for (i = 0;   i < ix->ncolumn;   i++) {
+        var = idxvars + i;
+        col = tbl->columns + (src.cindex = ix->columns[i]);
+
+        if (col->type != var->type) {
+            errno = EINVAL;
+            return -1;
+        }
+
+        mdb_column_write(col, data, &src, var->v.generic);
+    }
+
+    return select_by_index(tbl, idxlen,idxval, cds, result);
+}
+
+int mdb_table_update(mdb_table_t       *tbl,
+                     mqi_cond_entry_t  *cond,
+                     mqi_column_desc_t *cds,
+                     void              *data)
+{
+    int           index_update = 0;
+    mdb_column_t *col;
+    int           cindex;
+    int           nupdate;
+    int           i;
+
+    MDB_CHECKARG(tbl, -1);
+
+
+    if (MDB_TABLE_HAS_INDEX(tbl)) {
+        for (i = 0;   (cindex = cds[i].cindex) >= 0;    i++) {
+            col = tbl->columns + cindex;
+            if ((col->flags & MQI_COLUMN_KEY)) {
+                index_update = 1;
+                break;
+            }
+        }
+    }
+
+    if (cond)
+        nupdate = update_conditional(tbl, cond, cds, data, index_update);
+    else
+        nupdate = update_all(tbl, cds, data, index_update);
+
+    return nupdate;
+}
+
+int mdb_table_delete(mdb_table_t *tbl, mqi_cond_entry_t *cond)
+{
+    int ndelete;
+
+    MDB_CHECKARG(tbl, -1);
+
+    if (cond)
+        ndelete = delete_conditional(tbl, cond);
+    else
+        ndelete = delete_all(tbl);
+
+    return ndelete;
+}
+
+mdb_table_t *mdb_table_find(char *table_name)
+{
+    MDB_CHECKARG(table_name, NULL);
+    MDB_PREREQUISITE(table_hash, NULL);
+
+    return mdb_hash_get_data(table_hash, 0,table_name);
+}
+
+
+int mdb_table_get_column_index(mdb_table_t *tbl, char *column_name)
+{
+    MDB_CHECKARG(tbl && column_name, -1);
+
+    return (mdb_hash_get_data(tbl->chash, 0,column_name) - NULL) - 1;
+}
+
+int mdb_table_get_size(mdb_table_t *tbl)
+{
+    MDB_CHECKARG(tbl, -1);
+
+    return tbl->nrow;
+}
+
+char *mdb_table_get_column_name(mdb_table_t *tbl, int colidx)
+{
+    MDB_CHECKARG(tbl && colidx >= 0 && colidx < tbl->ncolumn, NULL);
+
+    return tbl->columns[colidx].name;
+}
+
+mqi_data_type_t mdb_table_get_column_type(mdb_table_t *tbl, int colidx)
+{
+    MDB_CHECKARG(tbl && colidx >= 0 && colidx < tbl->ncolumn, mqi_error);
+
+    return tbl->columns[colidx].type;
+}
+
+int mdb_table_get_column_size(mdb_table_t *tbl, int colidx)
+{
+    MDB_CHECKARG(tbl && colidx >= 0 && colidx < tbl->ncolumn, -1);
+
+    return tbl->columns[colidx].length;
+}
+
+uint32_t mdb_table_get_stamp(mdb_table_t *tbl)
+{
+    return tbl->cnt.stamp;
+}
+
+int mdb_table_print_rows(mdb_table_t *tbl, char *buf, int len)
+{
+    mdb_row_t *row;
+    char      *p, *e;
+    int       l;
+    int       i;
+    char      dashes[1024];
+    table_iterator_t it;
+
+    MDB_CHECKARG(tbl && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+
+    for (i = 0;  i < tbl->ncolumn;  i++)
+        p += mdb_column_print_header(tbl->columns + i, p, e-p);
+
+    if (p + ((l = p - buf) + 3) < e) {
+        if (l > (int)sizeof(dashes) - 1)
+            l = sizeof(dashes) - 1;
+
+        memset(dashes, '-', l);
+        dashes[l] = '\0';
+
+        p += snprintf(p, e-p, "\n%s\n", dashes);
+
+        for (it.cursor = NULL;  (row = table_iterator(tbl, &it)) && p < e;) {
+            for (i = 0;  i < tbl->ncolumn && p < e;  i++)
+                p += mdb_column_print(tbl->columns + i, row->data, p, e-p);
+            if (p < e)
+                p += snprintf(p, e-p, "\n");
+        }
+    }
+
+    return p - buf;
+}
+
+
+static void destroy_table(mdb_table_t *tbl)
+{
+    mdb_row_t    *row, *n;
+    mdb_column_t *cols;
+    int           i;
+
+    mdb_index_drop(tbl);
+
+    mdb_hash_table_destroy(tbl->chash);
+
+    MDB_DLIST_FOR_EACH_SAFE(mdb_row_t, link, row,n, &tbl->rows)
+        mdb_row_delete(tbl, row, 0, 1);
+
+    for (i = 0, cols = tbl->columns;   i < tbl->ncolumn;    i++)
+        free(cols[i].name);
+
+    free(tbl->columns);
+    free(tbl->name);
+    free(tbl);
+}
+
+
+static mdb_row_t *table_iterator(mdb_table_t *tbl, table_iterator_t *it)
+{
+    mdb_dlist_t *next;
+    mdb_dlist_t *head;
+    mdb_row_t   *row;
+
+    if (!it->cursor)
+        it->indexed = MDB_TABLE_HAS_INDEX(tbl);
+
+    if (it->indexed)
+        row = mdb_sequence_iterate(tbl->index.sequence, &it->cursor);
+    else {
+        head = &tbl->rows;
+        next = it->cursor ? (mdb_dlist_t *)it->cursor : head->next;
+
+        if (next == head)
+            row = NULL;
+        else {
+            row = MDB_LIST_RELOCATE(mdb_row_t, link, next);
+            it->cursor = next->next;
+        }
+    }
+
+    return row;
+}
+
+#if 0
+static int table_print_info(mdb_table_t *tbl, char *buf, int len)
+{
+#define PRINT(args...)  if (e > p) p += snprintf(p, e-p, args)
+
+    mdb_column_t *col;
+    char         *p, *e;
+    int           i;
+
+    MDB_CHECKARG(tbl && buf && len > 0, 0);
+
+    e = (p = buf) + len;
+    *buf = '\0';
+
+    PRINT("table name  : '%s'\n", tbl->name);
+    PRINT("table stamp : %u\n"  , tbl->stamp);
+    PRINT("row length  : %d\n"  , tbl->dlgh);
+    PRINT("no of column: %d\n"  , tbl->ncolumn);
+    PRINT("    index name             type     offset length\n"
+          "    ---------------------------------------------\n");
+
+    for (i = 0;  i < tbl->ncolumn;  i++) {
+        col = tbl->columns + i;
+
+        PRINT("    %s %02d: %-16s %-8s   %4d   %4d\n",
+              col->flags & MQI_COLUMN_KEY ? "*" : " ",
+              i+1, col->name, mqi_data_type_str(col->type),
+              col->offset, col->length);
+    }
+
+    p += mdb_index_print(tbl, p, e-p);
+
+    return p - buf;
+
+#undef PRINT
+}
+#endif
+
+
+static int select_conditional(mdb_table_t       *tbl,
+                              mqi_cond_entry_t  *cond,
+                              mqi_column_desc_t *cds,
+                              void              *results,
+                              int                size,
+                              int                dim)
+{
+    mdb_column_t      *columns = tbl->columns;
+    mdb_row_t         *row;
+    mqi_cond_entry_t  *ce;
+    table_iterator_t   it;
+    int                nresult;
+    void              *result;
+    mqi_column_desc_t *result_dsc;
+    int                cindex;
+    int                i;
+
+    for (it.cursor = NULL, nresult = 0;  (row = table_iterator(tbl, &it)); ) {
+        ce = cond;
+        if (mdb_cond_evaluate(tbl, &ce, row->data)) {
+            if (nresult >= dim) {
+                errno = EOVERFLOW;
+                return -1;
+            }
+
+            result = results + (size * nresult++);
+
+            for (i = 0;  (cindex = (result_dsc = cds + i)->cindex) >= 0;   i++)
+                mdb_column_read(result_dsc, result, columns+cindex, row->data);
+        }
+    }
+
+    return nresult;
+}
+
+static int select_all(mdb_table_t       *tbl,
+                      mqi_column_desc_t *cds,
+                      void              *results,
+                      int                size,
+                      int                dim)
+{
+    mdb_column_t      *columns = tbl->columns;
+    mdb_row_t         *row;
+    table_iterator_t   it;
+    int                nresult;
+    void              *result;
+    mqi_column_desc_t *result_dsc;
+    int                cindex;
+    int                j;
+
+    MQI_UNUSED(dim);
+
+    for (it.cursor = NULL, nresult = 0;
+         (row = table_iterator(tbl, &it));
+         nresult++)
+    {
+        result = results + (size * nresult);
+
+        for (j = 0;   (cindex = (result_dsc = cds + j)->cindex) >= 0;    j++)
+            mdb_column_read(result_dsc, result, columns + cindex, row->data);
+    }
+
+    return nresult;
+}
+
+static int select_by_index(mdb_table_t       *tbl,
+                           int                idxlen,
+                           void              *idxval,
+                           mqi_column_desc_t *cds,
+                           void              *result)
+{
+    mdb_column_t      *columns = tbl->columns;
+    mdb_row_t         *row;
+    mqi_column_desc_t *result_dsc;
+    int                cindex;
+    int                j;
+
+    if (!(row = mdb_index_get_row(tbl, idxlen,idxval)))
+        return 0;
+
+    for (j = 0;   (cindex = (result_dsc = cds + j)->cindex) >= 0;    j++)
+            mdb_column_read(result_dsc, result, columns + cindex, row->data);
+
+    return 1;
+}
+
+
+static int update_conditional(mdb_table_t       *tbl,
+                              mqi_cond_entry_t  *cond,
+                              mqi_column_desc_t *cds,
+                              void              *data,
+                              int                index_update)
+{
+    mdb_row_t        *row;
+    mqi_cond_entry_t *ce;
+    table_iterator_t  it;
+    int               nupdate, changed;
+
+    for (it.cursor = NULL, nupdate = 0;  (row = table_iterator(tbl, &it)); ) {
+        ce = cond;
+        if (mdb_cond_evaluate(tbl, &ce, row->data)) {
+            changed = update_single_row(tbl, row, cds, data, index_update);
+
+            if (changed < 0)
+                nupdate = -1;
+            else
+                nupdate += (nupdate >= 0) ? changed : 0;
+        }
+    }
+
+    return nupdate;
+}
+
+static int update_all(mdb_table_t       *tbl,
+                      mqi_column_desc_t *cds,
+                      void              *data,
+                      int                index_update)
+{
+    mdb_row_t        *row;
+    table_iterator_t  it;
+    int               nupdate, changed;
+
+    for (it.cursor = NULL, nupdate = 0; (row = table_iterator(tbl, &it)); )
+    {
+        changed = update_single_row(tbl, row, cds, data, index_update);
+
+        if (changed < 0)
+            nupdate = -1;
+        else
+            nupdate += (nupdate >= 0) ? changed : 0;
+    }
+
+    if (nupdate < 0)
+        errno = EEXIST;
+
+    return nupdate;
+}
+
+static int update_single_row(mdb_table_t       *tbl,
+                             mdb_row_t         *row,
+                             mqi_column_desc_t *cds,
+                             void              *data,
+                             int                index_update)
+{
+    mdb_row_t   *before  = NULL;
+    uint32_t     txdepth = mdb_transaction_get_depth();
+    mqi_bitfld_t cmask;
+    int          changed;
+
+    if (txdepth > 0 && !(before = mdb_row_duplicate(tbl, row)))
+        return -1;
+
+    changed = mdb_row_update(tbl, row, cds, data, index_update, &cmask);
+
+    if (changed <= 0) {
+        mdb_row_delete(tbl, before, 0, 1);
+        return changed;
+    }
+
+    if (mdb_log_change(tbl, txdepth, mdb_log_update, cmask, before, row) < 0)
+        return -1;
+
+    return 1;
+}
+
+static int delete_conditional(mdb_table_t *tbl, mqi_cond_entry_t *cond)
+{
+    table_iterator_t  it;
+    mdb_row_t        *row;
+    mqi_cond_entry_t *ce;
+    int               ndelete;
+
+    for (it.cursor = NULL, ndelete = 0; (row = table_iterator(tbl, &it)); )
+    {
+        ce = cond;
+        if (mdb_cond_evaluate(tbl, &ce, row->data)) {
+            if (delete_single_row(tbl, row, 1) < 0)
+                ndelete = -1;
+            else
+                ndelete += (ndelete >= 0) ? 1 : 0;
+        }
+    }
+
+    return ndelete;
+}
+
+
+static int delete_all(mdb_table_t *tbl)
+{
+    mdb_row_t *row, *n;
+    int        ndelete = 0;
+
+    mdb_index_reset(tbl);
+
+    MDB_DLIST_FOR_EACH_SAFE(mdb_row_t, link, row,n, &tbl->rows) {
+        if (delete_single_row(tbl, row, 0) < 0)
+            ndelete = -1;
+        else
+            ndelete += (ndelete >= 0) ? 1 : 0;
+    }
+
+    return ndelete;
+}
+
+static int delete_single_row(mdb_table_t *tbl, mdb_row_t *row,int index_update)
+{
+    uint32_t txdepth = mdb_transaction_get_depth();
+
+    mdb_row_delete(tbl, row, index_update, !txdepth);
+
+    if (txdepth)
+        mdb_log_change(tbl, txdepth, mdb_log_delete, 0, row, NULL);
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/table.h b/src/murphy-db/mdb/table.h
new file mode 100644 (file)
index 0000000..a806795
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_TABLE_H__
+#define __MDB_TABLE_H__
+
+#include <murphy-db/mdb.h>
+#include <murphy-db/hash.h>
+#include <murphy-db/list.h>
+#include "index.h"
+#include "column.h"
+#include "log.h"
+#include "trigger.h"
+
+#define MDB_TABLE_HAS_INDEX(t)  MDB_INDEX_DEFINED(&t->index)
+
+struct mdb_table_s {
+    mqi_handle_t  handle;
+    char         *name;
+    mdb_index_t   index;
+    mdb_hash_t   *chash;         /* hash table for column names */
+    int           ncolumn;
+    mdb_column_t *columns;
+    int           dlgh;          /* length of row data */
+    int           nrow;
+    mdb_dlist_t   rows;
+    mdb_dlist_t   logs;         /* transaction logs */
+    mdb_opcnt_t   cnt;
+    mdb_trigger_t trigger;      /* must be the last: it has a array[0] @end  */
+};
+
+
+#endif /* __MDB_TABLE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/transaction.c b/src/murphy-db/mdb/transaction.c
new file mode 100644 (file)
index 0000000..9b0a8b7
--- /dev/null
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "transaction.h"
+#include "log.h"
+#include "index.h"
+#include "table.h"
+
+#define TRANSACTION_STATISTICS
+
+
+static uint32_t txdepth;
+
+static int destroy_row(mdb_table_t *, mdb_row_t *);
+static int remove_row(mdb_table_t *, mdb_row_t *);
+static int add_row(mdb_table_t *, mdb_row_t *);
+static int copy_row(mdb_table_t *, mdb_row_t *, mdb_row_t *);
+static int check_stamp(mdb_log_entry_t *);
+
+
+uint32_t mdb_transaction_begin(void)
+{
+    return ++txdepth;
+}
+
+int mdb_transaction_commit(uint32_t depth)
+{
+#define DATA_MAX  (MQI_COLUMN_MAX * MQI_QUERY_RESULT_MAX)
+#define CHECK_TRIGGER_START(en) do {                    \
+        if (!start_triggered) {                         \
+            start_triggered = true;                     \
+            mdb_trigger_transaction_start(depth);       \
+        }                                               \
+    } while (0)
+#define CHECK_TRIGGER_END() do {                        \
+        if (start_triggered) {                          \
+            mdb_trigger_transaction_end(depth);         \
+        }                                               \
+    } while (0)
+
+
+    static uint8_t    blank[sizeof(mdb_row_t) + DATA_MAX];
+
+    mdb_log_entry_t  *en;
+    mdb_row_t        *before;
+    mdb_row_t        *after;
+    void             *cursor;
+    bool              start_triggered = false;
+    int               sts = 0, s;
+
+    MDB_CHECKARG(depth > 0 && depth == txdepth, -1);
+
+    MDB_TRANSACTION_LOG_FOR_EACH_DELETE(depth, en, MDB_BACKWARD, cursor) {
+
+        if (!(before = en->before))
+            before = (mdb_row_t *)blank;
+
+        if (!(after = en->after))
+            after = (mdb_row_t *)blank;
+
+        switch (en->change) {
+
+        case mdb_log_insert:
+            CHECK_TRIGGER_START(en);
+            mdb_trigger_row_insert(en->table, after);
+            mdb_trigger_column_change(en->table, en->colmask, before, after);
+            s = 0;
+            break;
+
+        case mdb_log_update:
+            CHECK_TRIGGER_START(en);
+            mdb_trigger_column_change(en->table, en->colmask, before, after);
+            s = destroy_row(en->table, en->before);
+            break;
+
+        case mdb_log_delete:
+            CHECK_TRIGGER_START(en);
+            mdb_trigger_row_delete(en->table, before);
+            s = destroy_row(en->table, en->before);
+            break;
+
+        case mdb_log_start:
+            check_stamp(en);
+            free(en->cnt);
+            s = 0;
+            break;
+
+        default:
+            s = -1;
+            break;
+        }
+
+        if (sts == 0)
+            sts = s;
+    }
+
+    txdepth--;
+
+    CHECK_TRIGGER_END();
+
+    return sts;
+
+#undef DATA_MAX
+}
+
+int mdb_transaction_rollback(uint32_t depth)
+{
+    mdb_log_entry_t  *en;
+    mdb_table_t      *tbl;
+    void             *cursor;
+    int               sts = 0, s;
+
+    MDB_CHECKARG(depth > 0 && depth == txdepth, -1);
+
+    MDB_TRANSACTION_LOG_FOR_EACH_DELETE(depth, en, MDB_FORWARD, cursor) {
+
+        tbl = en->table;
+
+        switch (en->change) {
+
+        case mdb_log_insert:  s = remove_row(tbl, en->after);            break;
+        case mdb_log_delete:  s = add_row(tbl, en->before);              break;
+        case mdb_log_update:  s = copy_row(tbl, en->after, en->before);  break;
+        case mdb_log_start:   s = check_stamp(en);                       break;
+        default:              s = -1;                                    break;
+        }
+
+        if (sts == 0)
+            sts = s;
+    }
+
+    txdepth--;
+
+    return sts;
+}
+
+int mdb_transaction_drop_table(mdb_table_t *tbl)
+{
+    mdb_log_entry_t *en;
+    void            *cursor;
+    int              sts = 0, s;
+
+    MDB_CHECKARG(tbl, -1);
+
+    MDB_TABLE_LOG_FOR_EACH_DELETE(tbl, en, cursor) {
+
+        switch (en->change) {
+
+        case mdb_log_insert:  s = 0;                                     break;
+        case mdb_log_delete:
+        case mdb_log_update:  s = destroy_row(en->table, en->before);    break;
+        case mdb_log_start:   s = 0;                                     break;
+        default:              s = -1;                                    break;
+        }
+
+        if (sts == 0)
+            sts = s;
+    }
+
+    return sts;
+}
+
+uint32_t mdb_transaction_get_depth(void)
+{
+    return txdepth;
+}
+
+static int destroy_row(mdb_table_t *tbl, mdb_row_t *row)
+{
+    MDB_CHECKARG(tbl && row && MDB_DLIST_EMPTY(row->link), -1);
+
+    return mdb_row_delete(tbl, row, 0, 1);
+}
+
+static int remove_row(mdb_table_t *tbl, mdb_row_t *row)
+{
+    MDB_CHECKARG(tbl && row, -1);
+
+    if (mdb_index_delete(tbl, row) < 0 ||
+        mdb_row_delete(tbl, row, 0, 1) < 0    )
+    {
+        return -1;
+    }
+
+    tbl->cnt.inserts--;
+
+    return 0;
+}
+
+static int add_row(mdb_table_t *tbl, mdb_row_t *row)
+{
+    MDB_CHECKARG(tbl && row, -1);
+
+    MDB_DLIST_APPEND(mdb_row_t, link, row, &tbl->rows);
+
+    tbl->cnt.deletes--;
+
+    return mdb_index_insert(tbl, row, 0, 0);
+}
+
+static int copy_row(mdb_table_t *tbl, mdb_row_t *dst, mdb_row_t *src)
+{
+
+    MDB_CHECKARG(tbl && dst && src && MDB_DLIST_EMPTY(src->link), -1);
+
+    if (src == dst)
+        return 0;
+
+    if (mdb_row_copy_over(tbl,dst,src) < 0 || mdb_row_delete(tbl,src,0,1) < 0)
+        return -1;
+
+    tbl->cnt.updates--;
+
+    return 0;
+}
+
+
+static int check_stamp(mdb_log_entry_t *en)
+{
+    mdb_table_t *tbl;
+
+    if (en->change != mdb_log_start)
+        return -1;
+
+    tbl = en->table;
+
+    if (tbl->cnt.inserts == en->cnt->inserts &&
+        tbl->cnt.deletes == en->cnt->deletes &&
+        tbl->cnt.updates == en->cnt->updates)
+        tbl->cnt.stamp = en->cnt->stamp;
+
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/transaction.h b/src/murphy-db/mdb/transaction.h
new file mode 100644 (file)
index 0000000..c59fd83
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_TRANSACTION_H__
+#define __MDB_TRANSACTION_H__
+
+#include <murphy-db/mdb.h>
+
+
+int mdb_transaction_drop_table(mdb_table_t *);
+
+
+#endif /* __MDB_TRANSACTION_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/trigger.c b/src/murphy-db/mdb/trigger.c
new file mode 100644 (file)
index 0000000..a8eaf41
--- /dev/null
@@ -0,0 +1,628 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <alloca.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include "table.h"
+#include "row.h"
+
+#ifndef LOG_TRIGGER
+#define LOG_TRIGGER
+#endif
+
+typedef struct callback_s         callback_t;
+typedef struct select_s           select_t;
+
+typedef struct column_trigger_s   column_trigger_t;
+typedef struct row_trigger_s      row_trigger_t;
+typedef struct table_trigger_s    table_trigger_t;
+typedef struct transact_trigger_s transact_trigger_t;
+
+struct callback_s {
+    mqi_trigger_cb_t  function;
+    void             *user_data;
+};
+
+struct select_s {
+    int               length;
+    size_t            cdsiz;
+    mqi_column_desc_t column[0];
+};
+
+struct column_trigger_s {
+    mdb_dlist_t link;
+    callback_t  callback;
+    select_t    select;
+};
+
+struct row_trigger_s {
+    mdb_dlist_t link;
+    callback_t  callback;
+    select_t    select;
+};
+
+struct table_trigger_s {
+    mdb_dlist_t  link;
+    callback_t   callback;
+};
+
+struct transact_trigger_s {
+    mdb_dlist_t  link;
+    callback_t   callback;
+};
+
+
+static int8_t lowest_bit_in[256] = {
+    /*         0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F */
+    /*        -------------------------------------------------------------- */
+    /* 00 */  -1,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 10 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 20 */   5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 30 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 40 */   6,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 50 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 60 */   5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 70 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 80 */   7,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* 90 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* A0 */   5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* B0 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* C0 */   6,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* D0 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* E0 */   5,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+    /* F0 */   4,  0,  1,  0,  2,  0,  1,  0,  3,  0,  1,  0,  2,  0,  1,  0,
+};
+
+
+static MDB_DLIST_HEAD(table_change_triggers);
+static MDB_DLIST_HEAD(transact_change_triggers);
+
+static int get_select_params(mdb_table_t *, mqi_column_desc_t *, int *, int *);
+static void row_change(mqi_event_type_t, mdb_table_t *, mdb_row_t *);
+static void table_change(mqi_event_type_t, mdb_table_t *);
+static void transaction_change(mqi_event_type_t, uint32_t);
+
+
+void mdb_trigger_init(mdb_trigger_t *trigger, int ncol)
+{
+    int i;
+
+    if (!trigger || ncol < 1)
+        return;
+
+    MDB_DLIST_INIT(trigger->row_change);
+
+    for (i = 0;  i < ncol;  i++)
+        MDB_DLIST_INIT(trigger->column_change[i]);
+}
+
+void mdb_trigger_reset(mdb_trigger_t *trigger, int ncol)
+{
+    row_trigger_t *rt, *n;
+    column_trigger_t *ct, *m;
+    mdb_dlist_t *head;
+    int i;
+
+    if (!trigger || ncol < 1)
+        return;
+
+    MDB_DLIST_FOR_EACH_SAFE(row_trigger_t, link, rt,n, &trigger->row_change) {
+        MDB_DLIST_UNLINK(row_trigger_t, link, rt);
+        free(rt);
+    }
+
+    for (i = 0;  i < ncol;  i++) {
+        head = trigger-> column_change + i;
+
+        MDB_DLIST_FOR_EACH_SAFE(column_trigger_t, link, ct,m, head) {
+            MDB_DLIST_UNLINK(column_trigger_t, link, ct);
+            free(ct);
+        }
+    }
+}
+
+
+int mdb_trigger_add_column_callback(mdb_table_t       *tbl,
+                                    int                cidx,
+                                    mqi_trigger_cb_t   cb_function,
+                                    void              *cb_data,
+                                    mqi_column_desc_t *cds)
+{
+    column_trigger_t *tr;
+    size_t cdsiz;
+    int length, ncd;
+    mdb_dlist_t *head;
+
+    MDB_CHECKARG(tbl && cidx >= 0 && cidx < tbl->ncolumn && cb_function, -1);
+
+    if (!cds)
+        ncd = length = 0;
+    else {
+        if (get_select_params(tbl, cds, &ncd, &length) < 0) {
+            errno = EINVAL;
+            return -1;
+        }
+    }
+
+    cdsiz = sizeof(mqi_column_desc_t) * ncd;
+    head  = tbl->trigger.column_change + cidx;
+
+    MDB_DLIST_FOR_EACH(column_trigger_t, link, tr, head) {
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            if (cdsiz == tr->select.cdsiz) {
+                if (!cdsiz || memcmp(cds, tr->select.column, cdsiz))
+                    return 0; /* silently ignore multiple registrations */
+            }
+
+            errno = EEXIST;
+            return -1;
+        }
+    }
+
+    if (!(tr = calloc(1, sizeof(column_trigger_t) + cdsiz))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    MDB_DLIST_APPEND(column_trigger_t, link, tr, head);
+
+    tr->callback.function = cb_function;
+    tr->callback.user_data = cb_data;
+
+    tr->select.length = length;
+    tr->select.cdsiz = cdsiz;
+
+    if (ncd > 0)
+        memcpy(tr->select.column, cds, cdsiz);
+
+    return 0;
+}
+
+int mdb_trigger_delete_column_callback(mdb_table_t      *tbl,
+                                       int               cidx,
+                                       mqi_trigger_cb_t  cb_function,
+                                       void             *cb_data)
+{
+    column_trigger_t *tr, *n;
+    mdb_dlist_t *head;
+
+    MDB_CHECKARG(tbl && cidx >= 0 && cidx < tbl->ncolumn && cb_function, -1);
+
+    head = tbl->trigger.column_change + cidx;
+
+    MDB_DLIST_FOR_EACH_SAFE(column_trigger_t, link, tr,n, head) {
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            MDB_DLIST_UNLINK(column_trigger_t, link, tr);
+            free(tr);
+            return 0;
+        }
+    }
+
+    errno = ENOENT;
+    return -1;
+}
+
+int mdb_trigger_add_row_callback(mdb_table_t       *tbl,
+                                 mqi_trigger_cb_t   cb_function,
+                                 void              *cb_data,
+                                 mqi_column_desc_t *cds)
+{
+    row_trigger_t *tr;
+    size_t cdsiz;
+    int length, ncd;
+    mdb_dlist_t *head;
+
+    MDB_CHECKARG(tbl && cb_function, -1);
+
+    if (!cds)
+        ncd = length = 0;
+    else {
+        if (get_select_params(tbl, cds, &ncd, &length) < 0) {
+            errno = EINVAL;
+            return -1;
+        }
+    }
+
+    cdsiz = sizeof(mqi_column_desc_t) * ncd;
+    head  = &tbl->trigger.row_change;
+
+    MDB_DLIST_FOR_EACH(row_trigger_t, link, tr, head) {
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            if (cdsiz == tr->select.cdsiz) {
+                if (!cdsiz || memcmp(cds, tr->select.column, cdsiz))
+                    return 0; /* silently ignore multiple registrations */
+            }
+
+            errno = EEXIST;
+            return -1;
+        }
+    }
+
+    if (!(tr = calloc(1, sizeof(row_trigger_t) + cdsiz))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    MDB_DLIST_APPEND(row_trigger_t, link, tr, head);
+
+    tr->callback.function = cb_function;
+    tr->callback.user_data = cb_data;
+
+    tr->select.length = length;
+    tr->select.cdsiz = cdsiz;
+
+    if (ncd > 0)
+        memcpy(tr->select.column  , cds, cdsiz);
+
+    return 0;
+}
+
+
+int mdb_trigger_delete_row_callback(mdb_table_t      *tbl,
+                                    mqi_trigger_cb_t  cb_function,
+                                    void             *cb_data)
+{
+    row_trigger_t *tr, *n;
+
+    MDB_CHECKARG(tbl && cb_function, -1);
+
+    MDB_DLIST_FOR_EACH_SAFE(row_trigger_t,link, tr,n,&tbl->trigger.row_change){
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            MDB_DLIST_UNLINK(row_trigger_t, link, tr);
+            free(tr);
+            return 0;
+        }
+    }
+
+    errno = ENOENT;
+    return -1;
+}
+
+
+int mdb_trigger_add_table_callback(mqi_trigger_cb_t  cb_function,
+                                   void             *cb_data)
+{
+    table_trigger_t *tr;
+
+    MDB_CHECKARG(cb_function, -1);
+
+    MDB_DLIST_FOR_EACH(table_trigger_t, link, tr, &table_change_triggers) {
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            return 0; /* silently ignore multiple registrations */
+        }
+    }
+
+    if (!(tr = calloc(1, sizeof(table_trigger_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    MDB_DLIST_APPEND(table_trigger_t, link, tr, &table_change_triggers);
+
+    tr->callback.function = cb_function;
+    tr->callback.user_data = cb_data;
+
+    return 0;
+}
+
+
+int mdb_trigger_delete_table_callback(mqi_trigger_cb_t  cb_function,
+                                      void             *cb_data)
+{
+    table_trigger_t *tr, *n;
+
+    MDB_CHECKARG(cb_function, -1);
+
+    MDB_DLIST_FOR_EACH_SAFE(table_trigger_t,link, tr,n,&table_change_triggers){
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            MDB_DLIST_UNLINK(table_trigger_t,link, tr);
+            free(tr);
+            return 0;
+        }
+    }
+
+    errno = ENOENT;
+    return -1;
+}
+
+int mdb_trigger_add_transaction_callback(mqi_trigger_cb_t  cb_function,
+                                         void             *cb_data)
+{
+    transact_trigger_t *tr;
+
+    MDB_CHECKARG(cb_function, -1);
+
+    MDB_DLIST_FOR_EACH(transact_trigger_t,link, tr, &transact_change_triggers){
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            return 0; /* silently ignore multiple registrations */
+        }
+    }
+
+    if (!(tr = calloc(1, sizeof(transact_trigger_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    MDB_DLIST_APPEND(transact_trigger_t, link, tr, &transact_change_triggers);
+
+    tr->callback.function = cb_function;
+    tr->callback.user_data = cb_data;
+
+    return 0;
+}
+
+int mdb_trigger_delete_transaction_callback(mqi_trigger_cb_t  cb_function,
+                                            void             *cb_data)
+{
+    mdb_dlist_t *head = &transact_change_triggers;
+    transact_trigger_t *tr, *n;
+
+    MDB_CHECKARG(cb_function, -1);
+
+    MDB_DLIST_FOR_EACH_SAFE(transact_trigger_t, link, tr,n, head) {
+        if (cb_function == tr->callback.function &&
+            cb_data == tr->callback.user_data)
+        {
+            MDB_DLIST_UNLINK(transact_trigger_t, link, tr);
+            free(tr);
+            return 0;
+        }
+    }
+
+    errno = ENOENT;
+    return -1;
+}
+
+void mdb_trigger_column_change(mdb_table_t  *tbl,
+                               mqi_bitfld_t  colmask,
+                               mdb_row_t    *before,
+                               mdb_row_t    *after)
+{
+    mqi_event_t         evt;
+    mdb_dlist_t        *hd;
+    column_trigger_t   *tr;
+    mdb_column_t       *col;
+    mqi_column_desc_t   cd;
+    mqi_column_event_t *ce;
+    int                 cx;
+    int                 sx;
+    mqi_bitfld_t        mask, byte;
+    int                 i,j,k;
+
+    if (!tbl || !colmask || !before || !after)
+        return;
+
+    memset(&evt, 0, sizeof(evt));
+    ce = &evt.column;
+
+    ce->event = mqi_column_changed;
+
+    ce->table.handle = tbl->handle;
+    ce->table.name   = tbl->name;
+
+    ce->select.data = alloca(MDB_COLUMN_LENGTH_MAX * tbl->ncolumn);
+
+    if (!ce->select.data)
+        return;
+
+    for (mask = colmask, i = 0;     mask != 0;     mask >>= 8, i += 8) {
+        byte = mask & 0xff;
+
+        while ((j = lowest_bit_in[byte]) >= 0) {
+            byte &= ~MQI_BIT(j);
+            cx  = i + j;
+            col = tbl->columns + cx;
+            hd  = tbl->trigger.column_change + cx;
+
+            MDB_DLIST_FOR_EACH(column_trigger_t, link, tr, hd) {
+                ce->column.index = cx;
+                ce->column.name  = tbl->columns[cx].name;
+
+                ce->value.type = tbl->columns[cx].type;
+
+                cd.cindex = cx;
+                cd.offset = 0;
+
+                mdb_column_read(&cd, &ce->value.old, col, before->data);
+                mdb_column_read(&cd, &ce->value.new_, col, after->data );
+
+                if (tr->select.length > 0) {
+                    for (k = 0; (sx = tr->select.column[k].cindex) >= 0;  k++){
+                        mdb_column_read(tr->select.column + k, ce->select.data,
+                                        tbl->columns + sx, after->data);
+                    }
+                }
+
+                tr->callback.function(&evt, tr->callback.user_data);
+            }
+        }
+    }
+}
+
+
+void mdb_trigger_row_insert(mdb_table_t *tbl, mdb_row_t *row)
+{
+    if (tbl && row)
+        row_change(mqi_row_inserted, tbl, row);
+}
+
+void mdb_trigger_row_delete(mdb_table_t *tbl, mdb_row_t *row)
+{
+    if (tbl && row)
+        row_change(mqi_row_deleted, tbl, row);
+}
+
+
+void mdb_trigger_table_create(mdb_table_t *tbl)
+{
+    if (tbl)
+        table_change(mqi_table_created, tbl);
+}
+
+void mdb_trigger_table_drop(mdb_table_t *tbl)
+{
+    if (tbl)
+        table_change(mqi_table_dropped, tbl);
+}
+
+void mdb_trigger_transaction_start(uint32_t depth)
+{
+    transaction_change(mqi_transaction_start, depth);
+}
+
+void mdb_trigger_transaction_end(uint32_t depth)
+{
+    transaction_change(mqi_transaction_end, depth);
+}
+
+static int get_select_params(mdb_table_t       *tbl,
+                             mqi_column_desc_t *cds,
+                             int               *ncd_ret,
+                             int               *length_ret)
+{
+    mqi_column_desc_t *cd;
+    int ncd, length;
+    int end;
+    int cx;
+
+    *ncd_ret = *length_ret = 0;
+
+    for (ncd = length = 0;    (cx = (cd = cds + ncd)->cindex) >= 0;    ncd++) {
+        if ((end = cd->offset + tbl->columns[cx].length) > length)
+            length = end;
+    }
+
+    *ncd_ret = ncd + 1;
+    *length_ret = length;
+
+    return 0;
+}
+
+
+static void row_change(mqi_event_type_t  event,
+                       mdb_table_t      *tbl,
+                       mdb_row_t        *row)
+{
+    mqi_event_t      evt;
+    row_trigger_t   *tr;
+    mqi_row_event_t *re;
+    int              sx;
+    int              i;
+
+    memset(&evt, 0, sizeof(evt));
+    re = &evt.row;
+
+    re->event = event;
+
+    re->table.handle = tbl->handle;
+    re->table.name   = tbl->name;
+
+    re->select.data = alloca(MDB_COLUMN_LENGTH_MAX * tbl->ncolumn);
+
+    if (!re->select.data)
+        return;
+
+    MDB_DLIST_FOR_EACH(row_trigger_t, link, tr, &tbl->trigger.row_change) {
+        if (tr->select.length > 0) {
+            for (i = 0;  (sx = tr->select.column[i].cindex) >= 0;   i++)  {
+                mdb_column_read(tr->select.column + i, re->select.data,
+                                tbl->columns + sx, row->data);
+            }
+        }
+
+        tr->callback.function(&evt, tr->callback.user_data);
+    }
+}
+
+static void table_change(mqi_event_type_t event, mdb_table_t *tbl)
+{
+    mqi_event_t        evt;
+    table_trigger_t   *tr;
+    mqi_table_event_t *te;
+
+    memset(&evt, 0, sizeof(evt));
+    te = &evt.table;
+
+    te->event = event;
+
+    te->table.handle = tbl->handle;
+    te->table.name   = tbl->name;
+
+    MDB_DLIST_FOR_EACH(table_trigger_t, link, tr, &table_change_triggers) {
+        tr->callback.function(&evt, tr->callback.user_data);
+    }
+}
+
+static void transaction_change(mqi_event_type_t event, uint32_t depth)
+{
+    mqi_event_t           evt;
+    transact_trigger_t   *tr;
+    mqi_transact_event_t *te;
+
+    memset(&evt, 0, sizeof(evt));
+    te = &evt.transact;
+
+    te->event = event;
+    te->depth = depth;
+
+    MDB_DLIST_FOR_EACH(transact_trigger_t,link, tr, &transact_change_triggers){
+        tr->callback.function(&evt, tr->callback.user_data);
+    }
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mdb/trigger.h b/src/murphy-db/mdb/trigger.h
new file mode 100644 (file)
index 0000000..887d698
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MDB_TRIGGER_H__
+#define __MDB_TRIGGER_H__
+
+#include <murphy-db/mqi-types.h>
+#include <murphy-db/list.h>
+
+
+
+typedef struct {
+    mdb_dlist_t   row_change;
+    mdb_dlist_t   column_change[0];
+} mdb_trigger_t;
+
+void mdb_trigger_init(mdb_trigger_t *, int);
+void mdb_trigger_reset(mdb_trigger_t *, int);
+
+void mdb_trigger_column_change(mdb_table_t*, mqi_bitfld_t,
+                               mdb_row_t *, mdb_row_t *);
+
+void mdb_trigger_row_delete(mdb_table_t *, mdb_row_t *);
+void mdb_trigger_row_insert(mdb_table_t *, mdb_row_t *);
+
+void mdb_trigger_table_create(mdb_table_t *);
+void mdb_trigger_table_drop(mdb_table_t *);
+
+void mdb_trigger_transaction_start(uint32_t);
+void mdb_trigger_transaction_end(uint32_t);
+
+#endif /* __MDB_TRIGGER_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/Makefile.am b/src/murphy-db/mqi/Makefile.am
new file mode 100644 (file)
index 0000000..99e848b
--- /dev/null
@@ -0,0 +1,34 @@
+pkglib_LTLIBRARIES = libmqi.la
+
+LINKER_SCRIPT = linker-script.mqi
+QUIET_GEN     = $(Q:@=@echo '  GEN   '$@;)
+
+libmqi_la_CFLAGS = -I../include
+
+libmqi_ladir     = \
+               $(includedir)/murphy-db
+
+libmqi_la_HEADERS = \
+               ../include/murphy-db/mqi.h
+
+libmqi_la_SOURCES = \
+               $(libmqi_ls_HEADERS) \
+               mqi.c db.h mdb-backend.h mdb-backend.c
+
+libmqi_la_LDFLAGS =            \
+               -Wl,-version-script=$(LINKER_SCRIPT)
+#              -version-info @MURPHYDB_VERSION_INFO@
+
+libmqi_la_DEPENDENCIES = $(LINKER_SCRIPT)
+
+# linker script generation
+$(LINKER_SCRIPT): $(libmqi_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+           -P "$(CC)" -c "$(libmqi_la_CFLAGS)" -p "^mqi_" -o $@ $^
+
+clean-$(LINKER_SCRIPT):
+       -rm -f $(LINKER_SCRIPT)
+
+# cleanup
+clean-local:: # clean-$(LINKER_SCRIPT)
+       rm -f *~
diff --git a/src/murphy-db/mqi/db.h b/src/murphy-db/mqi/db.h
new file mode 100644 (file)
index 0000000..f5ff656
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQI_DB_H__
+#define __MQI_DB_H__
+
+typedef struct {
+    int (*create_transaction_trigger)(mqi_trigger_cb_t, void *);
+    int (*create_table_trigger)(mqi_trigger_cb_t, void *);
+    int (*create_row_trigger)(void *, mqi_trigger_cb_t, void *,
+                              mqi_column_desc_t *);
+    int (*create_column_trigger)(void *, int, mqi_trigger_cb_t, void *,
+                                 mqi_column_desc_t *);
+    int (*drop_transaction_trigger)(mqi_trigger_cb_t, void *);
+    int (*drop_table_trigger)(mqi_trigger_cb_t, void *);
+    int (*drop_row_trigger)(void *, mqi_trigger_cb_t, void *);
+    int (*drop_column_trigger)(void *, int, mqi_trigger_cb_t, void *);
+    uint32_t (*begin_transaction)(void);
+    int (*commit_transaction)(uint32_t);
+    int (*rollback_transaction)(uint32_t);
+    uint32_t (*get_transaction_id)(void);
+    void *(*create_table)(char *, char **, mqi_column_def_t *);
+    int (*register_table_handle)(void *, mqi_handle_t);
+    int (*create_index)(void *, char **);
+    int (*drop_table)(void *);
+    int (*describe)(void *, mqi_column_def_t *, int);
+    int (*insert_into)(void *, int, mqi_column_desc_t *, void **);
+    int (*select)(void *, mqi_cond_entry_t *, mqi_column_desc_t *,
+                  void *, int, int);
+    int (*select_by_index)(void *, mqi_variable_t *,
+                           mqi_column_desc_t *, void *);
+    int (*update)(void *, mqi_cond_entry_t *, mqi_column_desc_t *,void*);
+    int (*delete_from)(void *, mqi_cond_entry_t *);
+    void *(*find_table)(char *);
+    int (*get_column_index)(void *, char *);
+    int (*get_table_size)(void *);
+    uint32_t (*get_table_stamp)(void *);
+    char *(*get_column_name)(void *, int);
+    mqi_data_type_t (*get_column_type)(void *, int);
+    int (*get_column_size)(void *, int);
+    int (*print_rows)(void *, char *, int);
+} mqi_db_functbl_t;
+
+
+
+#endif /* __MQI_DB_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/mdb-backend.c b/src/murphy-db/mqi/mdb-backend.c
new file mode 100644 (file)
index 0000000..9fc0ea6
--- /dev/null
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/mdb.h>
+
+#include "mdb-backend.h"
+
+
+static int      create_transaction_trigger(mqi_trigger_cb_t, void *);
+static int      create_table_trigger(mqi_trigger_cb_t, void *);
+static int      create_row_trigger(void *, mqi_trigger_cb_t, void *,
+                                   mqi_column_desc_t *);
+static int      create_column_trigger(void *, int, mqi_trigger_cb_t, void *,
+                                      mqi_column_desc_t *);
+static int      drop_transaction_trigger(mqi_trigger_cb_t, void *);
+static int      drop_table_trigger(mqi_trigger_cb_t, void *);
+static int      drop_row_trigger(void *, mqi_trigger_cb_t, void *);
+static int      drop_column_trigger(void*, int, mqi_trigger_cb_t, void *);
+static uint32_t begin_transaction(void);
+static int      commit_transaction(uint32_t);
+static int      rollback_transaction(uint32_t);
+static uint32_t get_transaction_id(void);
+static void *   create_table(char *, char **, mqi_column_def_t *);
+static int      register_table_handle(void *, mqi_handle_t);
+static int      create_index(void *, char **);
+static int      drop_table(void *);
+static int      describe(void *, mqi_column_def_t *, int);
+static int      insert_into(void *, int, mqi_column_desc_t *, void **);
+static int      select_general(void *, mqi_cond_entry_t *, mqi_column_desc_t *,
+                               void *, int, int);
+static int      select_by_index(void *, mqi_variable_t *, mqi_column_desc_t *,
+                                 void *);
+static int      update(void *, mqi_cond_entry_t *, mqi_column_desc_t*,void*);
+static int      delete_from(void *, mqi_cond_entry_t *);
+static void *   find_table(char *);
+static int      get_column_index(void *, char *);
+static int      get_table_size(void *);
+static uint32_t get_table_stamp(void *);
+static char *   get_column_name(void *, int);
+static mqi_data_type_t get_column_type(void *, int);
+static int      get_column_size(void *, int);
+static int      print_rows(void *, char *, int);
+
+static mqi_db_functbl_t functbl = {
+    create_transaction_trigger,
+    create_table_trigger,
+    create_row_trigger,
+    create_column_trigger,
+    drop_transaction_trigger,
+    drop_table_trigger,
+    drop_row_trigger,
+    drop_column_trigger,
+    begin_transaction,
+    commit_transaction,
+    rollback_transaction,
+    get_transaction_id,
+    create_table,
+    register_table_handle,
+    create_index,
+    drop_table,
+    describe,
+    insert_into,
+    select_general,
+    select_by_index,
+    update,
+    delete_from,
+    find_table,
+    get_column_index,
+    get_table_size,
+    get_table_stamp,
+    get_column_name,
+    get_column_type,
+    get_column_size,
+    print_rows
+};
+
+
+mqi_db_functbl_t *mdb_backend_init(void)
+{
+    return &functbl;
+}
+
+
+static int create_transaction_trigger(mqi_trigger_cb_t cb, void *data)
+{
+    return mdb_trigger_add_transaction_callback(cb, data);
+}
+
+static int create_table_trigger(mqi_trigger_cb_t cb, void *data)
+{
+    return mdb_trigger_add_table_callback(cb, data);
+}
+
+static int create_row_trigger(void *t,
+                              mqi_trigger_cb_t cb,
+                              void *data,
+                              mqi_column_desc_t *cds)
+{
+    return mdb_trigger_add_row_callback((mdb_table_t *)t, cb, data, cds);
+}
+
+static int create_column_trigger(void *t,
+                                 int colidx,
+                                 mqi_trigger_cb_t cb,
+                                 void *data,
+                                 mqi_column_desc_t *cds)
+{
+    return mdb_trigger_add_column_callback((mdb_table_t *)t, colidx,
+                                         cb, data, cds);
+}
+
+static int drop_transaction_trigger(mqi_trigger_cb_t cb, void *data)
+{
+    return mdb_trigger_delete_transaction_callback(cb, data);
+}
+
+static int drop_table_trigger(mqi_trigger_cb_t cb, void *data)
+{
+    return mdb_trigger_delete_table_callback(cb, data);
+}
+
+static int drop_row_trigger(void *t, mqi_trigger_cb_t cb, void *data)
+{
+    return mdb_trigger_delete_row_callback((mdb_table_t *)t, cb, data);
+}
+
+static int drop_column_trigger(void *t,
+                               int colidx,
+                               mqi_trigger_cb_t cb,
+                               void *data)
+{
+    return mdb_trigger_delete_column_callback((mdb_table_t *)t,colidx,cb,data);
+}
+
+static uint32_t begin_transaction(void)
+{
+    uint32_t depth = mdb_transaction_begin();
+
+    if (!depth)
+        return MDB_HANDLE_INVALID;
+
+    return depth;
+}
+
+static int commit_transaction(uint32_t depth)
+{
+    return mdb_transaction_commit(depth);
+}
+
+static int rollback_transaction(uint32_t depth)
+{
+    return mdb_transaction_rollback(depth);
+}
+
+static uint32_t get_transaction_id(void)
+{
+    return mdb_transaction_get_depth();
+}
+
+static void *create_table(char *name,
+                          char **index_columns,
+                          mqi_column_def_t *cdefs)
+{
+    return mdb_table_create(name, index_columns, cdefs);
+}
+
+static int register_table_handle(void *t, mqi_handle_t handle)
+{
+    return mdb_table_register_handle((mdb_table_t *)t, handle);
+}
+
+
+
+static int create_index(void *t, char **index_columns)
+{
+    return mdb_table_create_index((mdb_table_t *)t, index_columns);
+}
+
+static int drop_table(void *t)
+{
+    return mdb_table_drop((mdb_table_t *)t);
+}
+
+static int describe(void *t, mqi_column_def_t *defs, int len)
+{
+    return mdb_table_describe((mdb_table_t *)t, defs, len);
+}
+
+static int insert_into(void               *t,
+                        int                 ignore,
+                        mqi_column_desc_t  *cds,
+                        void              **data)
+{
+    return mdb_table_insert((mdb_table_t *)t, ignore, cds, data);
+}
+
+static int select_general(void              *t,
+                          mqi_cond_entry_t  *cond,
+                          mqi_column_desc_t *cds,
+                          void              *results,
+                          int                size,
+                          int                dim)
+{
+    return mdb_table_select((mdb_table_t *)t, cond, cds, results, size, dim);
+}
+
+static int select_by_index(void              *t,
+                            mqi_variable_t    *idxvars,
+                            mqi_column_desc_t *cds,
+                            void              *result)
+{
+    return mdb_table_select_by_index((mdb_table_t *)t, idxvars, cds, result);
+}
+
+
+static int update(void              *t,
+                  mqi_cond_entry_t  *cond,
+                  mqi_column_desc_t *cds,
+                  void              *data)
+{
+    return mdb_table_update((mdb_table_t *)t, cond, cds, data);
+}
+
+static int delete_from(void *t, mqi_cond_entry_t *cond)
+{
+    return mdb_table_delete((mdb_table_t *)t, cond);
+}
+
+
+static void *find_table(char *table_name)
+{
+    return mdb_table_find(table_name);
+}
+
+
+static int get_column_index(void *t, char *column_name)
+{
+    return mdb_table_get_column_index((mdb_table_t *)t, column_name);
+}
+
+static int get_table_size(void *t)
+{
+    return  mdb_table_get_size((mdb_table_t *)t);
+}
+
+static uint32_t get_table_stamp(void *t)
+{
+    return mdb_table_get_stamp((mdb_table_t *)t);
+}
+
+static char *get_column_name(void *t, int colidx)
+{
+    return  mdb_table_get_column_name((mdb_table_t *)t, colidx);
+}
+
+static mqi_data_type_t get_column_type(void *t, int colidx)
+{
+    return  mdb_table_get_column_type((mdb_table_t *)t, colidx);
+}
+
+static int get_column_size(void *t, int colidx)
+{
+    return  mdb_table_get_column_size((mdb_table_t *)t, colidx);
+}
+
+static int print_rows(void *t, char *buf, int len)
+{
+    return mdb_table_print_rows((mdb_table_t *)t, buf, len);
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/mdb-backend.h b/src/murphy-db/mqi/mdb-backend.h
new file mode 100644 (file)
index 0000000..e16db3b
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MQI_MDB_BACKEND_H__
+#define __MQI_MDB_BACKEND_H__
+
+#include "db.h"
+
+mqi_db_functbl_t *mdb_backend_init(void);
+
+
+#endif  /* __MQI_MDB_BACKEND_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mqi/mqi.c b/src/murphy-db/mqi/mqi.c
new file mode 100644 (file)
index 0000000..34cb75c
--- /dev/null
@@ -0,0 +1,833 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <string.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/mqi.h>
+#include <murphy-db/handle.h>
+#include <murphy-db/hash.h>
+#include "mdb-backend.h"
+
+#define MAX_DB 2
+
+#define TX_DEPTH_BITS  4
+#define TX_USEID_BITS  ((sizeof(mqi_handle_t) * 8) - TX_DEPTH_BITS)
+#define TX_DEPTH_MAX   (((mqi_handle_t)1) << TX_DEPTH_BITS)
+#define TX_USEID_MAX   (((mqi_handle_t)1) << TX_USEID_BITS)
+#define TX_DEPTH_MASK  (TX_DEPTH_MAX - 1)
+#define TX_USEID_MASK  (TX_USEID_MAX - 1)
+
+#define TX_DEPTH(h)    ((h) & TX_DEPTH_MASK)
+#define TX_USEID(h)    ((h) & (TX_USEID_MASK << TX_DEPTH_BITS))
+
+#define TX_HANDLE(useid, depth)                                         \
+    (((useid) & (TX_USEID_MASK << TX_DEPTH_BITS)) | ((depth) & TX_DEPTH_MASK))
+
+#define TX_USEID_INCREMENT(u)                                   \
+    (u) = ((((u) + ((mqi_handle_t)1)) << TX_DEPTH_BITS) &       \
+           (TX_USEID_MASK << TX_DEPTH_BITS))
+
+
+#if MQI_TXDEPTH_MAX > (1 << TX_DEPTH_BITS)
+#error "Too few TX_DEPTH_BITS to represent MQI_TXDEPTH_MAX"
+#endif
+
+#define DB_TYPE(db) ((db)->flags & MQI_TABLE_TYPE_MASK)
+
+#define GET_TABLE(tbl, ftb, h, errval)                                      \
+    do {                                                                    \
+        mqi_table_t *t;                                                     \
+        mqi_db_t *db;                                                       \
+        if (!(t = mdb_handle_get_data(table_handle, h)) || !(db = t->db)) { \
+            errno = ENOENT;                                                 \
+            return errval;                                                  \
+        }                                                                   \
+        if (!(tbl = t->handle) || !(ftb = db->functbl)) {                   \
+            errno = EIO;                                                    \
+            return errval;                                                  \
+        }                                                                   \
+    } while(0)
+
+typedef struct {
+    const char       *engine;
+    uint32_t          flags;
+    mqi_db_functbl_t *functbl;
+} mqi_db_t;
+
+typedef struct {
+    mqi_db_t    *db;
+    void        *handle;
+} mqi_table_t;
+
+typedef struct {
+    uint32_t useid;
+    uint32_t txid[MAX_DB];
+} mqi_transaction_t;
+
+
+static int db_register(const char *, uint32_t, mqi_db_functbl_t *);
+
+
+static int        ndb;
+static mqi_db_t   *dbs;
+mdb_handle_map_t  *table_handle;
+mdb_hash_t        *table_name_hash;
+mdb_handle_map_t  *transact_handle;
+mqi_transaction_t  txstack[MQI_TXDEPTH_MAX];
+int                txdepth;
+
+
+int mqi_open(void)
+{
+    if (!ndb && !dbs) {
+        if (!(dbs = calloc(MAX_DB, sizeof(mqi_db_t)))) {
+            errno = ENOMEM;
+            return -1;
+        }
+
+        table_handle = MDB_HANDLE_MAP_CREATE();
+        table_name_hash = MDB_HASH_TABLE_CREATE(varchar, 256);
+
+        transact_handle = MDB_HANDLE_MAP_CREATE();
+
+        if (db_register("MurphyDB", MQI_TEMPORARY, mdb_backend_init()) < 0) {
+            errno = EIO;
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+int mqi_close(void)
+{
+    int i;
+
+    if (ndb > 0 && dbs) {
+        for (i = 0; i < ndb; i++)
+            free((void *)dbs[i].engine);
+
+        free(dbs);
+
+        MDB_HANDLE_MAP_DESTROY(table_handle);
+        MDB_HASH_TABLE_DESTROY(table_name_hash);
+        MDB_HANDLE_MAP_DESTROY(transact_handle);
+
+        table_handle = NULL;
+        table_name_hash = NULL;
+        transact_handle = NULL;
+
+        dbs = NULL;
+        ndb = 0;
+    }
+
+    return 0;
+}
+
+
+int mqi_show_tables(uint32_t flags, char **buf, int len)
+{
+    mqi_handle_t h;
+    mqi_table_t *tbl;
+    mqi_db_t *db;
+    void *data;
+    char *name;
+    void *cursor;
+    int   i = 0;
+    int   j;
+
+    MDB_CHECKARG(buf && len > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    MDB_HASH_TABLE_FOR_EACH_WITH_KEY(table_name_hash, data, name, cursor) {
+        if (i >= len) {
+            errno = EOVERFLOW;
+            return -1;
+        }
+
+        if ((h = data - NULL) == MQI_HANDLE_INVALID)
+            continue;
+
+        if (!(tbl = mdb_handle_get_data(table_handle, h)) || !(db = tbl->db))
+            continue;
+
+        if (!(DB_TYPE(db) & flags))
+            continue;
+
+        for (j = 0; j < i;  j++) {
+            if (strcasecmp(name, buf[j]) < 0) {
+                memmove(buf + (j+1), buf + j, sizeof(char *) * (i-j));
+                break;
+            }
+        }
+        buf[j] = name;
+
+        i++;
+    }
+
+    return i;
+}
+
+
+int mqi_create_transaction_trigger(mqi_trigger_cb_t callback, void *user_data)
+{
+    mqi_db_t         *db;
+    mqi_db_functbl_t *ftb;
+    int               i;
+
+    MDB_CHECKARG(callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    for (i = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->create_transaction_trigger(callback, user_data) < 0) {
+
+            for (i--;  i >= 0;  i--) {
+                db  = dbs + i;
+                ftb = db->functbl;
+
+                ftb->drop_transaction_trigger(callback, user_data);
+            }
+
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+int mqi_create_table_trigger(mqi_trigger_cb_t callback, void *user_data)
+{
+    mqi_db_t         *db;
+    mqi_db_functbl_t *ftb;
+    int               i;
+
+    MDB_CHECKARG(callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    for (i = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->create_table_trigger(callback, user_data) < 0) {
+
+            for (i--;  i >= 0;  i--) {
+                db  = dbs + i;
+                ftb = db->functbl;
+
+                ftb->drop_table_trigger(callback, user_data);
+            }
+
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+int mqi_create_row_trigger(mqi_handle_t h,
+                           mqi_trigger_cb_t callback,
+                           void *user_data,
+                           mqi_column_desc_t *cds)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->create_row_trigger(tbl, callback, user_data, cds);
+}
+
+
+int mqi_create_column_trigger(mqi_handle_t h,
+                              int colidx,
+                              mqi_trigger_cb_t callback,
+                              void *user_data,
+                              mqi_column_desc_t *cds)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->create_column_trigger(tbl, colidx, callback, user_data, cds);
+}
+
+
+int mqi_drop_transaction_trigger(mqi_trigger_cb_t callback, void *user_data)
+{
+    mqi_db_t         *db;
+    mqi_db_functbl_t *ftb;
+    int               sts;
+    int               i;
+
+    MDB_CHECKARG(callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    for (sts = 0, i = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->drop_transaction_trigger(callback, user_data) < 0)
+            sts = -1;
+    }
+
+    return sts;
+}
+
+
+int mqi_drop_table_trigger(mqi_trigger_cb_t callback, void *user_data)
+{
+    mqi_db_t         *db;
+    mqi_db_functbl_t *ftb;
+    int               sts;
+    int               i;
+
+    MDB_CHECKARG(callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    for (sts = 0, i = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->drop_table_trigger(callback, user_data) < 0)
+            sts = -1;
+    }
+
+    return sts;
+}
+
+
+int mqi_drop_row_trigger(mqi_handle_t h,
+                         mqi_trigger_cb_t callback,
+                         void *user_data)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->drop_row_trigger(tbl, callback, user_data);
+}
+
+
+int mqi_drop_column_trigger(mqi_handle_t h,
+                            int colidx,
+                            mqi_trigger_cb_t callback,
+                            void *user_data)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && callback, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->drop_column_trigger(tbl, colidx, callback, user_data);
+}
+
+
+mqi_handle_t mqi_begin_transaction(void)
+{
+    mqi_transaction_t *tx;
+    mqi_db_t          *db;
+    mqi_db_functbl_t  *ftb;
+    uint32_t           depth;
+    int                i;
+
+    MDB_PREREQUISITE(dbs && ndb > 0 && transact_handle, MQI_HANDLE_INVALID);
+    MDB_ASSERT(txdepth < MQI_TXDEPTH_MAX - 1, EOVERFLOW, MQI_HANDLE_INVALID);
+
+    depth = txdepth++;
+    tx = txstack + depth;
+
+    TX_USEID_INCREMENT(tx->useid);
+
+    for (i = 0; i < ndb; i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+        tx->txid[i] = ftb->begin_transaction();
+    }
+
+    return TX_HANDLE(tx->useid, depth);
+}
+
+
+int mqi_commit_transaction(mqi_handle_t h)
+{
+    uint32_t           depth = TX_DEPTH(h);
+    uint32_t           useid = TX_USEID(h);
+    mqi_transaction_t *tx;
+    mqi_db_t          *db;
+    mqi_db_functbl_t  *ftb;
+    int                err;
+    int                i;
+
+    MDB_CHECKARG(h != MQI_HANDLE_INVALID && depth < MQI_TXDEPTH_MAX, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+    MDB_ASSERT(txdepth > 0 && depth == (uint32_t)txdepth - 1, EBADSLT, -1);
+
+    tx = txstack + depth;
+
+    MDB_ASSERT(tx->useid == useid, EBADSLT, -1);
+
+    for (i = 0, err = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->commit_transaction(tx->txid[i]) < 0)
+            err = -1;
+    }
+
+    txdepth--;
+
+    return err;
+}
+
+int mqi_rollback_transaction(mqi_handle_t h)
+{
+    uint32_t           depth = TX_DEPTH(h);
+    uint32_t           useid = TX_USEID(h);
+    mqi_transaction_t *tx;
+    mqi_db_t          *db;
+    mqi_db_functbl_t  *ftb;
+    int                err;
+    int                i;
+
+    MDB_CHECKARG(h != MQI_HANDLE_INVALID && depth < MQI_TXDEPTH_MAX, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+    MDB_ASSERT(txdepth > 0 && depth == (uint32_t)txdepth - 1, EBADSLT, -1);
+
+    tx = txstack + depth;
+
+    MDB_ASSERT(tx->useid == useid, EBADSLT, -1);
+
+    for (i = 0, err = 0;  i < ndb;  i++) {
+        db  = dbs + i;
+        ftb = db->functbl;
+
+        if (ftb->rollback_transaction(tx->txid[i]) < 0)
+            err = -1;
+    }
+
+    txdepth--;
+
+    return err;
+}
+
+mqi_handle_t mqi_get_transaction_handle(void)
+{
+    uint32_t           depth;
+    mqi_transaction_t *tx;
+
+    MDB_CHECKARG(txdepth > 0, MQI_HANDLE_INVALID);
+    MDB_PREREQUISITE(dbs && ndb > 0, MQI_HANDLE_INVALID);
+
+    depth = txdepth - 1;
+    tx = txstack + depth;
+
+    return TX_HANDLE(tx->useid, depth);
+}
+
+
+uint32_t mqi_get_transaction_depth(void)
+{
+    return txdepth;
+}
+
+
+mqi_handle_t mqi_create_table(char *name,
+                              uint32_t flags,
+                              char **index_columns,
+                              mqi_column_def_t *cdefs)
+{
+    mqi_db_t         *db;
+    mqi_db_functbl_t *ftb;
+    mqi_table_t      *tbl = NULL;
+    mqi_handle_t      h = MQI_HANDLE_INVALID;
+    char             *namedup = NULL;
+    int               i;
+
+    MDB_CHECKARG(name && cdefs, MQI_HANDLE_INVALID);
+    MDB_PREREQUISITE(dbs && ndb > 0, MQI_HANDLE_INVALID);
+
+    for (i = 0, ftb = NULL;  i < ndb;  i++) {
+        db = dbs + i;
+
+        if ((DB_TYPE(db) & flags) != 0) {
+            ftb = db->functbl;
+            break;
+        }
+    }
+
+    MDB_ASSERT(ftb, ENOENT, MQI_HANDLE_INVALID);
+
+    if(!(tbl = calloc(1, sizeof(mqi_table_t))))
+        return MQI_HANDLE_INVALID;
+
+    tbl->db = db;
+    tbl->handle = NULL;
+
+    if (!(namedup = strdup(name)))
+        goto cleanup;
+
+    if (!(tbl->handle = ftb->create_table(name, index_columns, cdefs)))
+        goto cleanup;
+
+    if ((h = mdb_handle_add(table_handle, tbl)) == MQI_HANDLE_INVALID)
+        goto cleanup;
+
+    if (mdb_hash_add(table_name_hash, 0,namedup, NULL + h) < 0) {
+        mdb_handle_delete(table_handle, h);
+        h = MQI_HANDLE_INVALID;
+    }
+
+    ftb->register_table_handle(tbl->handle, h);
+
+    return h;
+
+ cleanup:
+    if (tbl) {
+        if (tbl->handle) {
+            mdb_handle_delete(table_handle, h);
+            ftb->drop_table(tbl->handle);
+        }
+        mdb_hash_delete(table_name_hash, 0,name);
+        free(namedup);
+        free(tbl);
+    }
+
+    return MDB_HANDLE_INVALID;
+}
+
+
+int mqi_create_index(mqi_handle_t h, char **index_columns)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && index_columns, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->create_index(tbl, index_columns);
+}
+
+int mqi_drop_table(mqi_handle_t h)
+{
+    mqi_table_t      *tbl;
+    mqi_db_functbl_t *ftb;
+    char             *name;
+    void             *data;
+    void             *cursor;
+    int               sts;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    if (!(tbl = mdb_handle_delete(table_handle, h)))
+        return -1;
+
+    ftb = tbl->db->functbl;
+
+    MDB_HASH_TABLE_FOR_EACH_WITH_KEY_SAFE(table_name_hash, data,name, cursor) {
+        if ((mqi_handle_t)(data - NULL) == h) {
+            mdb_hash_delete(table_name_hash, 0,name);
+            sts = ftb->drop_table(tbl->handle);
+            free(name);
+            free(tbl);
+            return sts;
+        }
+    }
+
+    return -1;
+}
+
+int mqi_describe(mqi_handle_t h, mqi_column_def_t *defs, int len)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && defs && len > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->describe(tbl, defs, len);
+}
+
+int mqi_insert_into(mqi_handle_t         h,
+                    int                 ignore,
+                    mqi_column_desc_t  *cds,
+                    void              **data)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && cds && data && data[0], -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->insert_into(tbl, ignore, cds, data);
+}
+
+int mqi_select(mqi_handle_t       h,
+               mqi_cond_entry_t  *cond,
+               mqi_column_desc_t *cds,
+               void              *rows,
+               int                rowsize,
+               int                dim)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && cds &&
+                 rows && rowsize > 0 && dim > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->select(tbl, cond, cds, rows, rowsize, dim);
+}
+
+int mqi_select_by_index(mqi_handle_t       h,
+                        mqi_variable_t    *idxvars,
+                        mqi_column_desc_t *cds,
+                        void              *result)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && idxvars && cds && result, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->select_by_index(tbl, idxvars, cds, result);
+}
+
+int mqi_update(mqi_handle_t       h,
+               mqi_cond_entry_t  *cond,
+               mqi_column_desc_t *cds,
+               void              *data)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && cds && data, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->update(tbl, cond, cds, data);
+}
+
+int mqi_delete_from(mqi_handle_t h, mqi_cond_entry_t *cond)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->delete_from(tbl, cond);
+}
+
+mqi_handle_t mqi_get_table_handle(char *table_name)
+{
+    void *data;
+
+    MDB_CHECKARG(table_name, MQI_HANDLE_INVALID);
+    MDB_PREREQUISITE(dbs && ndb > 0, MQI_HANDLE_INVALID);
+
+    data = mdb_hash_get_data(table_name_hash, 0,table_name);
+
+    if (data != NULL)
+        return data - NULL;
+    else
+        return MQI_HANDLE_INVALID;
+}
+
+
+int mqi_get_column_index(mqi_handle_t h, char *column_name)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && column_name, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->get_column_index(tbl, column_name);
+}
+
+int mqi_get_table_size(mqi_handle_t h)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_table_size(tbl);
+}
+
+uint32_t mqi_get_table_stamp(mqi_handle_t h)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID, MQI_STAMP_NONE);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_table_stamp(tbl);
+}
+
+char *mqi_get_column_name(mqi_handle_t h, int colidx)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && colidx >= 0, NULL);
+    MDB_PREREQUISITE(dbs && ndb > 0, NULL);
+
+    GET_TABLE(tbl, ftb, h, NULL);
+
+    return  ftb->get_column_name(tbl, colidx);
+}
+
+mqi_data_type_t mqi_get_column_type(mqi_handle_t h, int colidx)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && colidx >= 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_column_type(tbl, colidx);
+}
+
+int mqi_get_column_size(mqi_handle_t h, int colidx)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && colidx >= 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return  ftb->get_column_size(tbl, colidx);
+}
+
+int mqi_print_rows(mqi_handle_t h, char *buf, int len)
+{
+    mqi_db_functbl_t *ftb;
+    void             *tbl;
+
+    MDB_CHECKARG(h != MDB_HANDLE_INVALID && buf && len > 0, -1);
+    MDB_PREREQUISITE(dbs && ndb > 0, -1);
+
+    GET_TABLE(tbl, ftb, h, -1);
+
+    return ftb->print_rows(tbl, buf, len);
+}
+
+
+
+static int db_register(const char       *engine,
+                       uint32_t          flags,
+                       mqi_db_functbl_t *functbl)
+{
+    mqi_db_t *db;
+    int i;
+
+    MDB_CHECKARG(engine && engine[0] && functbl, -1);
+    MDB_PREREQUISITE(dbs, -1);
+
+    if (ndb + 1 >= MAX_DB) {
+        errno = EOVERFLOW;
+        return -1;
+    }
+
+    for (i = 0; i < ndb;  i++) {
+        if (!strcmp(engine, dbs[i].engine)) {
+            errno = EEXIST;
+            return -1;
+        }
+    }
+
+    db = dbs + ndb++;
+
+    db->engine  = strdup(engine);
+    db->flags   = flags;
+    db->functbl = functbl;
+
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mql/Makefile.am b/src/murphy-db/mql/Makefile.am
new file mode 100644 (file)
index 0000000..506ea23
--- /dev/null
@@ -0,0 +1,61 @@
+pkglib_LTLIBRARIES = libmql.la
+
+PARSER_PREFIX   = yy_mql_
+AM_YFLAGS       = -p $(PARSER_PREFIX)
+LEX_OUTPUT_ROOT = ./lex.$(PARSER_PREFIX)
+BUILT_SOURCES   = mql-scanner.c mql-parser.c
+
+LINKER_SCRIPT = linker-script.mql
+QUIET_GEN     = $(Q:@=@echo '  GEN   '$@;)
+
+libmql_la_CFLAGS = -I../include
+
+libmql_ladir     = \
+               $(includedir)/murphy-db
+
+libmql_la_HEADERS = \
+               ../include/murphy-db/mql.h \
+               ../include/murphy-db/mql-statement.h \
+               ../include/murphy-db/mql-result.h \
+               ../include/murphy-db/mql-trigger.h
+
+libmql_la_SOURCES = \
+               $(libmql_la_HEADERS) \
+               mql-scanner.l mql-parser.y \
+               statement.c result.c trigger.c transaction.c
+
+libmql_la_LDFLAGS =            \
+               -Wl,-version-script=$(LINKER_SCRIPT)
+#              -version-info @MURPHYDB_VERSION_INFO@
+
+libmql_la_LIBADD = ../mqi/libmqi.la ../mdb/libmdb.la
+
+libmql_la_DEPENDENCIES = $(LINKER_SCRIPT)
+
+
+mql-parser.h mql-parser.c: mql-parser.y
+       $(YACCCOMPILE) $<
+       mv -f y.tab.h mql-parser.h
+       mv -f y.tab.c mql-parser.c
+
+mql-scanner.c: mql-scanner.l mql-parser.c
+       $(LEXCOMPILE) $<
+       mv lex.$(PARSER_PREFIX).c $@
+
+clean-parser:
+       -rm -f mql-parser.[hc] *.tab.[hc]
+
+clean-scanner:
+       -rm -f mql-scanner.c
+
+# linker script generation
+$(LINKER_SCRIPT): $(libmql_la_HEADERS)
+       $(QUIET_GEN)$(top_builddir)/build-aux/gen-linker-script -q \
+           -P "$(CC)" -c "$(libmql_la_CFLAGS)" -p "^mql_" -o $@ $^
+
+clean-$(LINKER_SCRIPT):
+       -rm -f $(LINKER_SCRIPT)
+
+# cleanup
+clean-local:: clean-parser clean-scanner # clean-$(LINKER_SCRIPT)
+       rm -f *~
diff --git a/src/murphy-db/mql/mql-parser.y b/src/murphy-db/mql/mql-parser.y
new file mode 100644 (file)
index 0000000..e598fd5
--- /dev/null
@@ -0,0 +1,1564 @@
+%{
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <alloca.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+
+#define MQL_SUCCESS                                                     \
+    do {                                                                \
+        if (mode == mql_mode_exec)                                      \
+            result = mql_result_success_create();                       \
+    } while (0)
+
+#define MQL_ERROR(code, fmt...)                                         \
+    do {                                                                \
+        switch (mode) {                                                 \
+        case mql_mode_exec:                                             \
+            result = mql_result_error_create(code, fmt);                \
+            break;                                                      \
+        case mql_mode_precompile:                                       \
+            errno = code;                                               \
+            free(statement);                                            \
+            statement = NULL;                                           \
+            break;                                                      \
+        case mql_mode_parser:                                           \
+            fprintf(mqlout, "%s:%d: error: ", file, yy_mql_lineno);     \
+            fprintf(mqlout, fmt);                                       \
+            fprintf(mqlout, "\n");                                      \
+            break;                                                      \
+        }                                                               \
+        YYERROR;                                                        \
+    } while (0)
+
+
+#define SET_INPUT(t,v)                                                  \
+    input_t *input;                                                     \
+    if (ninput >= MQI_COLUMN_MAX)                                       \
+        MQL_ERROR(EOVERFLOW, "Too many input values\n");                \
+    input = inputs + ninput++;                                          \
+    input->type = mqi_##t;                                              \
+    input->flags = 0;                                                   \
+    input->value.t = (v)
+
+typedef enum mql_mode_e        mql_mode_t;
+typedef struct input_s         input_t;
+
+enum mql_mode_e {
+    mql_mode_parser,
+    mql_mode_exec,
+    mql_mode_precompile,
+};
+
+struct input_s {
+    mqi_data_type_t      type;
+    uint32_t             flags;
+    union {
+        char *varchar;
+        int32_t integer;
+        uint32_t unsignd;
+        double floating;
+    }                    value;
+};
+
+extern int yy_mql_lineno;
+extern int yy_mql_lex(void);
+
+
+void yy_mql_error(const char *);
+
+static int set_select_variables(int *, mqi_data_type_t *, int *, char *,int);
+static void print_query_result(mqi_column_desc_t *, mqi_data_type_t *,
+                               int *, int, int, void *);
+
+static mqi_handle_t table;
+static uint32_t     table_flags;
+
+static char                  *trigger_name;
+static struct mql_callback_s *callback;
+
+static mqi_column_def_t   coldefs[MQI_COLUMN_MAX + 1];
+static mqi_column_def_t  *coldef = coldefs;
+
+static char              *colnams[MQI_COLUMN_MAX + 1];
+static int                ncolnam;
+
+static mqi_cond_entry_t   conds[MQI_COND_MAX + 1];
+static mqi_cond_entry_t  *cond = conds;
+static int                binds;
+
+static input_t            inputs[MQI_COLUMN_MAX];
+static int                ninput;
+
+static mqi_column_desc_t  coldescs[MQI_COLUMN_MAX + 1];
+static int                ncoldesc;
+
+static char    *strs[256];
+static int      nstr;
+
+static int32_t  ints[256];
+static int      nint;
+
+static uint32_t uints[32];
+static int      nuint;
+
+static double   floats[256];
+static int      nfloat;
+
+static mql_mode_t mode;
+
+static mql_statement_t   *statement;
+
+static mql_result_type_t  rtype;
+static mql_result_t      *result;
+
+static char        *file;
+static const char  *mqlbuf;
+static int          mqlin;
+static FILE        *mqlout;
+
+%}
+
+%union {
+    mqi_data_type_t  type;
+    char            *string;
+    long long int    number;
+    double           floating;
+    int              integer;
+    bool             boolean;
+};
+
+
+%defines
+
+%token <string>   TKN_SHOW
+%token <string>   TKN_BEGIN
+%token <string>   TKN_COMMIT
+%token <string>   TKN_ROLLBACK
+%token <string>   TKN_TRANSACTION
+%token <string>   TKN_TRANSACTIONS
+%token <string>   TKN_CREATE
+%token <string>   TKN_UPDATE
+%token <string>   TKN_REPLACE
+%token <string>   TKN_DELETE
+%token <string>   TKN_DROP
+%token <string>   TKN_DESCRIBE
+%token <string>   TKN_TABLE
+%token <string>   TKN_TABLES
+%token <string>   TKN_INDEX
+%token <string>   TKN_ROWS
+%token <string>   TKN_COLUMN
+%token <string>   TKN_TRIGGER
+%token <string>   TKN_INSERT
+%token <string>   TKN_SELECT
+%token <string>   TKN_INTO
+%token <string>   TKN_FROM
+%token <string>   TKN_WHERE
+%token <string>   TKN_VALUES
+%token <string>   TKN_SET
+%token <string>   TKN_ON
+%token <string>   TKN_IN
+%token <string>   TKN_OR
+%token <string>   TKN_PERSISTENT
+%token <string>   TKN_TEMPORARY
+%token <string>   TKN_CALLBACK
+%token <string>   TKN_VARCHAR
+%token <string>   TKN_INTEGER
+%token <string>   TKN_UNSIGNED
+%token <string>   TKN_REAL
+%token <string>   TKN_BLOB
+%token <integer>  TKN_PARAMETER
+%token <string>   TKN_LOGICAL_AND
+%token <string>   TKN_LOGICAL_OR
+%token <string>   TKN_LESS
+%token <string>   TKN_LESS_OR_EQUAL
+%token <string>   TKN_EQUAL
+%token <string>   TKN_GREATER_OR_EQUAL
+%token <string>   TKN_GREATER
+%token <string>   TKN_NOT
+%token <string>   TKN_LEFT_PAREN
+%token <string>   TKN_RIGHT_PAREN
+%token <string>   TKN_COMMA
+%token <string>   TKN_SEMICOLON
+%token <string>   TKN_PLUS
+%token <string>   TKN_MINUS
+%token <string>   TKN_STAR
+%token <string>   TKN_SLASH
+%token <number>   TKN_NUMBER
+%token <floating> TKN_FLOATING
+%token <string>   TKN_IDENTIFIER
+%token <string>   TKN_QUOTED_STRING
+
+%type <boolean>   optional_trigger_select
+
+%type <integer>   insert
+%type <integer>   insert_or_replace
+%type <integer>   insert_option
+
+%type <integer>   varchar
+%type <integer>   blob
+%type <integer>   sign
+
+%type <floating>  floating_value
+
+%start statement_list
+
+%code requires {
+    #include <murphy-db/mqi.h>
+    #include <murphy-db/mql.h>
+
+    typedef struct mql_callback_s  mql_callback_t;
+
+    int yy_mql_input(void *, unsigned);
+
+    mql_statement_t *mql_make_show_tables_statement(uint32_t);
+    mql_statement_t *mql_make_describe_statement(mqi_handle_t);
+    mql_statement_t *mql_make_transaction_statement(mql_statement_type_t,
+                                                    char *);
+    mql_statement_t *mql_make_insert_statement(mqi_handle_t, int, int,
+                                               mqi_data_type_t*,
+                                               mqi_column_desc_t*, void*);
+    mql_statement_t *mql_make_update_statement(mqi_handle_t, int,
+                                               mqi_cond_entry_t *, int,
+                                               mqi_data_type_t *,
+                                               mqi_column_desc_t *, void *);
+    mql_statement_t *mql_make_delete_statement(mqi_handle_t, int,
+                                               mqi_cond_entry_t *);
+    mql_statement_t *mql_make_select_statement(mqi_handle_t, int, int,
+                                               mqi_cond_entry_t *, int,
+                                               char **, mqi_data_type_t *,
+                                               int *, mqi_column_desc_t *);
+
+    mql_result_t *mql_result_success_create(void);
+    mql_result_t *mql_result_error_create(int, const char *, ...);
+    mql_result_t *mql_result_event_column_change_create(mqi_handle_t, int,
+                                                        mqi_change_value_t *,
+                                                        mql_result_t *);
+    mql_result_t *mql_result_event_row_change_create(mqi_event_type_t,
+                                                     mqi_handle_t,
+                                                     mql_result_t *);
+    mql_result_t *mql_result_event_table_create(mqi_event_type_t,mqi_handle_t);
+    mql_result_t *mql_result_event_transaction_create(mqi_event_type_t);
+    mql_result_t *mql_result_columns_create(int, mqi_column_def_t *);
+    mql_result_t *mql_result_rows_create(int, mqi_column_desc_t*,
+                                         mqi_data_type_t*,int*,int,int,void*);
+    mql_result_t *mql_result_string_create_table_list(int, char **);
+    mql_result_t *mql_result_string_create_column_change(const char *,
+                                                         const char *,
+                                                         mqi_change_value_t *,
+                                                         mql_result_t *);
+    mql_result_t *mql_result_string_create_row_change(mqi_event_type_t,
+                                                      const char *,
+                                                      mql_result_t *);
+    mql_result_t *mql_result_string_create_table_change(mqi_event_type_t,
+                                                        const char *);
+    mql_result_t *mql_result_string_create_transaction_change(
+                                                            mqi_event_type_t);
+    mql_result_t *mql_result_string_create_column_list(int, mqi_column_def_t*);
+    mql_result_t *mql_result_string_create_row_list(int, char **,
+                                                    mqi_column_desc_t *,
+                                                    mqi_data_type_t *, int *,
+                                                    int, int, void *);
+    mql_result_t *mql_result_list_create(mqi_data_type_t, int, void *);
+
+    mql_callback_t *mql_find_callback(char *);
+    int mql_create_column_trigger(char *, mqi_handle_t, int,mqi_data_type_t,
+                                  mql_callback_t *,
+                                  int, char **, mqi_column_desc_t *,
+                                  mqi_data_type_t *, int *,
+                                  int);
+    int mql_create_row_trigger(char *, mqi_handle_t, mql_callback_t *,
+                               int, char **, mqi_column_desc_t *,
+                               mqi_data_type_t *, int *, int);
+    int mql_create_table_trigger(char *, mql_callback_t *);
+    int mql_create_transaction_trigger(char *, mql_callback_t *);
+
+    int mql_begin_transaction(char *);
+    int mql_rollback_transaction(char *);
+    int mql_commit_transaction(char *);
+}
+
+
+%%
+
+/*#toplevel#*/
+statement_list:
+  statement
+| statement_list semicolon statement
+;
+
+semicolon: TKN_SEMICOLON {
+    if (mode != mql_mode_parser) {
+        result = mql_result_error_create(EINVAL, "multiple MQL statements");
+        YYERROR;
+    }
+};
+
+/*#toplevel#*/
+statement:
+  show_statement
+| create_statement
+| drop_statement
+| begin_statement
+| commit_statement
+| rollback_statement
+| describe_statement
+| insert_statement
+| update_statement
+| delete_statement
+| select_statement
+| error
+;
+
+/***************************
+ *
+ * Show statement
+ *
+ */
+/*#toplevel#*/
+show_statement:
+  show_table_statement
+;
+
+show_table_statement: TKN_SHOW show_tables
+;
+
+show_tables: table_flags TKN_TABLES {
+    char  *names[4096];
+    int    n;
+    
+    if (mode == mql_mode_precompile)
+        statement = mql_make_show_tables_statement(table_flags);
+    else {
+        if ((n = mqi_show_tables(table_flags, names,MQI_DIMENSION(names))) < 0)
+            MQL_ERROR(errno, "can't show tables: %s", strerror(errno));
+        else {
+            if (mode == mql_mode_exec) {
+                switch (rtype) {
+                case mql_result_string:
+                    result = mql_result_string_create_table_list(n, names);
+                    break;
+                case mql_result_list:
+                    result = mql_result_list_create(mqi_string,n,(void*)names);
+                    break;
+                default:
+                    result = mql_result_error_create(EINVAL,
+                                                     "can't show tables: %s",
+                                                     strerror(EINVAL));
+                    break;
+                }
+            }
+            else {
+                mql_result_t *r = mql_result_string_create_table_list(n,names);
+
+                fprintf(mqlout, "%s", mql_result_string_get(r));
+
+                mql_result_free(r);
+            }
+        }
+    }
+};
+
+/***********************************
+ *
+ * Create statement
+ *
+ */
+/*#toplevel#*/
+create_statement:
+  create_table_statement
+| create_index_statement
+| create_trigger_statement
+; 
+
+/*#toplevel#*/
+create_table_statement: TKN_CREATE create_table table_definition
+;
+
+/*#toplevel#*/
+create_index_statement: TKN_CREATE create_index index_definition
+;
+
+/*#toplevel#*/
+create_trigger_statement:
+  create_transaction_trigger
+| create_table_trigger
+| create_row_trigger
+| create_column_trigger
+;
+
+
+/* create table */
+
+create_table: table_flags TKN_TABLE {
+    coldef = coldefs;
+    
+    if (table_flags == MQI_ANY)
+        table_flags = MQI_TEMPORARY;
+};
+
+
+
+table_definition: TKN_IDENTIFIER TKN_LEFT_PAREN column_defs TKN_RIGHT_PAREN {
+    if (mqi_create_table($1, table_flags, NULL, coldefs) == MQI_HANDLE_INVALID)
+        MQL_ERROR(errno, "Can't create table: %s\n", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+/*#toplevel#*/
+column_defs:
+  column_def
+| column_defs TKN_COMMA column_def
+;
+
+/*#toplevel#*/
+column_def: column_name column_type {
+    memset(++coldef, 0, sizeof(mqi_column_def_t));
+};
+
+column_name: TKN_IDENTIFIER {
+    if ((coldef - coldefs) >= MQI_COLUMN_MAX) {
+        MQL_ERROR(EOVERFLOW, "Too many columns. Max %d columns allowed\n",
+                  MQI_COLUMN_MAX);
+    }
+
+    coldef->name = $1;
+};
+
+/*#toplevel#*/
+column_type:
+  varchar       { coldef->type = mqi_varchar;   coldef->length = $1; }
+| TKN_INTEGER   { coldef->type = mqi_integer;   coldef->length = 0;  }
+| TKN_UNSIGNED  { coldef->type = mqi_unsignd;   coldef->length = 0;  }
+| TKN_REAL      { coldef->type = mqi_floating;  coldef->length = 0;  }
+| blob          { coldef->type = mqi_blob;      coldef->length = $1; }
+;
+
+varchar: TKN_VARCHAR TKN_LEFT_PAREN TKN_NUMBER TKN_RIGHT_PAREN {
+    $$ = (int)$3;
+};
+
+blob: TKN_BLOB TKN_LEFT_PAREN TKN_NUMBER TKN_RIGHT_PAREN {
+    $$ = (int)$3;
+};
+
+/* create index */
+
+create_index: TKN_INDEX {
+    ncolnam = 0;
+};
+
+index_definition: TKN_ON table_name TKN_LEFT_PAREN column_list TKN_RIGHT_PAREN
+{
+    colnams[ncolnam] = NULL;
+
+    if (mqi_create_index(table, colnams) < 0)
+        MQL_ERROR(errno, "failed to create index: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+
+/* create trigger */
+
+/*#toplevel#*/
+create_transaction_trigger: TKN_CREATE create_trigger transaction_trigger
+;
+
+/*#toplevel#*/
+create_table_trigger: TKN_CREATE create_trigger table_trigger
+;
+
+/*#toplevel#*/
+create_row_trigger: TKN_CREATE create_trigger row_trigger
+;
+
+/*#toplevel#*/
+create_column_trigger: TKN_CREATE create_trigger column_trigger
+;
+
+create_trigger: TKN_TRIGGER TKN_IDENTIFIER TKN_ON {
+    if (mode != mql_mode_exec)
+        MQL_ERROR(EPERM, "only mql_exec_string() can create triggers");
+    else {
+        table = MQI_HANDLE_INVALID;
+        ncolnam = 0;
+        trigger_name = $2;
+        callback = NULL;
+    }
+};
+
+
+
+transaction_trigger: TKN_TRANSACTIONS callback {
+
+    if (mql_create_transaction_trigger(trigger_name, callback) < 0) {
+        MQL_ERROR(errno, "failed to create transaction trigger: %s",
+                  strerror(errno));
+    }
+    else {
+        MQL_SUCCESS;
+    }
+};
+
+table_trigger: TKN_TABLES callback {
+
+    if (mql_create_table_trigger(trigger_name, callback) < 0)
+        MQL_ERROR(errno, "failed to create table trigger: %s",strerror(errno));
+    else
+        MQL_SUCCESS;
+
+};
+
+row_trigger: TKN_ROWS TKN_IN table_name callback trigger_select {
+
+    int rowsize;
+    int colsizes[MQI_COLUMN_MAX + 1];
+    mqi_data_type_t coltypes[MQI_COLUMN_MAX + 1];
+    char errbuf[256];
+    int sts;
+
+    sts = set_select_variables(&rowsize, coltypes,colsizes,
+                               errbuf, sizeof(errbuf));
+    if (sts < 0)
+        MQL_ERROR(errno, "%s", errbuf);
+
+    sts = mql_create_row_trigger(trigger_name, table, callback,
+                                 ncolnam,colnams,
+                                 coldescs, coltypes, colsizes,
+                                 rowsize);
+    if (sts < 0)
+        MQL_ERROR(errno, "failed to create row triger: %s",strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+column_trigger: TKN_COLUMN TKN_IDENTIFIER TKN_IN table_name callback
+                optional_trigger_select
+{
+    int colidx;
+    mqi_data_type_t coltype;
+    int rowsize;
+    int colsizes[MQI_COLUMN_MAX + 1];
+    mqi_data_type_t coltypes[MQI_COLUMN_MAX + 1];
+    char errbuf[256];
+    int sts;
+
+    if ((colidx  = mqi_get_column_index(table, $2))    < 0 ||
+        (coltype = mqi_get_column_type(table, colidx)) < 0  )
+    {
+        MQL_ERROR(errno, "do not know trigger column '%s'", $2);
+    }
+
+    if ($6) {
+        sts = set_select_variables(&rowsize, coltypes,colsizes,
+                                   errbuf, sizeof(errbuf));
+        if (sts < 0)
+            MQL_ERROR(errno, "%s", errbuf);
+
+        sts = mql_create_column_trigger(trigger_name,
+                                        table, colidx,coltype, callback,
+                                        ncolnam,colnams,
+                                        coldescs,coltypes,colsizes,
+                                        rowsize);
+    }
+    else {
+        sts = mql_create_column_trigger(trigger_name,
+                                        table, colidx,coltype, callback,
+                                        0,NULL,NULL,NULL,NULL, 0);
+    }
+
+    if (sts < 0)
+        MQL_ERROR(errno,"failed to create column trigger: %s",strerror(errno));
+    else
+        MQL_SUCCESS;
+};
+
+
+callback: TKN_CALLBACK TKN_IDENTIFIER {
+    if (!(callback = mql_find_callback($2))) {
+        MQL_ERROR(ENOENT, "can't find callback '%s'", $2);
+    }
+};
+
+trigger_select: TKN_SELECT columns
+;
+
+optional_trigger_select:
+  /* no select */   { $$ = false; }
+| trigger_select    { $$ = true;  }
+;
+
+/***********************************
+ *
+ * Drop statement
+ *
+ */
+/*#toplevel#*/
+drop_statement:
+  drop_table_statement
+| drop_index_statement
+; 
+
+/* drop table */
+
+/*#toplevel#*/
+drop_table_statement: TKN_DROP TKN_TABLE  table_name {
+    if (mqi_drop_table(table) < 0)
+        MQL_ERROR(errno, "failed to drop table: %s", strerror(errno));
+    else
+        MQL_SUCCESS;
+}
+;
+
+
+/* drop index */
+
+/*#toplevel#*/
+drop_index_statement: TKN_DROP TKN_INDEX table_name {
+};
+
+
+/***********************************
+ *
+ * Begin/Commit/Rollback statement
+ *
+ */
+/*#toplevel#*/
+begin_statement: TKN_BEGIN transaction TKN_IDENTIFIER {
+    if (mode == mql_mode_precompile)
+        statement = mql_make_transaction_statement(mql_statement_begin, $3);
+    else {
+        if (mql_begin_transaction($3) < 0)
+            MQL_ERROR(errno, "can't start transaction: %s", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+/*#toplevel#*/
+commit_statement: TKN_COMMIT transaction TKN_IDENTIFIER {
+    if (mode == mql_mode_precompile)
+        statement = mql_make_transaction_statement(mql_statement_commit, $3);
+    else {
+        if (mql_commit_transaction($3) < 0)
+            MQL_ERROR(errno, "can't commit transaction: %s", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+/*#toplevel#*/
+rollback_statement: TKN_ROLLBACK transaction TKN_IDENTIFIER {
+    if (mode == mql_mode_precompile)
+        statement = mql_make_transaction_statement(mql_statement_rollback, $3);
+    else {
+        if (mql_rollback_transaction($3) < 0)
+            MQL_ERROR(errno, "can't rollback transaction: %s",strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+
+
+/***********************************
+ *
+ * Describe statement
+ *
+ */
+/*#toplevel#*/
+describe_statement: TKN_DESCRIBE table_name {
+    mqi_column_def_t defs[MQI_COLUMN_MAX];
+    int              n;
+
+    if (mode == mql_mode_precompile)
+        statement = mql_make_describe_statement(table);
+    else {
+        if ((n = mqi_describe(table, defs, MQI_COLUMN_MAX)) < 0)
+            MQL_ERROR(errno, "can't describe table: %s", strerror(errno));
+        else {
+            if (mode == mql_mode_exec) {
+                switch (rtype) {
+                case mql_result_columns:
+                    result = mql_result_columns_create(n, defs);
+                    break;
+                case mql_result_string:
+                    result = mql_result_string_create_column_list(n, defs);
+                    break;
+                default:
+                    result = mql_result_error_create(EINVAL, "describe failed:"
+                                                     " invalid result type %d",
+                                                     rtype);
+                    break;
+                }
+            }
+            else {
+                mql_result_t *r = mql_result_string_create_column_list(n,defs);
+
+                fprintf(mqlout, "%s", mql_result_string_get(r));
+
+                mql_result_free(r);
+            }
+        }
+    }
+};
+
+/***********************************
+ *
+ * Insert statement
+ *
+ */
+/*#toplevel#*/
+insert_statement: insert table_name insert_columns TKN_VALUES insert_values {
+    void              *row[2];
+    char              *col;
+    mqi_column_desc_t *cd;
+    mqi_data_type_t    coltypes[MQI_COLUMN_MAX + 1];
+    input_t           *inp;
+    mqi_data_type_t    type;
+    int                cindex;
+    int                err;
+    int                i;
+
+    if (!ncolnam) {
+        while ((colnams[ncolnam] = mqi_get_column_name(table, ncolnam)))
+            ncolnam++;
+    }
+
+    if (ncolnam != ninput)
+        MQL_ERROR(EINVAL, "unbalanced set of columns and values");
+
+    for (i = 0, err = 0; i < ncolnam; i++) {
+        col = colnams[i];
+        cd  = coldescs + i;
+        inp = inputs + i;
+
+        if ((cindex = mqi_get_column_index(table, col)) < 0) {
+            MQL_ERROR(ENOENT, "know nothing about '%s'", col);
+            err = 1;
+            continue;
+        }
+
+        type = coltypes[i] = mqi_get_column_type(table, cindex);
+
+        if (type != inp->type) {
+            if (type != mqi_integer ||
+                inp->type != mqi_unsignd ||
+                inp->value.unsignd > INT32_MAX)
+            {
+                MQL_ERROR(EINVAL, "mismatching column and value type for '%s'",
+                          col);
+                err = 1;
+                continue;
+            }
+        }
+
+        cd->cindex = cindex;
+        cd->offset = (void *)&inp->value - (void *)inputs;
+    }
+
+    cd = coldescs + i;
+    cd->cindex = -1;
+    cd->offset = -1;
+
+
+    if (mode == mql_mode_precompile) {
+        statement = mql_make_insert_statement(table, $1, ncolnam, coltypes,
+                                              coldescs, inputs);
+    }
+    else {
+        row[0] = (void *)inputs;
+        row[1] = NULL;
+
+        if (err || mqi_insert_into(table, $1, coldescs, row) < 0)
+            MQL_ERROR(errno, "insert failed: %s\n", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+
+insert: insert_or_replace {
+      table = MQI_HANDLE_INVALID;
+      ncolnam = 0;
+      ninput = 0;
+      ncoldesc = 0;
+      $$ = $1;
+};
+
+insert_or_replace:
+  TKN_INSERT insert_option TKN_INTO  { $$ = $2; }
+| TKN_REPLACE TKN_INTO               { $$ = 1;  } 
+;
+
+insert_option:
+   /* no option */     { $$ = 0; }
+| TKN_OR TKN_REPLACE   { $$ = 1; }
+/*
+| TKN_IGNORE           { $$ = 1; }
+*/
+;
+
+
+insert_columns: 
+  /* all columns: leaves ncolnam as zero */
+| TKN_LEFT_PAREN column_list TKN_RIGHT_PAREN
+;
+
+insert_values: TKN_LEFT_PAREN input_value_list TKN_RIGHT_PAREN;
+
+/*#toplevel#*/
+input_value_list:
+  input_value
+| input_value_list TKN_COMMA input_value
+; 
+
+
+
+/***********************************
+ *
+ * Update statement
+ *
+ */
+/*#toplevel#*/
+update_statement: update table_name TKN_SET assignment_list where_clause {
+    mqi_column_desc_t *cd    = coldescs + ninput;
+    mqi_cond_entry_t  *where = (cond == conds) ? NULL : conds;
+    mqi_data_type_t    coltypes[MQI_COLUMN_MAX + 1];
+    int                i;
+
+    if (!ninput)
+        MQL_ERROR(ENOMEDIUM, "No column to update");
+
+    cd->cindex = -1;
+    cd->offset = -1;
+
+    if (mode == mql_mode_precompile) {
+        for (i = 0;  i < ninput; i++)
+            coltypes[i] = inputs[i].type;
+
+        statement = mql_make_update_statement(table, cond - conds, conds,
+                                              ninput, coltypes, coldescs,
+                                              inputs);
+    }
+    else {
+        if (mqi_update(table, where, coldescs, inputs) < 0)
+            MQL_ERROR(errno, "update failed: %s", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+update: TKN_UPDATE {
+      table = MQI_HANDLE_INVALID;
+      ninput = 0;
+      ncoldesc = 0;
+      nstr = 0;
+      nint = 0;
+      nuint = 0;
+      nfloat = 0;
+      cond = conds;
+      binds = 0;
+};
+
+
+/*#toplevel#*/
+assignment_list:
+  assignment
+| assignment_list TKN_COMMA assignment
+;
+
+/*#toplevel#*/
+assignment: TKN_IDENTIFIER TKN_EQUAL input_value {
+    int                i   = ninput - 1;
+    input_t           *inp = inputs + i;
+    mqi_column_desc_t *cd  = coldescs + i;
+    int                cindex;
+    int                offset;
+    mqi_data_type_t    type;
+
+    if ((cindex = mqi_get_column_index(table, $1)) < 0)
+        MQL_ERROR(ENOENT, "know nothing about '%s'", $1);
+    if ((inp->flags & MQL_BINDABLE))
+        offset = -(MQL_BIND_INDEX(inp->flags) + 1);
+    else {
+        if ((type = mqi_get_column_type(table, cindex)) != inp->type) {
+            if (type != mqi_integer ||
+                inp->type != mqi_unsignd ||
+                inp->value.unsignd > INT32_MAX)
+            {
+                MQL_ERROR(EINVAL, "mismatching column and value type "
+                          "for '%s'",$1);
+            }
+        }
+        offset = (void *)&inp->value - (void *)inputs;
+    }
+
+    cd->cindex = cindex;
+    cd->offset = offset;
+};
+
+
+
+/***********************************
+ *
+ * Delete statement
+ *
+ */
+/*#toplevel#*/
+delete_statement: delete table_name where_clause {
+    mqi_cond_entry_t *where = (cond == conds) ? NULL : conds;
+
+    if (mode == mql_mode_precompile)
+        statement = mql_make_delete_statement(table, cond - conds, where);
+    else {
+        if (mqi_delete_from(table, where) < 0)
+            MQL_ERROR(errno, "delete failed: %s", strerror(errno));
+        else
+            MQL_SUCCESS;
+    }
+};
+
+delete: TKN_DELETE TKN_FROM {
+    table = MQI_HANDLE_INVALID;
+    nstr = 0;
+    nint = 0;
+    nuint = 0;
+    nfloat = 0;
+    cond = conds;
+    binds = 0;
+};
+
+/***********************************
+ *
+ * Select statement
+ *
+ */
+/*#toplevel#*/
+select_statement: select columns TKN_FROM table_name where_clause {
+    int colsizes[MQI_COLUMN_MAX + 1];
+    mqi_data_type_t coltypes[MQI_COLUMN_MAX + 1];
+    mqi_cond_entry_t *where;
+    int rowsize;
+    int tsiz;
+    size_t rsiz;
+    void *rows;
+    char errbuf[256];
+    int sts;
+    int n;
+
+
+    if ((tsiz = mqi_get_table_size(table)) < 0)
+        MQL_ERROR(errno, "can't get table size: %s", strerror(errno));
+
+
+    sts = set_select_variables(&rowsize, coltypes,colsizes,
+                               errbuf, sizeof(errbuf));
+    if (sts < 0)
+        MQL_ERROR(errno, "%s", errbuf);
+
+
+    if (mode != mql_mode_precompile && mode != mql_mode_exec && !tsiz) {
+        if (mode == mql_mode_parser)
+            fprintf(mqlout, "no rows\n");
+    }
+    else {
+        rsiz  = tsiz * rowsize;
+        rows  = alloca(rsiz);
+        where = (cond == conds) ? NULL : conds;
+
+        if (mode != mql_mode_precompile) {
+            if (tsiz != 0) {
+                if ((n = mqi_select(table, where,
+                                    coldescs, rows, rowsize, tsiz)) < 0)
+                    MQL_ERROR(errno, "select failed: %s", strerror(errno));
+            }
+            else
+                n = 0;
+        }
+
+        switch (mode) {
+        case mql_mode_parser:
+            fprintf(mqlout, "Selected %d rows:\n", n);
+            print_query_result(coldescs, coltypes, colsizes, n, rowsize, rows);
+            break;
+        case mql_mode_exec:
+            if (rtype == mql_result_rows) {
+                result = mql_result_rows_create(ncolnam, coldescs, coltypes,
+                                                colsizes, n, rowsize, rows);
+            }
+            else {
+                result = mql_result_string_create_row_list(ncolnam, colnams,
+                                                           coldescs, coltypes,
+                                                           colsizes,
+                                                           n, rowsize, rows);
+            }
+            break;
+        case mql_mode_precompile:
+            statement = mql_make_select_statement(table, rowsize,
+                                                  cond - conds, where,
+                                                  ncolnam, colnams, coltypes,
+                                                  colsizes, coldescs); 
+            break;
+        }
+    }
+};
+
+
+select: TKN_SELECT {
+    table = MQI_HANDLE_INVALID;
+    ncolnam = 0;
+    nstr = 0;
+    nint = 0;
+    nuint = 0;
+    nfloat = 0;
+    cond = conds;
+    binds = 0;
+};
+
+columns:
+  TKN_STAR
+| column_list
+;
+
+
+/***********************************
+ *
+ * Transaction
+ *
+ */
+transaction:
+  /* no token */
+| TKN_TRANSACTION
+;
+
+/***********************************
+ *
+ * Table name
+ *
+ */
+table_name: TKN_IDENTIFIER {
+    if ((table = mqi_get_table_handle($1)) == MQI_HANDLE_INVALID)
+        MQL_ERROR(errno, "Do not know anything about '%s'", $1);
+};
+
+/***********************************
+ *
+ * Table flags
+ *
+ */
+table_flags:
+  /* no option */ { table_flags = MQI_ANY;        }
+| TKN_PERSISTENT  { table_flags = MQI_PERSISTENT; }
+| TKN_TEMPORARY   { table_flags = MQI_TEMPORARY;  }
+;
+
+/***********************************
+ *
+ * Column list
+ *
+ */
+/*#toplevel#*/
+column_list:
+  column
+| column_list TKN_COMMA column
+;
+
+column: TKN_IDENTIFIER {
+    if (ncolnam < MQI_COLUMN_MAX)
+        colnams[ncolnam++] = $1;
+    else
+        MQL_ERROR(EOVERFLOW, "Too many columns");
+};
+
+/***********************************
+ *
+ * Input value
+ *
+ */
+/*#toplevel#*/
+input_value:
+  string_input
+| integer_input
+| unsigned_input
+| floating_input
+| parameter_input
+;
+
+string_input:   TKN_QUOTED_STRING { SET_INPUT(varchar,  $1);              };
+integer_input:  sign TKN_NUMBER   { SET_INPUT(integer,  $1 * $2);         };
+unsigned_input: TKN_NUMBER        { SET_INPUT(unsignd,  $1);              };
+floating_input: TKN_FLOATING      { SET_INPUT(floating, $1);              }
+|               sign TKN_FLOATING { SET_INPUT(floating, (double)$1 * $2); };
+
+parameter_input: TKN_PARAMETER {
+    input_t *input;
+
+    if (mode != mql_mode_precompile) {
+        MQL_ERROR(EINVAL, "parameters are allowed only in "
+                  "precompilation mode");
+    }
+    if (binds >= MQL_PARAMETER_MAX) {
+        MQL_ERROR(EOVERFLOW, "number of parameters exceeds %d",
+                  MQL_PARAMETER_MAX);
+    }
+
+    input = inputs + ninput++;
+    input->type = $1;
+    input->flags = MQL_BINDABLE | MQL_BIND_INDEX(binds++);
+
+    memset(&input->value, 0, sizeof(input->value));
+};
+
+
+/***********************************
+ *
+ * Where clause
+ *
+ */
+where_clause:
+  /* no where clause  */ {
+  }
+| TKN_WHERE conditional_expression {
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_end;
+    cond++;
+  };
+
+
+/*#toplevel#*/
+conditional_expression:
+  relational_expression
+| relational_expression logical_operator relational_expression
+;
+
+/*#toplevel#*/
+relational_expression: value relational_operator value;
+
+/*#toplevel#*/
+value: 
+  column_value
+| string_variable
+| integer_variable
+| unsigned_variable
+| floating_variable
+| parameter_value
+| expression_value
+| unary_operator value
+;
+
+column_value: TKN_IDENTIFIER {
+    int cx;
+
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+
+    if ((cx = mqi_get_column_index(table,$1)) < 0)
+        MQL_ERROR(ENOENT, "no column with name '%s'", $1);
+
+    cond->type = mqi_column;
+    cond->u.column = cx;
+    cond++;
+};
+
+string_variable: TKN_QUOTED_STRING {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    strs[nstr] = $1;
+    cond->type = mqi_variable;
+    cond->u.variable.flags = 0;
+    cond->u.variable.type = mqi_varchar;
+    cond->u.variable.v.varchar = strs + nstr++;
+    cond++;
+};
+
+integer_variable: sign TKN_NUMBER {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    ints[nint] = $1 * $2;
+    cond->type = mqi_variable;
+    cond->u.variable.type = mqi_integer;
+    cond->u.variable.v.integer = ints + nint++;
+    cond++;
+};
+
+unsigned_variable: TKN_NUMBER {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    uints[nuint] = $1;
+    cond->type = mqi_variable;
+    cond->u.variable.flags = 0;
+    cond->u.variable.type = mqi_unsignd;
+    cond->u.variable.v.unsignd = uints + nuint++;
+    cond++;
+};
+
+floating_variable: floating_value {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    floats[nfloat] = $1;
+    cond->type = mqi_variable;
+    cond->u.variable.flags = 0;
+    cond->u.variable.type = mqi_floating;
+    cond->u.variable.v.floating = floats + nfloat++;
+    cond++;
+};
+
+floating_value:
+  TKN_FLOATING        { return $1;              }
+| sign TKN_FLOATING   { return (double)$1 * $2; }
+
+
+parameter_value: TKN_PARAMETER {
+    if (mode != mql_mode_precompile) {
+        MQL_ERROR(EINVAL, "parameters are allowed only in "
+                  "precompilation mode");
+    }
+    if (binds >= MQL_PARAMETER_MAX) {
+        MQL_ERROR(EOVERFLOW, "number of parameters exceeds %d",
+                  MQL_PARAMETER_MAX);
+    }
+    if (cond - conds >= MQI_COND_MAX) {
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    }
+    cond->type = mqi_variable;
+    cond->u.variable.flags = MQL_BINDABLE | MQL_BIND_INDEX(binds++);
+    cond->u.variable.type = $1;
+    cond->u.variable.v.generic = NULL;
+    cond++;
+};
+
+expression_value:
+  TKN_LEFT_PAREN  {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_begin;
+    cond++;
+  }
+  conditional_expression
+  TKN_RIGHT_PAREN {
+    if (cond - conds >= MQI_COND_MAX)
+        MQL_ERROR(EOVERFLOW, "too complex condition");
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_end;
+    cond++;
+  }
+;
+
+
+sign:
+  TKN_PLUS  { $$ = +1; }
+| TKN_MINUS { $$ = -1; }
+;
+
+
+unary_operator:
+  TKN_NOT {
+      cond->type = mqi_operator;
+      cond->u.operator_ = mqi_not;
+      cond++;
+  }
+;
+
+relational_operator:
+  TKN_LESS {
+      cond->type = mqi_operator;
+      cond->u.operator_ = mqi_less;
+      cond++;
+  }
+| TKN_LESS_OR_EQUAL {
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_leq;
+    cond++;
+  }
+| TKN_EQUAL {
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_eq;
+    cond++;
+  }
+| TKN_GREATER_OR_EQUAL {
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_geq;
+    cond++;
+  }
+| TKN_GREATER {
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_gt;
+    cond++;
+  }
+;
+
+logical_operator:
+  TKN_LOGICAL_AND {
+      cond->type = mqi_operator;
+      cond->u.operator_ = mqi_and;
+      cond++;
+  }
+| TKN_LOGICAL_OR {
+    cond->type = mqi_operator;
+    cond->u.operator_ = mqi_or;
+    cond++;
+  }
+;
+
+
+%%
+
+
+int mql_exec_file(const char *path)
+{
+    char buf[1024];
+    int sts;
+
+    mode   = mql_mode_parser;
+    rtype  = mql_result_unknown;
+    mqlbuf = NULL;
+    mqlout = stderr;
+    
+    if (!path) {
+        mqlin = fileno(stdin);
+        sts = yy_mql_parse() ? -1 : 0;
+    }
+    else {
+        strncpy(buf, path, sizeof(buf));
+        buf[sizeof(buf)-1] = '\0';
+        
+        file = basename(buf);
+
+        if ((mqlin = open(path, O_RDONLY)) < 0) {
+            sts = -1;
+            fprintf(mqlout, "could not open file '%s': %s\n",
+                    path, strerror(errno));
+        }
+        else {
+            sts = yy_mql_parse() ? -1 : 0;
+            close(mqlin);
+        }
+    }
+
+    mqlin = -1;
+    
+    return sts;
+}
+
+
+mql_result_t *mql_exec_string(mql_result_type_t result_type, const char *str)
+{
+    if (result_type == mql_result_dontcare)
+        result_type = mql_result_string;
+
+    MDB_CHECKARG((result_type == mql_result_event ||
+                  result_type == mql_result_columns  ||
+                  result_type == mql_result_rows ||
+                  result_type == mql_result_string  ) && 
+                 str, NULL);
+
+    mode = mql_mode_exec;
+    result = NULL;
+    rtype  = result_type;
+    mqlbuf = str;
+
+    if (yy_mql_parse() && !result) {
+        result = mql_result_error_create(EIO, "Syntax error in '%s'", str);
+    }
+
+
+    return result;
+}
+
+mql_statement_t *mql_precompile(const char *str)
+{
+    MDB_CHECKARG(str, NULL);
+
+    mode = mql_mode_precompile;
+    rtype = mql_result_unknown;
+    statement = NULL;
+    mqlbuf = str;
+    
+    yy_mql_parse();
+
+    return statement;
+} 
+
+int yy_mql_input(void *dst, unsigned dstlen)
+{
+    int len = 0;
+
+    if (dst && dstlen > 0) {
+
+        if (mqlbuf) {
+            if ((len = strlen(mqlbuf)) < 1)
+                len = 0;
+            else if ((unsigned)len + 1 <= dstlen) {
+                memcpy(dst, mqlbuf, len + 1);
+                mqlbuf += len;
+            }
+            else {
+                memcpy(dst, mqlbuf, dstlen);
+                mqlbuf += dstlen;
+            }
+        }
+        else if (mqlin >= 0) {
+            while ((len = read(mqlin, dst, dstlen)) < 0) {
+                if (errno != EINTR) {
+                    break;
+                }
+            }
+        }
+    }
+
+    return len;
+}
+
+
+void yy_mql_error(const char *msg)
+{
+    if (mode == mql_mode_parser)
+        fprintf(mqlout, "Error: '%s'\n", msg);
+}
+
+
+static int set_select_variables(int *rowsize,
+                                mqi_data_type_t *coltypes,
+                                int *colsizes,
+                                char *errbuf, int elgh)
+{
+    mqi_column_desc_t *cd;
+    int i;
+    int rlgh;
+    int colsize;
+    int colidx;
+    mqi_data_type_t coltype;
+
+    if (!ncolnam) {
+        while ((colnams[ncolnam] = mqi_get_column_name(table, ncolnam)))
+            ncolnam++;
+    }
+
+    for (i = 0, rlgh = 0;  i < ncolnam;   i++) {
+        cd = coldescs + i;
+        
+        if ((colidx  = mqi_get_column_index(table, colnams[i])) < 0 ||
+            (colsize = mqi_get_column_size(table, colidx))      < 0 ||
+            (coltype = mqi_get_column_type(table, colidx)) == mqi_error)
+        {
+            snprintf(errbuf, elgh, "invalid column '%s'", colnams[i]);
+            return -1;
+        }
+        cd->cindex = colidx;
+        cd->offset = rlgh;
+        
+        coltypes[i] = coltype;
+        colsizes[i] = colsize;
+        
+        switch (coltype) {
+        case mqi_varchar:   rlgh += sizeof(char *);    break;
+        case mqi_integer:   rlgh += sizeof(int32_t);   break;
+        case mqi_unsignd:   rlgh += sizeof(uint32_t);  break;
+        case mqi_floating:  rlgh += sizeof(double);    break;
+        case mqi_blob:      rlgh += sizeof(void *);    break;
+        default:                                       break;
+        }
+    } /* for */
+    
+    cd = coldescs + i;
+    cd->cindex = -1;
+    cd->offset = -1;
+
+    *rowsize = rlgh;
+
+    return 0;
+}
+
+
+static void print_query_result(mqi_column_desc_t *coldescs,
+                               mqi_data_type_t   *coltypes,
+                               int               *colsizes,
+                               int                nresult,
+                               int                recsize,
+                               void              *results)
+{
+    int i, j, recoffs;
+    void *data;
+    char  name[4096];
+    int   clgh;
+    int   clghs[MQI_COLUMN_MAX + 1];
+    int   n;
+
+    for (j = 0, n = 0;  j < ncolnam;  j++) {
+        snprintf(name, sizeof(name),  "%s", colnams[j]);
+
+        switch (coltypes[j]) {
+        case mqi_varchar:   clgh = colsizes[j] - 1;  break;
+        case mqi_integer:   clgh = 11;               break;
+        case mqi_unsignd:   clgh = 10;               break;
+        case mqi_floating:  clgh = 10;               break;
+        default:            clgh = 0;                break;
+        }
+
+        clghs[j] = clgh;
+
+        if (clgh < (int)sizeof(name))
+            name[clgh] = '\0';
+
+        n += fprintf(mqlout, "%s%*s", j?" ":"", clgh,name);
+
+    }
+
+    if (n > (int)sizeof(name)-1)
+        n = sizeof(name)-1;
+    memset(name, '-', n);
+    name[n] = '\0';
+
+    fprintf(mqlout, "\n%s\n", name);
+
+
+
+    for (i = 0, recoffs = 0;  i < nresult;  i++, recoffs += recsize) {
+        for (j = 0;  j < ncolnam;  j++) {
+            if (j) fprintf(mqlout, " ");
+
+            data = results + (recoffs + coldescs[j].offset);
+            clgh = clghs[j];
+
+#define PRINT(t,f) fprintf(mqlout, f, clgh, *(t *)data)
+
+            switch (coltypes[j]) {
+            case mqi_varchar:     PRINT(char *  , "%*s"   );    break;
+            case mqi_integer:     PRINT(int32_t , "%*d"   );    break;
+            case mqi_unsignd:     PRINT(uint32_t, "%*u"   );    break;
+            case mqi_floating:    PRINT(double  , "%*.2lf");    break;
+            case mqi_blob:                                      break;
+            default:                                            break;
+            }
+
+#undef PRINT
+
+        }
+        fprintf(mqlout, "\n");
+    }
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim:set expandtab shiftwidth=4:
+ */
diff --git a/src/murphy-db/mql/mql-scanner.l b/src/murphy-db/mql/mql-scanner.l
new file mode 100644 (file)
index 0000000..3e19650
--- /dev/null
@@ -0,0 +1,283 @@
+%{
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "mql-parser.h"
+
+#if 0
+#define DEBUG_SCANNER
+#endif
+
+#ifdef DEBUG_SCANNER
+#define PRINT(fmt, args...) printf(fmt, args) 
+#else
+#define PRINT(fmt, args...)
+#endif
+
+#define YY_SKIP_YYWRAP
+#define YY_NO_INPUT
+
+#define EOF_TOKEN  \
+    YY_FLUSH_BUFFER; \
+    yy_mql_lex_destroy(); \
+    yyterminate()
+
+#define ARGLESS_TOKEN(t) \
+     do { \
+         PRINT("%s-", #t); \
+         yy_mql_lval.string = #t; \
+         return TKN_##t; \
+     } while (0)
+
+#define STRING_TOKEN(t) \
+     do { \
+         PRINT("%s(%s)-", #t, yytext); \
+         yy_mql_lval.string = copy_to_ringbuf(yytext);  \
+         return TKN_##t; \
+     } while (0)
+
+#define NUMBER_TOKEN \
+    do { \
+        yy_mql_lval.number = strtoul(yytext, NULL, 10); \
+        PRINT("NUMBER(%lld)-", yy_mql_lval.number); \
+        return TKN_NUMBER; \
+    } while(0)
+
+#define FLOATING_TOKEN \
+    do { \
+       yy_mql_lval.floating = strtod(yytext, NULL); \
+       return TKN_FLOATING; \
+    } while (0)
+
+#define SEMICOLON_TOKEN \
+     do { \
+         PRINT("%s\n", "SEMICOLON"); \
+         yy_mql_lval.string = "SEMICOLON"; \
+         return TKN_SEMICOLON; \
+     } while (0)
+
+#define PARAMETER_TOKEN \
+    do { \
+        mqi_data_type_t type; \
+        switch(yytext[1]) { \
+        case 's': type = mqi_varchar;  break; \
+        case 'd': type = mqi_integer;  break; \
+        case 'u': type = mqi_unsignd;  break; \
+        case 'f': type = mqi_floating; break; \
+        default : type = mqi_unknown;  break; \
+        } \
+        yy_mql_lval.type = type; \
+        PRINT("PARAMETER(%d)-", yy_mql_lval.type); \
+        return TKN_PARAMETER; \
+    } while(0)
+
+
+#define YY_INPUT(buf, result, max_size) \
+    do { \
+        int n; \
+        if ((n = yy_mql_input(buf, max_size)) >= 0) \
+            result = n; \
+        else { \
+            result = 0; \
+            YY_FATAL_ERROR( "mql input failed" ); \
+        } \
+    } while (0)
+
+
+YYSTYPE  yy_mql_lval;
+
+
+
+static char  ringbuf[4096];
+static char *bufptr = ringbuf;
+static char *bufend = ringbuf + sizeof(ringbuf) - 1;
+
+static char *copy_to_ringbuf(const char *);
+
+
+%}
+
+%option prefix="yy_mql_"
+%option batch
+%option yylineno
+%option case-insensitive
+%option nounput noyymore noyywrap
+
+WHITESPACE        [\ \t\n]+
+
+SHOW              show
+BEGIN             begin
+COMMIT            commit
+ROLLBACK          rollback
+TRANSACTION       transaction
+TRANSACTIONS      transactions
+CREATE            create
+UPDATE            update
+REPLACE           replace
+DELETE            delete
+DROP              drop
+DESCRIBE          describe
+TABLE             table
+TABLES            tables
+INDEX             index
+ROWS              rows
+COLUMN            column
+TRIGGER           trigger
+INSERT            insert
+SELECT            select
+INTO              into
+FROM              from
+WHERE             where
+VALUES            values
+SET               set
+ON                on
+IN                in
+OR                or
+PERSISTENT        persistent
+TEMPORARY         temporary
+CALLBACK          callback
+
+VARCHAR           varchar
+INTEGER           integer
+UNSIGNED          unsigned
+REAL              real
+BLOB              blob
+PARAMETER         \%[sduf]
+
+LOGICAL_AND       \&
+LOGICAL_OR        \|
+LESS              <
+LESS_OR_EQUAL     <=
+EQUAL             =
+GREATER_OR_EQUAL  >=
+GREATER           >
+NOT               \!
+
+NOT_SQUOTE        [^\n'\;]
+NOT_DQUOTE        [^\n\"\;]
+
+NUMBER            [0-9]+
+FLOATING          [0-9]\.[0-9]*
+IDENTIFIER        [a-zA-Z]([a-zA-Z0-9_-]*[a-zA-Z0-9])*
+QUOTED_STRING     (('{NOT_SQUOTE}*')|(\"{NOT_DQUOTE}*\"))
+
+LEFT_PAREN        \(
+RIGHT_PAREN       \)
+COMMA             ,
+SEMICOLON         ;
+PLUS              \+
+MINUS             \-
+STAR              \*
+SLASH             \/
+
+
+
+
+%%
+
+<<EOF>>            { EOF_TOKEN;                        }
+{WHITESPACE}       {                                   }
+
+{SHOW}             { ARGLESS_TOKEN (SHOW);             }
+{BEGIN}            { ARGLESS_TOKEN (BEGIN);            }
+{COMMIT}           { ARGLESS_TOKEN (COMMIT);           }
+{ROLLBACK}         { ARGLESS_TOKEN (ROLLBACK);         }
+{TRANSACTION}      { ARGLESS_TOKEN (TRANSACTION);      }
+{TRANSACTIONS}     { ARGLESS_TOKEN (TRANSACTIONS);     }
+{CREATE}           { ARGLESS_TOKEN (CREATE);           }
+{UPDATE}           { ARGLESS_TOKEN (UPDATE);           }
+{REPLACE}          { ARGLESS_TOKEN (REPLACE);          }
+{DELETE}           { ARGLESS_TOKEN (DELETE);           }
+{DROP}             { ARGLESS_TOKEN (DROP);             }
+{DESCRIBE}         { ARGLESS_TOKEN (DESCRIBE);         }
+{TABLE}            { ARGLESS_TOKEN (TABLE);            }
+{TABLES}           { ARGLESS_TOKEN (TABLES);           }
+{INDEX}            { ARGLESS_TOKEN (INDEX);            }
+{ROWS}             { ARGLESS_TOKEN (ROWS);             }
+{COLUMN}           { ARGLESS_TOKEN (COLUMN);           }
+{TRIGGER}          { ARGLESS_TOKEN (TRIGGER);          }
+{INSERT}           { ARGLESS_TOKEN (INSERT);           }
+{SELECT}           { ARGLESS_TOKEN (SELECT);           }
+{INTO}             { ARGLESS_TOKEN (INTO);             }
+{FROM}             { ARGLESS_TOKEN (FROM);             }
+{WHERE}            { ARGLESS_TOKEN (WHERE);            }
+{VALUES}           { ARGLESS_TOKEN (VALUES);           }
+{SET}              { ARGLESS_TOKEN (SET);              }
+{ON}               { ARGLESS_TOKEN (ON);               }
+{IN}               { ARGLESS_TOKEN (IN);               }
+{OR}               { ARGLESS_TOKEN (OR);               }
+{PERSISTENT}       { ARGLESS_TOKEN (PERSISTENT);       }
+{TEMPORARY}        { ARGLESS_TOKEN (TEMPORARY);        }
+{CALLBACK}         { ARGLESS_TOKEN (CALLBACK);         }
+
+{VARCHAR}          { ARGLESS_TOKEN (VARCHAR);          }
+{INTEGER}          { ARGLESS_TOKEN (INTEGER);          }
+{UNSIGNED}         { ARGLESS_TOKEN (UNSIGNED);         }
+{REAL}             { ARGLESS_TOKEN (REAL);             }
+{BLOB}             { ARGLESS_TOKEN (BLOB);             }
+{PARAMETER}        { PARAMETER_TOKEN;                  }
+
+{LOGICAL_AND}      { ARGLESS_TOKEN (LOGICAL_AND);      }
+{LOGICAL_OR}       { ARGLESS_TOKEN (LOGICAL_OR);       }
+{LESS}             { ARGLESS_TOKEN (LESS);             }
+{LESS_OR_EQUAL}    { ARGLESS_TOKEN (LESS_OR_EQUAL);    }
+{EQUAL}            { ARGLESS_TOKEN (EQUAL);            }
+{GREATER_OR_EQUAL} { ARGLESS_TOKEN (GREATER_OR_EQUAL); }
+{GREATER}          { ARGLESS_TOKEN (GREATER);          }
+{NOT}              { ARGLESS_TOKEN (NOT);              }
+
+{LEFT_PAREN}       { ARGLESS_TOKEN (LEFT_PAREN);       }
+{RIGHT_PAREN}      { ARGLESS_TOKEN (RIGHT_PAREN);      }
+{COMMA}            { ARGLESS_TOKEN (COMMA);            }
+{SEMICOLON}        { SEMICOLON_TOKEN;                  }
+{PLUS}             { ARGLESS_TOKEN (PLUS);             }
+{MINUS}            { ARGLESS_TOKEN (MINUS);            }
+{STAR}             { ARGLESS_TOKEN (STAR);             }
+{SLASH}            { ARGLESS_TOKEN (SLASH);            }
+
+{NUMBER}           { NUMBER_TOKEN;                     }
+{FLOATING}         { FLOATING_TOKEN;                   }
+{IDENTIFIER}       { STRING_TOKEN (IDENTIFIER);        }
+{QUOTED_STRING}    { STRING_TOKEN (QUOTED_STRING);     }
+
+%%
+
+
+
+static char *copy_to_ringbuf(const char *string)
+{
+   const char *src;
+   char       *copy;
+   char        qt;
+
+   for (;;) {
+        if (bufptr >= bufend)
+           bufptr = ringbuf;
+
+        copy = bufptr;
+
+        switch (*(src = string)) {
+        case '\'':  qt = *src++;   break;
+        case '\"':  qt = *src++;   break;
+        default:    qt =  0xff;    break;
+        }
+
+        while (bufptr < bufend) {
+            if (!(*bufptr++ = *src++)) {
+                if (bufptr[-2] == qt)
+                  (--bufptr)[-1] = '\0';
+                return copy;
+            }
+        }
+   }
+}
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ * vim:set expandtab shiftwidth=4:
+ */
diff --git a/src/murphy-db/mql/result.c b/src/murphy-db/mql/result.c
new file mode 100644 (file)
index 0000000..fb19d29
--- /dev/null
@@ -0,0 +1,1480 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mql-result.h>
+#include "mql-parser.h"
+
+typedef struct column_desc_s           column_desc_t;
+typedef struct error_desc_s            error_desc_t;
+typedef struct result_error_s          result_error_t;
+typedef struct result_event_s          result_event_t;
+typedef struct result_event_colchg_s   result_event_colchg_t;
+typedef struct result_event_rowchg_s   result_event_rowchg_t;
+typedef struct result_event_table_s    result_event_table_t;
+typedef struct result_event_transact_s result_event_transact_t;
+typedef struct result_columns_s        result_columns_t;
+typedef struct result_rows_s           result_rows_t;
+typedef struct result_string_s         result_string_t;
+typedef struct result_list_s           result_list_t;
+
+struct column_desc_s {
+    int                   cindex;
+    mqi_data_type_t       type;
+    int                   offset;
+};
+
+struct error_desc_s {
+    int                   code;
+    char                  msg[0];
+};
+
+struct result_error_s {
+    mql_result_type_t     type;
+    error_desc_t          error;
+};
+
+struct result_event_s {
+    mql_result_type_t     type;
+    mqi_event_type_t      event;
+    uint8_t               data[0];
+};
+
+struct result_event_colchg_s {
+    mql_result_type_t     type;
+    mqi_event_type_t      event;
+    mqi_handle_t          table;
+    int                   column;
+    mqi_change_value_t    value;
+    mql_result_t         *select;
+    uint8_t               data[0];
+};
+
+struct result_event_rowchg_s {
+    mql_result_type_t     type;
+    mqi_event_type_t      event;
+    mqi_handle_t          table;
+    mql_result_t         *select;
+};
+
+struct result_event_table_s {
+    mql_result_type_t     type;
+    mqi_event_type_t      event;
+    mqi_handle_t          table;
+};
+
+struct result_event_transact_s {
+    mql_result_type_t     type;
+    mqi_event_type_t      event;
+};
+
+struct result_columns_s {
+    mql_result_type_t     type;
+    int                   ncol;
+    mqi_column_def_t      cols[0];
+};
+
+struct result_rows_s {
+    mql_result_type_t     type;
+    int                   rowsize;
+    int                   ncol;
+    int                   nrow;
+    void                 *data;
+    column_desc_t         cols[0];
+};
+
+struct result_string_s {
+    mql_result_type_t     type;
+    int                   length;
+    char                  string[0];
+};
+
+struct result_list_s {
+    mql_result_type_t     type;
+    int                   length;
+    struct {
+        mqi_data_type_t   type;
+        union {
+            char     *varchar[0];
+            int32_t   integer[0];
+            uint32_t  unsignd[0];
+            double    floating[0];
+            int       generic[0];
+        } v;
+    }                     value;
+};
+
+static inline mqi_data_type_t get_column_type(result_rows_t *, int);
+static inline void *get_column_address(result_rows_t *, int, int);
+
+
+int mql_result_is_success(mql_result_t *r)
+{
+    result_error_t *e = (result_error_t *)r;
+
+    if (e) {
+        if (e->type != mql_result_error)
+            return 1;
+
+        if (!e->error.code)
+            return 1;
+    }
+
+    return 0;
+}
+
+mql_result_t *mql_result_success_create(void)
+{
+    return mql_result_error_create(0, "Success");
+}
+
+mql_result_t *mql_result_error_create(int code, const char *fmt, ...)
+{
+    va_list ap;
+    result_error_t *rslt;
+    char buf[1024];
+    int l;
+
+    MDB_CHECKARG(code >= 0 && fmt, NULL);
+
+    va_start(ap, fmt);
+    l = vsnprintf(buf, sizeof(buf), fmt, ap);
+    va_end(ap);
+
+    if (l > (int)sizeof(buf))
+        l = sizeof(buf) - 1;
+
+    if ((rslt = calloc(1, sizeof(result_error_t) + l + 1))) {
+        rslt->type = mql_result_error;
+        rslt->error.code = code;
+        memcpy(rslt->error.msg, buf, l);
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_error_get_code(mql_result_t *r)
+{
+    int code;
+
+    MDB_CHECKARG(r, -1);
+
+    if (r->type != mql_result_error)
+        code = 0;
+    else {
+        result_error_t *rslt = (result_error_t *)r;
+        code = rslt->error.code;
+    }
+
+    return code;
+}
+
+const char *mql_result_error_get_message(mql_result_t *r)
+{
+    const char *msg;
+
+    MDB_CHECKARG(r, NULL);
+
+    if (r->type != mql_result_error)
+        msg = "Success";
+    else {
+        result_error_t *rslt = (result_error_t *)r;
+        msg = rslt->error.msg;
+    }
+
+    return msg;
+}
+
+mqi_event_type_t mql_result_event_get_type(mql_result_t *r)
+{
+    result_event_t *ev;
+
+    if (!r || r->type != mql_result_event)
+        return mqi_event_unknown;
+
+    ev = (result_event_t *) r;
+
+    return ev->event;
+}
+
+mql_result_t *mql_result_event_get_changed_rows(mql_result_t *r)
+{
+    result_event_t *ev;
+    result_event_rowchg_t *rowchg_ev;
+
+    if (!r || r->type != mql_result_event)
+        return NULL;
+
+    ev = (result_event_t *) r;
+
+    if (ev->event != mqi_row_deleted && ev->event != mqi_row_inserted)
+        return NULL;
+
+    rowchg_ev = (result_event_rowchg_t *) ev;
+
+    return rowchg_ev->select;
+}
+
+mql_result_t *mql_result_event_column_change_create(mqi_handle_t        table,
+                                                    int                 column,
+                                                    mqi_change_value_t *value,
+                                                    mql_result_t       *select)
+{
+    result_event_colchg_t *rslt;
+    int                    osiz;
+    int                    nsiz;
+    int                    poollen;
+    void                  *poolptr;
+    size_t                 size;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 column >= 0 && column < MQI_COLUMN_MAX && value &&
+                 (!select || (select && select->type == mql_result_rows)),
+                 NULL);
+
+    switch (value->type) {
+
+    case mqi_varchar:
+        osiz = strlen(value->old.varchar) + 1;
+        nsiz = strlen(value->new_.varchar) + 1;
+        break;
+
+    case mqi_blob:
+        if ((osiz = nsiz = mqi_get_column_size(table, column)) < 0)
+            return NULL;
+        break;
+
+    default:
+        osiz = nsiz = 0;
+        break;
+    }
+
+    poollen = osiz + nsiz;
+    size = sizeof(result_event_colchg_t) + poollen;
+
+    if (!(rslt = calloc(1, size))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    poolptr = (void *)rslt->data;
+
+    rslt->type   = mql_result_event;
+    rslt->event  = mqi_column_changed;
+    rslt->table  = table;
+    rslt->column = column;
+    rslt->value  = *value;
+    rslt->select = select;
+
+    if (osiz > 0) {
+        rslt->value.old.generic = poolptr;
+        memcpy(poolptr, value->old.generic, osiz);
+        poolptr += osiz;
+    }
+
+    if (nsiz > 0) {
+        rslt->value.new_.generic = poolptr;
+        memcpy(poolptr, value->new_.generic, nsiz);
+        poolptr += nsiz;
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+
+mql_result_t *mql_result_event_row_change_create(mqi_event_type_t    event,
+                                                 mqi_handle_t        table,
+                                                 mql_result_t       *select)
+{
+    result_event_rowchg_t *rslt;
+
+    MDB_CHECKARG((event == mqi_row_inserted || event == mqi_row_deleted) &&
+                 table != MQI_HANDLE_INVALID &&
+                 select && select->type == mql_result_rows, NULL);
+
+    if (!(rslt = calloc(1, sizeof(result_event_rowchg_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type   = mql_result_event;
+    rslt->event  = event;
+    rslt->table  = table;
+    rslt->select = select;
+
+    return (mql_result_t *)rslt;
+}
+
+
+mql_result_t *mql_result_event_table_create(mqi_event_type_t  event,
+                                            mqi_handle_t      table)
+{
+    result_event_table_t *rslt;
+
+    MDB_CHECKARG((event == mqi_table_created || event == mqi_table_dropped) &&
+                 table != MQI_HANDLE_INVALID, NULL);
+
+    if (!(rslt = calloc(1, sizeof(result_event_table_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type   = mql_result_event;
+    rslt->event  = event;
+    rslt->table  = table;
+
+    return (mql_result_t *)rslt;
+}
+
+
+mql_result_t *mql_result_event_transaction_create(mqi_event_type_t event)
+{
+    result_event_transact_t *rslt;
+
+    MDB_CHECKARG(event == mqi_transaction_start ||
+                 event == mqi_transaction_end, NULL);
+
+    if (!(rslt = calloc(1, sizeof(result_event_transact_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type  = mql_result_event;
+    rslt->event = event;
+
+    return (mql_result_t *)rslt;
+}
+
+
+
+mql_result_t *mql_result_columns_create(int ncol, mqi_column_def_t *defs)
+{
+    result_columns_t *rslt;
+    mqi_column_def_t *col;
+    size_t            poollen;
+    size_t            dlgh;
+    int               namlen[MQI_COLUMN_MAX];
+    char             *strpool;
+    int               i;
+
+    MDB_CHECKARG(ncol > 0 && ncol < MQI_COLUMN_MAX && defs, NULL);
+
+    for (poollen = 0, i = 0;  i < ncol;  i++)
+        poollen += (namlen[i] = strlen(defs[i].name) + 1);
+
+    dlgh = sizeof(mqi_column_def_t) * ncol;
+
+    if (!(rslt = calloc(1, sizeof(result_columns_t) + dlgh + poollen))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_columns;
+    rslt->ncol = ncol;
+
+    memcpy(rslt->cols, defs, dlgh);
+
+    strpool = (char *)(rslt->cols + ncol);
+
+    for (i = 0;    i < ncol;    i++) {
+        col = rslt->cols + i;
+        col->name = memcpy(strpool, col->name, namlen[i]);
+        strpool += namlen[i];
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_columns_get_column_count(mql_result_t *r)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns, -1);
+
+    return rslt->ncol;
+}
+
+const char *mql_result_columns_get_name(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, NULL);
+
+    return rslt->cols[colidx].name;
+}
+
+mqi_data_type_t mql_result_columns_get_type(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, -1);
+
+    return rslt->cols[colidx].type;
+}
+
+int mql_result_columns_get_length(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, -1);
+
+    return rslt->cols[colidx].length;
+}
+
+
+uint32_t mql_result_columns_get_flags(mql_result_t *r, int colidx)
+{
+    result_columns_t *rslt = (result_columns_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_columns &&
+                 colidx >= 0 && colidx < rslt->ncol, -1);
+
+    return rslt->cols[colidx].flags;
+}
+
+
+mql_result_t *mql_result_rows_create(int                ncol,
+                                     mqi_column_desc_t *coldescs,
+                                     mqi_data_type_t   *coltypes,
+                                     int               *colsizes,
+                                     int                nrow,
+                                     int                rowsize,
+                                     void              *rows)
+{
+    result_rows_t     *rslt;
+    column_desc_t     *col;
+    mqi_column_desc_t *cd;
+    int                offs;
+    size_t             size;
+    size_t             dlgh;
+    int                i;
+
+    MDB_CHECKARG(ncol >  0 && coldescs && coltypes && colsizes &&
+                 nrow >= 0 && rowsize > 0 && rows, NULL);
+
+    offs = sizeof(column_desc_t) * ncol;
+    dlgh = rowsize * nrow;
+    size = sizeof(result_rows_t) + offs + dlgh;
+
+
+    if (!(rslt = calloc(1, size))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type    = mql_result_rows;
+    rslt->rowsize = rowsize;
+    rslt->ncol    = ncol;
+    rslt->nrow    = nrow;
+    rslt->data    = rslt->cols + ncol;
+
+    for (i = 0;   i < ncol;  i++) {
+        col = rslt->cols + i;
+        cd  = coldescs + i;
+
+        col->cindex = cd->cindex;
+        col->type   = coltypes[i];
+        col->offset = cd->offset;
+    }
+
+    if (dlgh > 0)
+        memcpy(rslt->data, rows, dlgh);
+
+    return (mql_result_t *)rslt;
+}
+
+
+int mql_result_rows_get_row_column_count(mql_result_t *r)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows, -1);
+
+    return rslt->ncol;
+}
+
+mqi_data_type_t mql_result_rows_get_row_column_type(mql_result_t *r, int colidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 rslt->ncol > colidx, -1);
+
+    return rslt->cols[colidx].type;
+}
+
+int mql_result_rows_get_row_column_index(mql_result_t *r, int colidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 rslt->ncol > colidx, -1);
+
+    return rslt->cols[colidx].cindex;
+}
+
+int mql_result_rows_get_row_count(mql_result_t *r)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows, -1);
+
+    return rslt->nrow;
+}
+
+const char *mql_result_rows_get_string(mql_result_t *r, int colidx, int rowidx,
+                                       char *buf, int len)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    char *v = NULL;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow &&
+                 (!buf || (buf && len > 0)), NULL);
+
+    if ((v = buf))
+        *v = '\0';
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:
+            if (!v)
+                v = *(char **)addr;
+            else {
+                strncpy(v, *(char **)addr, len);
+                v[len-1] = '\0';
+            }
+            break;
+
+        case mqi_integer:
+            if (!v)
+                v = "";
+            else
+                snprintf(v, len, "%d", *(int32_t *)addr);
+            break;
+
+        case mqi_unsignd:
+            if (!v)
+                v = "";
+            else
+                snprintf(v, len, "%u", *(uint32_t *)addr);
+            break;
+
+        case mqi_floating:
+            if (!v)
+                v = "";
+            else
+                snprintf(v, len, "%lf", *(double *)addr);
+            break;
+
+        default:
+            v = "";
+            break;
+        }
+    }
+
+    return v;
+}
+
+int32_t mql_result_rows_get_integer(mql_result_t *r, int colidx, int rowidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    int32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow, 0);
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:    v = strtol(*(char **)addr, NULL, 10);   break;
+        case mqi_integer:    v = *(int32_t *)addr;                   break;
+        case mqi_unsignd:    v = *(uint32_t *)addr;                  break;
+        case mqi_floating:   v = *(double *)addr;                    break;
+        default:             v = 0;                                  break;
+        }
+    }
+
+    return v;
+}
+
+uint32_t mql_result_rows_get_unsigned(mql_result_t *r, int colidx, int rowidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    uint32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow, 0);
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:    v = strtoul(*(char **)addr, NULL, 10);  break;
+        case mqi_integer:    v = *(int32_t *)addr;                   break;
+        case mqi_unsignd:    v = *(uint32_t *)addr;                  break;
+        case mqi_floating:   v = *(double *)addr;                    break;
+        default:             v = 0;                                  break;
+        }
+    }
+
+    return v;
+}
+
+double mql_result_rows_get_floating(mql_result_t *r, int colidx, int rowidx)
+{
+    result_rows_t *rslt = (result_rows_t *)r;
+    void *addr;
+    double v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_rows &&
+                 colidx >= 0 && colidx < rslt->ncol &&
+                 rowidx >= 0 && rowidx < rslt->nrow, 0.0);
+
+    if ((addr = get_column_address(rslt, colidx, rowidx))) {
+        switch (get_column_type(rslt, colidx)) {
+        case mqi_varchar:    v = strtod(*(char **)addr, NULL);    break;
+        case mqi_integer:    v = *(int32_t *)addr;                break;
+        case mqi_unsignd:    v = *(uint32_t *)addr;               break;
+        case mqi_floating:   v = *(double *)addr;                 break;
+        default:             v = 0;                               break;
+        }
+    }
+
+    return v;
+}
+
+
+mql_result_t *mql_result_string_create_table_list(int n, char **names)
+{
+    static const char *no_tables = "no tables\n";
+    result_string_t *rslt;
+    int    nlgh[4096];
+    char  *name;
+    char   first_letter, upper;
+    int    len;
+    char  *p;
+    int    i;
+
+    MDB_CHECKARG(n >= 0 && n < (int)MQI_DIMENSION(nlgh) && names, NULL);
+
+    len = 1;  /* zero terminator */
+
+    if (!n)
+        len += strlen(no_tables) + 1;
+    else {
+        for (i = 0, first_letter = 0;    i < n;    i++) {
+            name  = names[i];
+            upper = toupper(name[0]);
+
+            if (upper != first_letter) {
+                first_letter = upper;
+                len += 3; /* \n[A-Z]: */
+            }
+
+            len += (nlgh[i] = strlen(name)) + 1;
+        }
+    }
+
+    if (!(rslt = calloc(1, sizeof(result_string_t) + len))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_string;
+    rslt->length = len;
+
+    if (!n)
+        strcpy(rslt->string, no_tables);
+    else {
+        for (p = rslt->string, first_letter = 0, i = 0;     i < n;     i++) {
+            name  = names[i];
+            len   = nlgh[i];
+            upper = toupper(name[0]);
+
+            if (upper != first_letter) {
+                if (first_letter)
+                    *p++ = '\n';
+
+                first_letter = upper;
+
+                *p++ = upper;
+                *p++ = ':';
+            }
+
+            *p++ = ' ';
+
+            memcpy(p, name, len);
+            p += len;
+        }
+
+        *p++ = '\n';
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+mql_result_t *mql_result_string_create_column_change(const char         *table,
+                                                     const char         *col,
+                                                     mqi_change_value_t *value,
+                                                     mql_result_t       *rsel)
+{
+#define EVENT   0
+#define TABLE   1
+#define COLUMN  2
+#define VALUE   3
+#define FLDS    4
+
+    static const char *hstr[FLDS]  = {" event", "table" , "column", "change"};
+    static int         hlen[FLDS]  = {    6   ,    5    ,     6   ,     6   };
+
+
+    result_string_t *rs = (result_string_t *)rsel;
+    result_string_t *rslt;
+
+    char             buf[1024];
+    int              l;
+    int              len[FLDS];
+    const char      *cstr[FLDS];
+    int              cw[FLDS];
+    int              linlen;
+    size_t           esiz;
+    size_t           ssiz;
+    size_t           size;
+    char            *p;
+    int              i;
+
+    MDB_CHECKARG(table && col && value &&
+                 (!rsel || (rsel && rsel->type == mql_result_string)), NULL);
+
+    cstr[EVENT]  = "'column changed'";
+    cstr[TABLE]  = table;
+    cstr[COLUMN] = col;
+    cstr[VALUE]  = buf;
+
+    for (i = 0;  i < VALUE;  i++)
+        len[i] = strlen(cstr[i]);
+
+#define PRINT_VALUE(fmt,t) \
+    snprintf(buf, sizeof(buf), fmt " => " fmt, value->old.t, value->new_.t)
+#define PRINT_UNKNOWN \
+    snprintf(buf, sizeof(buf), "<unknown> => <unknown>");
+
+    switch (value->type) {
+    case mqi_varchar:    l = PRINT_VALUE("'%s'" , varchar );    break;
+    case mqi_integer:    l = PRINT_VALUE("%d"   , integer );    break;
+    case mqi_unsignd:    l = PRINT_VALUE("%u"   , unsignd );    break;
+    case mqi_floating:   l = PRINT_VALUE("%.2lf", floating);    break;
+    default:             l = PRINT_UNKNOWN;                     break;
+    }
+
+#undef PRINT
+
+    len[VALUE] = l;
+
+
+    for (linlen = (FLDS-1) * 2 + 1,  i = 0;   i < FLDS;   i++)
+        linlen += (cw[i] = len[i] > hlen[i] ?  len[i] : hlen[i]);
+
+    esiz = linlen * 3;
+    ssiz = rs ? rs->length : 0;
+    size = esiz + ssiz + 2;
+
+    if (!(rslt = calloc(1, sizeof(result_string_t) + size))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    p = rslt->string;
+
+    for (i = 0;  i < FLDS;  i++)
+        p += sprintf(p, "%-*s%s", cw[i],  hstr[i], i == FLDS-1 ? "\n" : "  ");
+
+    memset(p, '-', linlen-1);
+    p[linlen-1] = '\n';
+    p += linlen;
+
+    for (i = 0;  i < FLDS;  i++)
+        p += sprintf(p, "%-*s%s", cw[i], cstr[i], i == FLDS-1 ? "\n" : "  ");
+
+
+    if (ssiz > 0) {
+        *p++ = '\n';
+        memcpy(p, rs->string, ssiz);
+    }
+
+    rslt->type = mql_result_string;
+    rslt->length = p - rslt->string;
+
+    return (mql_result_t *)rslt;
+
+#undef FLDS
+#undef VALUE
+#undef COLUMN
+#undef TABLE
+#undef EVENT
+}
+
+mql_result_t *mql_result_string_create_row_change(mqi_event_type_t    event,
+                                                  const char         *table,
+                                                  mql_result_t       *rsel)
+{
+#define EVENT   0
+#define TABLE   1
+#define FLDS    2
+
+    static const char *hstr[FLDS]  = {" event", "table"};
+    static int         hlen[FLDS]  = {    6   ,    5   };
+
+
+    result_string_t *rs = (result_string_t *)rsel;
+    result_string_t *rslt;
+
+    int              len[FLDS];
+    const char      *cstr[FLDS];
+    int              cw[FLDS];
+    int              linlen;
+    size_t           esiz;
+    size_t           ssiz;
+    size_t           size;
+    char            *p;
+    int              i;
+
+    MDB_CHECKARG((event == mqi_row_inserted || event == mqi_row_deleted) &&
+                 table && rsel && rsel->type == mql_result_string, NULL);
+
+    cstr[EVENT] = (event==mqi_row_inserted) ? "'row inserted'":"'row deleted'";
+    cstr[TABLE] = table;
+
+    for (i = 0;  i < FLDS;  i++)
+        len[i] = strlen(cstr[i]);
+
+    for (linlen = (FLDS-1) * 2 + 1,  i = 0;   i < FLDS;   i++)
+        linlen += (cw[i] = len[i] > hlen[i] ?  len[i] : hlen[i]);
+
+    esiz = linlen * 3;
+    ssiz = rs ? rs->length : 0;
+    size = esiz + ssiz + 2;
+
+    if (!(rslt = calloc(1, sizeof(result_string_t) + size))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    p = rslt->string;
+
+    for (i = 0;  i < FLDS;  i++)
+        p += sprintf(p, "%-*s%s", cw[i],  hstr[i], i == FLDS-1 ? "\n" : "  ");
+
+    memset(p, '-', linlen-1);
+    p[linlen-1] = '\n';
+    p += linlen;
+
+    for (i = 0;  i < FLDS;  i++)
+        p += sprintf(p, "%-*s%s", cw[i], cstr[i], i == FLDS-1 ? "\n" : "  ");
+
+    *p++ = '\n';
+    memcpy(p, rs->string, ssiz);
+
+    rslt->type = mql_result_string;
+    rslt->length = p - rslt->string;
+
+    return (mql_result_t *)rslt;
+
+#undef FLDS
+#undef TABLE
+#undef EVENT
+}
+
+mql_result_t *mql_result_string_create_table_change(mqi_event_type_t    event,
+                                                    const char         *table)
+{
+#define EVENT   0
+#define TABLE   1
+#define FLDS    2
+
+    static const char *hstr[FLDS]  = {" event", "table"};
+    static int         hlen[FLDS]  = {    6   ,    5   };
+
+    result_string_t *rslt;
+
+    int              len[FLDS];
+    const char      *cstr[FLDS];
+    int              cw[FLDS];
+    int              linlen;
+    char            *p;
+    int              i;
+
+    MDB_CHECKARG((event == mqi_table_created || event == mqi_table_dropped) &&
+                 table, NULL);
+
+    cstr[EVENT] = (event == mqi_table_created) ?
+                  "'table created'" : "'table dropped'";
+    cstr[TABLE] = table;
+
+    for (i = 0;  i < FLDS;  i++)
+        len[i] = strlen(cstr[i]);
+
+    for (linlen = (FLDS-1) * 2 + 1,  i = 0;   i < FLDS;   i++)
+        linlen += (cw[i] = len[i] > hlen[i] ?  len[i] : hlen[i]);
+
+
+    if (!(rslt = calloc(1, sizeof(result_string_t) + (linlen*3 + 1)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    p = rslt->string;
+
+    for (i = 0;  i < FLDS;  i++)
+        p += sprintf(p, "%-*s%s", cw[i],  hstr[i], i == FLDS-1 ? "\n" : "  ");
+
+    memset(p, '-', linlen-1);
+    p[linlen-1] = '\n';
+    p += linlen;
+
+    for (i = 0;  i < FLDS;  i++)
+        p += sprintf(p, "%-*s%s", cw[i], cstr[i], i == FLDS-1 ? "\n" : "  ");
+
+    rslt->type = mql_result_string;
+    rslt->length = p - rslt->string;
+
+    return (mql_result_t *)rslt;
+
+#undef FLDS
+#undef TABLE
+#undef EVENT
+}
+
+mql_result_t *mql_result_string_create_transaction_change(mqi_event_type_t evt)
+{
+    static const char *hstr  = " event";
+    static int         hlen  = 6;
+
+    result_string_t *rslt;
+
+    int         len;
+    const char *cstr;
+    int         cw;
+    int         linlen;
+    char       *p;
+
+    MDB_CHECKARG(evt == mqi_transaction_start || evt == mqi_transaction_end,
+                 NULL);
+
+    cstr   = (evt == mqi_transaction_start) ?
+             "'transaction started'" : "'transaction ended'";
+    len    = strlen(cstr);
+    cw     = len > hlen ? len : hlen;
+    linlen = cw + 1;
+
+    if (!(rslt = calloc(1, sizeof(result_string_t) + (linlen*3 + 1)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    p  = rslt->string;
+    p += sprintf(p, "%-*s\n", cw,  hstr);
+
+    memset(p, '-', cw);
+    p[cw] = '\n';
+    p += linlen;
+
+    p += sprintf(p, "%-*s", cw, cstr);
+
+    rslt->type = mql_result_string;
+    rslt->length = p - rslt->string;
+
+    return (mql_result_t *)rslt;
+}
+
+mql_result_t *mql_result_string_create_column_list(int               ncol,
+                                                   mqi_column_def_t *defs)
+{
+#define INDEX   0
+#define NAME    1
+#define TYPE    2
+#define LENGTH  3
+#define FLDS    4
+
+    static const char *hstr[FLDS]  = {"index", " name" , "type", "length"};
+    static int         hlen[FLDS]  = {   5   ,     5   ,    4  ,     6   };
+    static int         align[FLDS] = {  +1   ,    -1   ,   -1  ,    +1   };
+
+    result_string_t  *rslt;
+    mqi_column_def_t *def;
+    const char       *typstr[MQI_COLUMN_MAX];
+    int               namlen[MQI_COLUMN_MAX];
+    int               typlen[MQI_COLUMN_MAX];
+    int               fldlen[FLDS];
+    int               linlen;
+    int               len;
+    int               offs;
+    char             *p, *q;
+    int               i, j;
+
+    MDB_CHECKARG(ncol > 0 && ncol < MQI_COLUMN_MAX && defs, NULL);
+
+    memcpy(fldlen, hlen, sizeof(fldlen));
+
+    for (i = 0;  i < ncol;  i++) {
+        def = defs + i;
+
+        typstr[i] = mqi_data_type_str(def->type);
+
+        if ((len = (namlen[i] = strlen(def->name)) + 1) > fldlen[NAME])
+            fldlen[NAME] = len;
+
+        if ((len = (typlen[i] = strlen(typstr[i]))) > fldlen[TYPE])
+            fldlen[TYPE] = len;
+    }
+
+    for (linlen = FLDS, i = 0;  i < FLDS;  linlen += fldlen[i++])
+        ;
+
+    len = linlen * (ncol+2) + 1;
+
+    /*
+     * allocate and initialize the result structure
+     */
+    if (!(rslt = calloc(1, sizeof(result_string_t) + len))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type = mql_result_string;
+    rslt->length = len;
+
+    p = rslt->string;
+    memset(p, ' ', linlen * (ncol+2));
+
+    /*
+     * labels
+     */
+    for (q = p, j = 0;   j < FLDS;   q += (fldlen[j++] + 1)) {
+        offs = (align[j] < 0) ? 0 : fldlen[j] - hlen[j];
+        memcpy(q + offs, hstr[j], hlen[j]);
+    }
+
+    p += linlen;
+    *(p-1) = '\n';
+
+    /*
+     * separator line
+     */
+    memset(p, '-', linlen);
+    p += linlen;
+    *(p-1) = '\n';
+
+
+    /*
+     * data lines
+     */
+    for (i = 0;  i < ncol;  i++, p += linlen) {
+        def = defs + i;
+        snprintf(p, linlen+1, "%*d %s%-*s %-*s %*d\n",
+                 fldlen[INDEX], i,
+                 def->flags&MQI_COLUMN_KEY ? "*":" ", fldlen[NAME]-1,def->name,
+                 fldlen[TYPE], typstr[i],
+                 fldlen[LENGTH], def->length);
+    }
+
+    return (mql_result_t *)rslt;
+
+#undef FLDS
+#undef LENGTH
+#undef TYPE
+#undef NAME
+#undef INDEX
+}
+
+mql_result_t *mql_result_string_create_row_list(int                 ncol,
+                                                char              **colnams,
+                                                mqi_column_desc_t  *coldescs,
+                                                mqi_data_type_t    *coltypes,
+                                                int                *colsizes,
+                                                int                 nrow,
+                                                int                 rowsize,
+                                                void               *rows)
+{
+    static const char *no_rows = "no rows\n";
+
+    result_string_t *rslt;
+    size_t  len;
+    int     rwidth;
+    int     cwidth;
+    int     cwidths[MQI_COLUMN_MAX + 1];
+    void   *row;
+    void   *column;
+    int     i,j;
+    char   *p;
+
+
+    MDB_CHECKARG(ncol >  0 && coldescs && coltypes && colsizes &&
+                 nrow >= 0 && rowsize > 0 && rows, NULL);
+
+    /*
+     * calculate column widths and row width
+     */
+    rwidth = ncol;  /* for each column a separating space or a \n at the end */
+
+    for (i = 0;   i < ncol;   i++) {
+        switch (coltypes[i]) {
+        case mqi_varchar:   cwidth = colsizes[i] - 1;  break;
+        case mqi_integer:   cwidth = 11;               break;
+        case mqi_unsignd:   cwidth = 10;               break;
+        case mqi_floating:  cwidth = 10;               break;
+        default:            cwidth = 0;                break;
+        }
+
+        rwidth += (cwidths[i] = cwidth);
+    }
+
+    if (!nrow)
+        len = rwidth * 2 + strlen(no_rows) + 1;
+    else
+        len = rwidth * (nrow + 2) + 1;
+
+    /*
+     * setup the result structure
+     */
+    if (!(rslt = calloc(1, sizeof(result_string_t) + len))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    rslt->type   = mql_result_string;
+    rslt->length = len;
+
+    p = rslt->string;
+
+    /*
+     * labels
+     */
+    for (i = 0;   i < ncol;   i++) {
+        if ((cwidth = cwidths[i])) {
+            if (cwidth <= (int)(len = strlen(colnams[i]))) {
+                /* truncate */
+                memcpy(p, colnams[i], cwidth);
+                p[cwidth] = ' ';
+            }
+            else {
+                if (coltypes[i] == mqi_varchar) {
+                    /* left align */
+                    memcpy(p, colnams[i], len);
+                    memset(p + len, ' ', (cwidth + 1) - len);
+                }
+                else {
+                    /* right align */
+                    memset(p, ' ', cwidth - len);
+                    memcpy(p + (cwidth - len), colnams[i], len);
+                    p[cwidth] = ' ';
+                }
+            }
+            p += cwidth + 1;
+        }
+    } /* for */
+
+    *(p-1) = '\n';
+
+    /*
+     * separator line
+     */
+    memset(p, '-', rwidth-1);
+    p[rwidth-1] = '\n';
+    p += rwidth;
+
+    /*
+     * data lines
+     */
+#define SPRINT(t,f) snprintf(p, cwidth+2, f " ", cwidth, *(t *)column)
+
+    if (!nrow)
+        strcpy(p, no_rows);
+    else {
+        for (i = 0, row = rows;  i < nrow;  i++, row += rowsize) {
+            for (j = 0;  j < ncol; j++) {
+                column = row + coldescs[j].offset;
+                cwidth = cwidths[j];
+
+                switch (coltypes[j]) {
+                case mqi_varchar:     SPRINT( char *  , "%-*s"   );     break;
+                case mqi_integer:     SPRINT( int32_t , "%*d"    );     break;
+                case mqi_unsignd:     SPRINT( uint32_t, "%*u"    );     break;
+                case mqi_floating:    SPRINT( double  , "%*.2lf" );     break;
+                default:              memset(p, ' ', cwidth+1    );     break;
+                }
+
+                p += cwidth+1;
+            }
+
+            *(p-1) = '\n';
+        }
+        *p = '\0';
+    }
+
+#undef SPRINT
+
+    return (mql_result_t *)rslt;
+}
+
+const char *mql_result_string_get(mql_result_t *r)
+{
+    result_string_t *rslt = (result_string_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_string, "");
+
+    return rslt->string;
+}
+
+
+mql_result_t *mql_result_list_create(mqi_data_type_t type,
+                                     int             length,
+                                     void           *values)
+{
+    result_list_t *rslt;
+    size_t   datalen;
+    char   **strs;
+    int     *slen = NULL;
+    char    *strpool;
+    int      poollen = 0;
+    int      i;
+
+    MDB_CHECKARG(length > 0 && values, NULL);
+
+    switch (type) {
+    case mqi_varchar:
+        slen = alloca(sizeof(int) * length);
+        for (strs = (char **)values, i = 0;  i < length;  i++)
+            poollen += (slen[i] = strlen(strs[i]) + 1);
+        datalen = sizeof(char *) * length + poollen;
+        break;
+    case mqi_integer:
+        datalen = sizeof(int32_t) * length;
+        break;
+    case mqi_unsignd:
+        datalen = sizeof(uint32_t) * length;
+        break;
+    case mqi_floating:
+        datalen = sizeof(double) * length;
+        break;
+    default:
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if ((rslt = calloc(1, sizeof(result_list_t) + datalen))) {
+        rslt->type   = mql_result_list;
+        rslt->length = length;
+        rslt->value.type = type;
+
+        if (type != mqi_varchar)
+            memcpy(rslt->value.v.generic, values, datalen);
+        else {
+            strs = (char **)values;
+            strpool = (char *)&rslt->value.v.varchar[length];
+            for (i = 0;  i < length;  i++) {
+                rslt->value.v.varchar[i] = memcpy(strpool, strs[i], slen[i]);
+                strpool += slen[i];
+            }
+        }
+    }
+
+    return (mql_result_t *)rslt;
+}
+
+int mql_result_list_get_length(mql_result_t *r)
+{
+    result_list_t *rslt = (result_list_t *)r;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list, -1);
+
+    return rslt->length;
+}
+
+const char *mql_result_list_get_string(mql_result_t *r, int idx,
+                                       char *buf, int len)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    char *v = NULL;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, NULL);
+
+    if ((v = buf))
+        *v = '\0';
+
+    switch (rslt->value.type) {
+
+    case mqi_varchar:
+        if (!v)
+            v = rslt->value.v.varchar[idx];
+        else {
+            strncpy(v, rslt->value.v.varchar[idx], len);
+            v[len-1] = '\0';
+        }
+        break;
+
+    case mqi_integer:
+        if (!v)
+            v = "";
+        else
+            snprintf(v, len, "%d", rslt->value.v.integer[idx]);
+        break;
+
+    case mqi_unsignd:
+        if (!v)
+            v = "";
+        else
+            snprintf(v, len, "%u", rslt->value.v.unsignd[idx]);
+        break;
+
+    case mqi_floating:
+        if (!v)
+            v = "";
+        else
+            snprintf(v, len, "%lf", rslt->value.v.floating[idx]);
+        break;
+
+    default:
+        v = "";
+        break;
+    }
+
+    return v;
+}
+
+int32_t mql_result_list_get_integer(mql_result_t *r, int idx)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    int32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, 0);
+
+    switch (rslt->value.type) {
+    case mqi_varchar:  v = strtol(rslt->value.v.varchar[idx], NULL, 10); break;
+    case mqi_integer:  v = rslt->value.v.integer[idx];                   break;
+    case mqi_unsignd:  v = rslt->value.v.unsignd[idx];                   break;
+    case mqi_floating: v = rslt->value.v.floating[idx];                  break;
+    default:           v = 0;                                            break;
+    }
+
+    return v;
+}
+
+int32_t mql_result_list_get_unsigned(mql_result_t *r, int idx)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    uint32_t v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, 0);
+
+    switch (rslt->value.type) {
+    case mqi_varchar:  v = strtoul(rslt->value.v.varchar[idx], NULL, 10);break;
+    case mqi_integer:  v = rslt->value.v.integer[idx];                   break;
+    case mqi_unsignd:  v = rslt->value.v.unsignd[idx];                   break;
+    case mqi_floating: v = rslt->value.v.floating[idx];                  break;
+    default:           v = 0;                                            break;
+    }
+
+    return v;
+}
+
+double mql_result_list_get_floating(mql_result_t *r, int idx)
+{
+    result_list_t *rslt = (result_list_t *)r;
+    double v = 0;
+
+    MDB_CHECKARG(rslt && rslt->type == mql_result_list &&
+                 idx >= 0 && idx < rslt->length, 0);
+
+    switch (rslt->value.type) {
+    case mqi_varchar:   v = strtod(rslt->value.v.varchar[idx], NULL);  break;
+    case mqi_integer:   v = rslt->value.v.integer[idx];                break;
+    case mqi_unsignd:   v = rslt->value.v.unsignd[idx];                break;
+    case mqi_floating:  v = rslt->value.v.floating[idx];               break;
+    default:            v = 0;                                         break;
+    }
+
+    return v;
+}
+
+void mql_result_free(mql_result_t *r)
+{
+    result_event_colchg_t *colchg = (result_event_colchg_t *)r;
+    mql_result_t          *select;
+
+    if (r) {
+        if (r->type == mql_result_event) {
+            if (colchg->event == mqi_column_changed) {
+                select = colchg->select;
+
+                if (select && select->type == mql_result_rows)
+                    mql_result_free(colchg->select);
+            }
+        }
+
+        free(r);
+    }
+}
+
+
+static mqi_data_type_t get_column_type(result_rows_t *rslt, int cx)
+{
+    return rslt->cols[cx].type;
+}
+
+static void *get_column_address(result_rows_t *rslt, int cx, int rx)
+{
+    return rslt->data + (rslt->rowsize * rx + rslt->cols[cx].offset);
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mql/statement.c b/src/murphy-db/mql/statement.c
new file mode 100644 (file)
index 0000000..d7533ec
--- /dev/null
@@ -0,0 +1,1026 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include <errno.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mql.h>
+#include "mql-parser.h"
+
+typedef struct {
+    mqi_data_type_t  type;
+    union {
+        const char  *varchar;
+        int32_t      integer;
+        uint32_t     unsignd;
+        double       floating;
+        void        *generic;
+    } v;
+} value_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    uint32_t             flags;
+} shtable_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+} describe_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    char                 trnam[0];
+} transact_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+    int                  ignore;
+    int                  ncolumn;
+    mqi_column_desc_t   *columns;
+    void                *rows[2];
+    int                  nbind;
+    value_t              values[0];
+} insert_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+    mqi_column_desc_t   *columns;
+    mqi_cond_entry_t    *cond;
+    int                  nbind;
+    value_t              values[0];
+} update_statement_t;
+
+typedef struct {
+    mql_statement_type_t  type;
+    mqi_handle_t          table;
+    mqi_cond_entry_t     *cond;
+    int                   nbind;
+    value_t               values[0];
+} delete_statement_t;
+
+typedef struct {
+    mql_statement_type_t type;
+    mqi_handle_t         table;
+    int                  rowsize;
+    int                  ncolumn;
+    mqi_column_desc_t   *columns;
+    char               **colnames;
+    mqi_data_type_t     *coltypes;
+    int                 *colsizes;
+    mqi_cond_entry_t    *cond;
+    int                  nbind;
+    value_t              values[0];
+} select_statement_t;
+
+
+
+static void count_condition_values(int, mqi_cond_entry_t *, int*, int*, int*);
+static void count_column_values(mqi_column_desc_t *, mqi_data_type_t*, void*,
+                                int *, int *, int *);
+static void copy_column_values(int, mqi_data_type_t *, mqi_column_desc_t *,
+                               mqi_column_desc_t *, value_t **, value_t **,
+                               char **, void *, void *);
+static void copy_conditions_and_values(int,mqi_cond_entry_t*,mqi_cond_entry_t*,
+                                       value_t**, value_t**, char**);
+
+static mql_result_t *exec_show_tables(mql_result_type_t, shtable_statement_t*);
+static mql_result_t *exec_describe(mql_result_type_t, describe_statement_t *);
+static mql_result_t *exec_begin(transact_statement_t *);
+static mql_result_t *exec_commit(transact_statement_t *);
+static mql_result_t *exec_rollback(transact_statement_t *);
+static mql_result_t *exec_insert(insert_statement_t *);
+static mql_result_t *exec_update(update_statement_t *);
+static mql_result_t *exec_delete(delete_statement_t *);
+static mql_result_t *exec_select(mql_result_type_t, select_statement_t *);
+
+static int bind_update_value(update_statement_t *,int,mqi_data_type_t,va_list);
+static int bind_delete_value(delete_statement_t *,int,mqi_data_type_t,va_list);
+static int bind_select_value(select_statement_t *,int,mqi_data_type_t,va_list);
+static int bind_value(value_t *, mqi_data_type_t, va_list);
+
+mql_statement_t *mql_make_show_tables_statement(uint32_t flags)
+{
+    shtable_statement_t *st;
+
+    if (!(st = calloc(1, sizeof(shtable_statement_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    st->type  = mql_statement_show_tables;
+    st->flags = flags;
+
+    return (mql_statement_t *)st;
+}
+
+mql_statement_t *mql_make_describe_statement(mqi_handle_t table)
+{
+    describe_statement_t *dis;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID, NULL);
+
+    if (!(dis = calloc(1, sizeof(describe_statement_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    dis->type  = mql_statement_describe;
+    dis->table = table;
+
+    return (mql_statement_t *)dis;
+}
+
+mql_statement_t *mql_make_transaction_statement(mql_statement_type_t  type,
+                                                char                 *trnam)
+{
+    transact_statement_t *tra;
+
+    MDB_CHECKARG((type == mql_statement_begin  ||
+                  type == mql_statement_commit ||
+                  type == mql_statement_rollback)
+                 && trnam && trnam[0], NULL);
+
+    if (!(tra = calloc(1, sizeof(transact_statement_t) + strlen(trnam) + 1))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    tra->type = type;
+    strcpy(tra->trnam, trnam);
+
+    return (mql_statement_t *)tra;
+}
+
+
+mql_statement_t *mql_make_insert_statement(mqi_handle_t       table,
+                                           int                ignore,
+                                           int                ncolumn,
+                                           mqi_data_type_t   *coltypes,
+                                           mqi_column_desc_t *columns,
+                                           void              *data)
+{
+    insert_statement_t *ins;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cdsclgh;
+    int      datalgh;
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 ncolumn > 0 && columns && data, NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_column_values(columns, coltypes, data, &nbind,&nconst,&poollen);
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cdsclgh = sizeof(mqi_column_desc_t) * (ncolumn + 1);
+
+    datalgh = vallgh + cdsclgh + poollen;
+
+    if (!(ins = calloc(1, sizeof(insert_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    ins->type    = mql_statement_insert;
+    ins->table   = table;
+    ins->ignore  = ignore;
+    ins->columns = (mqi_column_desc_t *)(ins->values + (nbind + nconst));
+    ins->rows[0] = ins->values;
+    ins->nbind   = nbind;
+
+    strpool = (void *)(ins->columns + (ncolumn + 1));
+
+    /*
+     * copy column values
+     */
+    bindv  = ins->values;
+    constv = bindv + nbind;
+
+    copy_column_values(ncolumn, coltypes, columns, ins->columns,
+                       &bindv, &constv, &strpool, data, ins->values);
+
+    return (mql_statement_t *)ins;
+}
+
+
+mql_statement_t *mql_make_update_statement(mqi_handle_t       table,
+                                           int                ncond,
+                                           mqi_cond_entry_t  *conds,
+                                           int                ncolumn,
+                                           mqi_data_type_t   *coltypes,
+                                           mqi_column_desc_t *columns,
+                                           void              *data)
+{
+    update_statement_t *upd;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cdsclgh;
+    int      cndlgh;
+    int      datalgh;
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 (!ncond || (ncond > 0 && conds)) &&
+                 ncolumn > 0 && coltypes && columns && data, NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_column_values(columns, coltypes, data, &nbind,&nconst,&poollen);
+    count_condition_values(ncond, conds, &nbind, &nconst, &poollen);
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cdsclgh = sizeof(mqi_column_desc_t) * (ncolumn + 1);
+    cndlgh  = sizeof(mqi_cond_entry_t ) *  ncond;
+
+    datalgh = vallgh + cdsclgh + cndlgh + poollen;
+
+    if (!(upd = calloc(1, sizeof(update_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    upd->type    = mql_statement_update;
+    upd->table   = table;
+    upd->columns = (mqi_column_desc_t *)(upd->values + (nbind + nconst));
+    upd->cond    = (mqi_cond_entry_t *)(upd->columns + (ncolumn + 1));
+    upd->nbind   = nbind;
+
+    strpool = (void *)(upd->cond + ncond);
+
+    /*
+     * copy column values, conditions and their values
+     */
+    bindv  = upd->values;
+    constv = bindv + nbind;
+
+    copy_column_values(ncolumn, coltypes, columns, upd->columns,
+                       &bindv, &constv, &strpool, data, upd->values);
+
+    copy_conditions_and_values(ncond, conds, upd->cond,
+                               &bindv, &constv, &strpool);
+
+    return (mql_statement_t *)upd;
+}
+
+
+mql_statement_t *mql_make_delete_statement(mqi_handle_t       table,
+                                           int                ncond,
+                                           mqi_cond_entry_t  *conds)
+{
+    delete_statement_t *del;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cndlgh;
+    int      datalgh;
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 (!ncond || (ncond > 0 && conds)), NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_condition_values(ncond, conds, &nbind, &nconst, &poollen);
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cndlgh  = sizeof(mqi_cond_entry_t ) *  ncond;
+
+    datalgh = vallgh + cndlgh + poollen;
+
+    if (!(del = calloc(1, sizeof(delete_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    del->type  = mql_statement_delete;
+    del->table = table;
+    del->cond  = (mqi_cond_entry_t *)(del->values + (nbind + nconst));
+    del->nbind = nbind;
+
+    strpool = (void *)(del->cond + ncond);
+
+    /*
+     * copy column values, conditions and their values
+     */
+    bindv  = del->values;
+    constv = bindv + nbind;
+
+    copy_conditions_and_values(ncond, conds, del->cond,
+                               &bindv, &constv, &strpool);
+
+    return (mql_statement_t *)del;
+}
+
+mql_statement_t *mql_make_select_statement(mqi_handle_t       table,
+                                           int                rowsize,
+                                           int                ncond,
+                                           mqi_cond_entry_t  *conds,
+                                           int                ncolumn,
+                                           char             **colnames,
+                                           mqi_data_type_t   *coltypes,
+                                           int               *colsizes,
+                                           mqi_column_desc_t *columns)
+{
+    select_statement_t *sel;
+    value_t *bindv;
+    value_t *constv;
+    char    *strpool;
+    int      vallgh;
+    int      cdsclgh;
+    int      cnamlgh;
+    int      ctyplgh;
+    int      csizlgh;
+    int      cndlgh;
+    int      datalgh;
+    int      colnamlgh[MQI_COLUMN_MAX];
+    int      nbind   = 0;
+    int      nconst  = 0;
+    int      poollen = 0;
+    int      i;
+
+    MDB_CHECKARG(table != MQI_HANDLE_INVALID &&
+                 (!ncond || (ncond >= 0 && conds)) &&
+                 ncolumn > 0 && columns, NULL);
+
+    /*
+     * calculate the number of constant and bindable values
+     */
+    count_condition_values(ncond, conds, &nbind, &nconst, &poollen);
+
+    for (i = 0;   i < ncolumn;   i++)
+        poollen += (colnamlgh[i] = strlen(colnames[i]) + 1);
+
+
+    /*
+     * set up the statement structure
+     */
+    vallgh  = sizeof(     value_t     ) * (nbind + nconst);
+    cdsclgh = sizeof(mqi_column_desc_t) * (ncolumn + 1);
+    cnamlgh = sizeof(      char *     ) *  ncolumn;
+    ctyplgh = sizeof( mqi_data_type_t ) *  ncolumn;
+    csizlgh = sizeof(       int       ) *  ncolumn;
+    cndlgh  = sizeof(mqi_cond_entry_t ) *  ncond;
+
+    datalgh = vallgh + cdsclgh + cnamlgh + ctyplgh + csizlgh + cndlgh +poollen;
+
+    if (!(sel = calloc(1, sizeof(select_statement_t) + datalgh))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    sel->type     = mql_statement_select;
+    sel->table    = table;
+    sel->rowsize  = rowsize;
+    sel->ncolumn  = ncolumn;
+    sel->columns  = (mqi_column_desc_t *)(sel->values + (nbind + nconst));
+    sel->colnames = (char **)(sel->columns + (ncolumn+1));
+    sel->coltypes = (mqi_data_type_t *)(sel->colnames + ncolumn);
+    sel->colsizes = (int *)(sel->coltypes + ncolumn);
+    sel->cond     = (mqi_cond_entry_t *)(sel->colsizes + ncolumn);
+    sel->nbind    = nbind;
+
+    strpool = (char *)(sel->cond + ncond);
+
+    if (!ncond)
+        sel->cond = NULL;
+
+    /*
+     * copy conditions and values
+     */
+    bindv  = sel->values;
+    constv = bindv + nbind;
+
+    copy_conditions_and_values(ncond, conds, sel->cond,
+                               &bindv, &constv, &strpool);
+    /*
+     * copy column descriptors, types and sizes
+     */
+    memcpy(sel->columns,  columns,  cdsclgh);
+    memcpy(sel->coltypes, coltypes, ctyplgh);
+    memcpy(sel->colsizes, colsizes, csizlgh);
+
+    /*
+     * copy column names
+     */
+    for (i = 0;   i < ncolumn;   i++) {
+        sel->colnames[i] = (char*)memcpy(strpool, colnames[i], colnamlgh[i]);
+        strpool += colnamlgh[i];
+    }
+
+    return (mql_statement_t *)sel;
+}
+
+int
+mql_bind_value(mql_statement_t *s, int id, mqi_data_type_t type, ...)
+{
+    int idx = id - 1;
+    va_list data;
+    int sts;
+
+    MDB_CHECKARG(s && id > 0, -1);
+
+    va_start(data, type);
+
+    switch (s->type) {
+
+    case mql_statement_update:
+        sts = bind_update_value((update_statement_t *)s, idx, type, data);
+        break;
+
+    case mql_statement_delete:
+        sts = bind_delete_value((delete_statement_t *)s, idx, type, data);
+        break;
+
+    case mql_statement_select:
+        sts = bind_select_value((select_statement_t *)s, idx, type, data);
+        break;
+
+    default:
+        errno = EBADRQC;
+        sts   = -1;
+        break;
+    }
+
+    va_end(data);
+
+    return sts;
+}
+
+mql_result_t *mql_exec_statement(mql_result_type_t type, mql_statement_t *s)
+{
+    mql_result_t *result;
+
+    MDB_CHECKARG(s, NULL);
+
+    switch (s->type) {
+
+    case mql_statement_show_tables:
+        result = exec_show_tables(type, (shtable_statement_t *)s);
+        break;
+
+    case mql_statement_describe:
+        result = exec_describe(type, (describe_statement_t *)s);
+        break;
+
+    case mql_statement_begin:
+        result = exec_begin((transact_statement_t *)s);
+        break;
+
+    case mql_statement_commit:
+        result = exec_commit((transact_statement_t *)s);
+        break;
+
+    case mql_statement_rollback:
+        result = exec_rollback((transact_statement_t *)s);
+        break;
+
+    case mql_statement_insert:
+        result = exec_insert((insert_statement_t *)s);
+        break;
+
+    case mql_statement_update:
+        result = exec_update((update_statement_t *)s);
+        break;
+
+    case mql_statement_delete:
+        result = exec_delete((delete_statement_t *)s);
+        break;
+
+    case mql_statement_select:
+        result = exec_select(type, (select_statement_t *)s);
+        break;
+
+    default:
+        result = mql_result_error_create(EBADRQC, "statement execution failed:"
+                                         " %s", strerror(EBADRQC));
+        break;
+    }
+
+    return result;
+}
+
+
+void mql_statement_free(mql_statement_t *s)
+{
+    free(s);
+}
+
+
+static void count_column_values(mqi_column_desc_t *cds,
+                                mqi_data_type_t   *coltypes,
+                                void              *data,
+                                int               *nbind_ret,
+                                int               *nconst_ret,
+                                int               *poollen_ret)
+{
+    mqi_column_desc_t *cd;
+    int   offs;
+    int   bidx;
+    char *str;
+    int   nbind   = *nbind_ret;
+    int   nconst  = 0;
+    int   poollen = 0;
+    int   i;
+
+    for (i = 0;   (cd = cds + i)->cindex >= 0;   i++) {
+
+        if ((offs = cd->offset) >= 0) {
+            if (coltypes[i] == mqi_varchar && (str = *(char**)(data + offs)))
+                poollen += strlen(str) + 1;
+            nconst++;
+        }
+        else {
+            if ((bidx = -(cd->offset - 1)) + 1 > nbind)
+                nbind = bidx + 1;
+        }
+    }
+
+    *nbind_ret    = nbind;
+    *nconst_ret  += nconst;
+    *poollen_ret += poollen;
+}
+
+static void count_condition_values(int               ncond,
+                                   mqi_cond_entry_t *conds,
+                                   int              *nbind_ret,
+                                   int              *nconst_ret,
+                                   int              *poollen_ret)
+{
+    mqi_cond_entry_t *ce;
+    mqi_variable_t   *var;
+    uint32_t flags;
+    int bidx;
+    char *str;
+    int nbind   = *nbind_ret;
+    int nconst  = 0;
+    int poollen = 0;
+    int i;
+
+    for (i = 0;    i < ncond;    i++) {
+        ce = conds + i;
+
+        if (ce->type == mqi_variable) {
+            var   = &ce->u.variable;
+            flags = var->flags;
+
+            if (!(flags & MQL_BINDABLE)) {
+                if (var->type == mqi_varchar && (str = *(var->v.varchar)))
+                    poollen += strlen(str) + 1;
+                nconst++;
+            }
+            else {
+                if ((bidx = MQL_BIND_INDEX(flags)) + 1 > nbind)
+                    nbind = bidx + 1;
+            }
+
+        }
+    }
+
+    *nbind_ret    = nbind;
+    *nconst_ret  += nconst;
+    *poollen_ret += poollen;
+}
+
+static void copy_column_values(int                 ncol,
+                               mqi_data_type_t    *coltypes,
+                               mqi_column_desc_t  *src_cols,
+                               mqi_column_desc_t  *dst_cols,
+                               value_t           **bindv_ptr,
+                               value_t           **constv_ptr,
+                               char              **strpool_ptr,
+                               void               *data,
+                               void               *values)
+{
+    value_t           *bindv   = *bindv_ptr;
+    value_t           *constv  = *constv_ptr;
+    void              *strpool = *strpool_ptr;
+    mqi_column_desc_t *col;
+    value_t           *val;
+    mqi_data_type_t    type;
+    int                offs;
+    void              *vptr;
+    char              *str;
+    int                len;
+    int                i;
+
+    for (i = 0;  i < ncol;  i++) {
+        type = coltypes[i];
+        *(col = dst_cols + i) = src_cols[i];
+
+        if ((offs = col->offset) < 0)
+            val  = bindv - (offs + 1);
+        else {
+            val  = constv++;
+            vptr = data + offs;
+
+            switch (type) {
+            case mqi_varchar:
+                str = *(char **)vptr;
+                len = strlen(str) + 1;
+                val->v.varchar = (char *)memcpy(strpool, str, len);
+                strpool += len;
+                break;
+            case mqi_integer:
+                val->v.integer = *(int32_t *)vptr;
+                break;
+            case mqi_unsignd:
+                val->v.unsignd  = *(uint32_t *)vptr;
+                break;
+            case mqi_floating:
+                val->v.floating = *(double *)vptr;
+                break;
+            default:
+                break;
+            }
+        }
+
+        val->type   = type;
+        col->offset = (void *)&val->v.generic - values;
+    }
+
+    col = dst_cols + i;
+    col->cindex = -1;
+    col->offset = -1;
+
+    *bindv_ptr   = bindv;
+    *constv_ptr  = constv;
+    *strpool_ptr = strpool;
+}
+
+static void copy_conditions_and_values(int                ncond,
+                                       mqi_cond_entry_t  *src_conds,
+                                       mqi_cond_entry_t  *dst_conds,
+                                       value_t          **bindv_ptr,
+                                       value_t          **constv_ptr,
+                                       char             **strpool_ptr)
+{
+    value_t          *bindv   = *bindv_ptr;
+    value_t          *constv  = *constv_ptr;
+    char             *strpool = *strpool_ptr;
+    mqi_cond_entry_t *cond;
+    mqi_variable_t   *var;
+    mqi_data_type_t   type;
+    uint32_t          flags;
+    value_t          *val;
+    char             *str;
+    int               len;
+    int               i;
+
+    for (i = 0;   i < ncond;   i++) {
+        *(cond = dst_conds + i) = src_conds[i];
+
+        if (cond->type == mqi_variable) {
+            var   = &cond->u.variable;
+            type  = var->type;
+            flags = var->flags;
+
+            if ((flags & MQL_BINDABLE))
+                val = bindv + MQL_BIND_INDEX(flags);
+            else {
+                val = constv++;
+
+                switch (type) {
+                case mqi_varchar:
+                    str = *(var->v.varchar);
+                    len = strlen(str) + 1;
+                    val->v.varchar = (char *)memcpy(strpool, str, len);
+                    strpool += len;
+                    break;
+                case mqi_integer:
+                    val->v.integer = *(var->v.integer);
+                    break;
+                case mqi_unsignd:
+                    val->v.unsignd = *(var->v.unsignd);
+                    break;
+                case mqi_floating:
+                    val->v.floating = *(var->v.floating);
+                    break;
+                default:
+                    break;
+                }
+            }
+
+            val->type = type;
+            var->v.generic = (void *)&val->v.generic;
+        }
+    }
+
+    *bindv_ptr   = bindv;
+    *constv_ptr  = constv;
+    *strpool_ptr = strpool;
+}
+
+static mql_result_t *exec_show_tables(mql_result_type_t type,
+                                      shtable_statement_t *st)
+{
+    mql_result_t *rslt;
+    char         *names[4096];
+    int           n;
+
+    MQI_UNUSED(type);
+
+    if ((n = mqi_show_tables(st->flags, names, MQI_DIMENSION(names))) < 0) {
+        rslt = mql_result_error_create(errno, "can't show tables: %s",
+                                       strerror(errno));
+    }
+    else {
+        if (!n)
+            rslt = mql_result_error_create(0, "no tables");
+        else
+            rslt = mql_result_list_create(mqi_string, n, names);
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_describe(mql_result_type_t type,
+                                   describe_statement_t *d)
+{
+    mql_result_t     *rslt;
+    mqi_column_def_t  defs[MQI_COLUMN_MAX];
+    int               n;
+
+    if ((n = mqi_describe(d->table, defs, MQI_COLUMN_MAX)) < 0) {
+        rslt = mql_result_error_create(errno, "describe failed: %s",
+                                       strerror(errno));
+    }
+    else {
+        switch (type) {
+        case mql_result_columns:
+            rslt = mql_result_columns_create(n, defs);
+            break;
+        case mql_result_string:
+            rslt = mql_result_string_create_column_list(n, defs);
+            break;
+        default:
+            rslt = mql_result_error_create(EINVAL, "describe failed: invalid"
+                                           " result type %d", type);
+            break;
+        }
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_begin(transact_statement_t *b)
+{
+    mql_result_t *rslt;
+
+    if (mql_begin_transaction(b->trnam) == 0)
+        rslt = mql_result_success_create();
+    else {
+        rslt = mql_result_error_create(errno, "begin failed: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+
+static mql_result_t *exec_commit(transact_statement_t *c)
+{
+    mql_result_t *rslt;
+
+    if (mql_commit_transaction(c->trnam) == 0)
+        rslt = mql_result_success_create();
+    else {
+        rslt = mql_result_error_create(errno, "commit failed: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+
+static mql_result_t *exec_rollback(transact_statement_t *r)
+{
+    mql_result_t *rslt;
+
+    if (mql_rollback_transaction(r->trnam) == 0)
+        rslt = mql_result_success_create();
+    else {
+        rslt = mql_result_error_create(errno, "rollback failed: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+
+static mql_result_t *exec_insert(insert_statement_t *i)
+{
+    mql_result_t *rslt;
+    int           n;
+
+    if ((n = mqi_insert_into(i->table, i->ignore, i->columns, i->rows)) >= 0)
+        rslt = mql_result_error_create(0, "inserted %d rows", n);
+    else {
+        rslt = mql_result_error_create(errno, "insert error: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_update(update_statement_t *u)
+{
+    mql_result_t *rslt;
+    int           n;
+
+    if ((n = mqi_update(u->table, u->cond, u->columns, u->values)) >= 0)
+        rslt = mql_result_error_create(0, "updated %d rows", n);
+    else {
+        rslt = mql_result_error_create(errno, "update error: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_delete(delete_statement_t *d)
+{
+    mql_result_t *rslt;
+    int           n;
+
+    if ((n = mqi_delete_from(d->table, d->cond)) >= 0)
+        rslt = mql_result_error_create(0, "deleted %d rows", n);
+    else {
+        rslt = mql_result_error_create(errno, "delete error: %s",
+                                       strerror(errno));
+    }
+
+    return rslt;
+}
+
+static mql_result_t *exec_select(mql_result_type_t type, select_statement_t *s)
+{
+    mql_result_t *rslt;
+    int           maxrow;
+    int           nrow;
+    void         *rows;
+
+    if ((maxrow = mqi_get_table_size(s->table)) < 0)
+        rslt = mql_result_error_create(ENOENT, "can't access table");
+    else {
+        if (!maxrow) {
+            rows = alloca(s->rowsize);
+            nrow = 0;
+        }
+        else {
+            rows = alloca(maxrow * s->rowsize);
+            nrow = mqi_select(s->table, s->cond, s->columns,
+                              rows, s->rowsize, maxrow);
+        }
+
+       if (nrow < 0) {
+           rslt = mql_result_error_create(errno, "select error: %s",
+                                          strerror(errno));
+        }
+        else {
+            switch (type) {
+            case mql_result_rows:
+                rslt = mql_result_rows_create(s->ncolumn, s->columns,
+                                              s->coltypes, s->colsizes,
+                                              nrow, s->rowsize, rows);
+                break;
+            case mql_result_string:
+                rslt = mql_result_string_create_row_list(
+                                         s->ncolumn, s->colnames, s->columns,
+                                         s->coltypes, s->colsizes,
+                                         nrow, s->rowsize, rows);
+                break;
+            default:
+                rslt = mql_result_error_create(EINVAL, "select failed: invalid"
+                                               " result type %d", type);
+                break;
+            }
+        }
+    }
+
+    return rslt;
+}
+
+static int bind_update_value(update_statement_t *u,
+                             int                 idx,
+                             mqi_data_type_t     type,
+                             va_list             data)
+{
+    if (idx >= u->nbind) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return bind_value(u->values + idx, type, data);
+}
+
+
+static int bind_delete_value(delete_statement_t *d,
+                             int                 idx,
+                             mqi_data_type_t     type,
+                             va_list             data)
+{
+    if (idx >= d->nbind) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return bind_value(d->values + idx, type, data);
+}
+
+
+static int bind_select_value(select_statement_t *s,
+                             int                 idx,
+                             mqi_data_type_t     type,
+                             va_list             data)
+{
+    if (idx >= s->nbind) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    return bind_value(s->values + idx, type, data);
+}
+
+
+static int bind_value(value_t *v, mqi_data_type_t type, va_list data)
+{
+    if (type == v->type) {
+        switch (type) {
+        case mqi_varchar:   v->v.varchar  = va_arg(data, char *);     return 0;
+        case mqi_integer:   v->v.integer  = va_arg(data, int32_t);    return 0;
+        case mqi_unsignd:   v->v.unsignd  = va_arg(data, uint32_t);   return 0;
+        case mqi_floating:  v->v.floating = va_arg(data, double);     return 0;
+        default:                                                      break;
+        }
+    }
+
+    errno = EINVAL;
+    return -1;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mql/transaction.c b/src/murphy-db/mql/transaction.c
new file mode 100644 (file)
index 0000000..da21802
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include <errno.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mqi.h>
+#include <murphy-db/hash.h>
+#include "mql-parser.h"
+
+/* Note: HANDLE_TO_PTR(MQI_HANDLE_INVALID) == NULL */
+#define HANDLE_TO_PTR(h)   (((void *)1) + (h))
+#define PTR_TO_HANDLE(p)   (mqi_handle_t)((p) - ((void *)1))
+
+static mdb_hash_t *transact_handles;
+
+static int init(void);
+static int add_handle(char *, mqi_handle_t);
+static mqi_handle_t delete_handle(char *);
+
+
+int mql_begin_transaction(char *name)
+{
+    mqi_handle_t h;
+
+    MDB_CHECKARG(name, -1);
+
+    if ((h = mqi_begin_transaction()) == MQI_HANDLE_INVALID)
+        return -1;
+
+    if (add_handle(name, h) < 0) {
+        mqi_rollback_transaction(h);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+int mql_rollback_transaction(char *name)
+{
+    mqi_handle_t h;
+
+    MDB_CHECKARG(name, -1);
+
+    if ((h = delete_handle(name)) == MQI_HANDLE_INVALID)
+        return -1;
+
+    if (mqi_rollback_transaction(h) < 0)
+        return -1;
+
+    return 0;
+}
+
+int mql_commit_transaction(char *name)
+{
+    mqi_handle_t h;
+
+    MDB_CHECKARG(name, -1);
+
+    if ((h = delete_handle(name)) == MQI_HANDLE_INVALID)
+        return -1;
+
+    if (mqi_commit_transaction(h) < 0)
+        return -1;
+
+    return 0;
+}
+
+
+static int init(void)
+{
+    static bool done = false;
+
+    int sts = 0;
+
+    if (!done) {
+        if (!(transact_handles = MDB_HASH_TABLE_CREATE(string, 16)))
+            sts = -1;
+
+        done = true;
+    }
+
+    return sts;
+}
+
+static int add_handle(char *name, mqi_handle_t handle)
+{
+    if (init() < 0)
+        return -1;
+
+    if (mdb_hash_add(transact_handles, 0,name, HANDLE_TO_PTR(handle)) < 0)
+        return -1;
+
+    return 0;
+}
+
+static mqi_handle_t delete_handle(char *name)
+{
+    void *ptr;
+
+    if (init() < 0)
+        return MQI_HANDLE_INVALID;
+
+    if (!(ptr = mdb_hash_delete(transact_handles, 0,name)))
+        return MQI_HANDLE_INVALID;
+
+    return PTR_TO_HANDLE(ptr);
+}
+
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/mql/trigger.c b/src/murphy-db/mql/trigger.c
new file mode 100644 (file)
index 0000000..a214397
--- /dev/null
@@ -0,0 +1,700 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include <errno.h>
+
+#include <murphy-db/assert.h>
+#include <murphy-db/mql.h>
+#include <murphy-db/hash.h>
+#include "mql-parser.h"
+
+#ifndef MQL_CALLBACK_HASH_CHAINS
+#define MQL_CALLBACK_HASH_CHAINS  128
+#endif
+
+#ifndef MQL_TRIGGER_HASH_CHAINS
+#define MQL_TRIGGER_HASH_CHAINS  128
+#endif
+
+
+typedef enum trigger_type_e          trigger_type_t;
+typedef struct select_s              select_t;
+typedef struct column_s              column_t;
+typedef struct trigger_s             trigger_t;
+typedef struct trigger_s             transact_trigger_t;
+typedef struct trigger_s             table_trigger_t;
+typedef struct row_trigger_s         row_trigger_t;
+typedef struct column_trigger_s      column_trigger_t;
+
+
+struct mql_callback_s {
+    int                refcnt;
+    char              *name;
+    mql_result_type_t  rtype;
+    mql_trigger_cb_t   function;
+    void              *user_data;
+};
+
+enum trigger_type_e {
+    trigger_unknown = 0,
+    trigger_first = trigger_unknown,
+
+    trigger_transaction,
+    trigger_table,
+    trigger_row,
+    trigger_column,
+
+    trigger_last
+};
+
+#define TRIGGER_COMMON                          \
+    char               *name;                   \
+    trigger_type_t      type;                   \
+    mql_callback_t     *callback
+
+struct select_s {
+    struct {
+        int                 ncol;
+        char              **names;
+        mqi_column_desc_t  *descs;
+        mqi_data_type_t    *types;
+        int                *sizes;
+    }                   column;
+    int                 rowsize;
+    struct {
+        char   *addr;
+        size_t  size;
+    }                   strpool;
+};
+
+struct column_s {
+    int                 index;
+    mqi_data_type_t     type;
+};
+
+struct trigger_s {
+    TRIGGER_COMMON;
+};
+
+struct row_trigger_s {
+    TRIGGER_COMMON;
+    mqi_handle_t table;
+    select_t     select;
+    uint8_t      data[0];
+};
+
+struct column_trigger_s {
+    TRIGGER_COMMON;
+    mqi_handle_t table;
+    column_t     column;
+    select_t     select;
+    uint8_t      data[0];
+};
+
+
+static mdb_hash_t *callbacks;
+static mdb_hash_t *triggers;
+
+static int unref_callback(mql_callback_t *);
+static mql_callback_t *ref_callback(mql_callback_t *);
+
+static void column_event_callback(mqi_event_t *, void *);
+static void row_event_callback(mqi_event_t *, void *);
+static void table_event_callback(mqi_event_t *, void *);
+static void transaction_event_callback(mqi_event_t *, void *);
+
+
+int mql_register_callback(const char        *name,
+                          mql_result_type_t  rtype,
+                          mql_trigger_cb_t   function,
+                          void              *user_data)
+{
+    mql_callback_t *cb;
+
+    MDB_CHECKARG(name && *name && function &&
+                 (rtype == mql_result_event  ||
+                  rtype == mql_result_string ||
+                  rtype == mql_result_dontcare), -1);
+
+    if (!callbacks) {
+        callbacks = MDB_HASH_TABLE_CREATE(string, MQL_CALLBACK_HASH_CHAINS);
+        MDB_PREREQUISITE(callbacks, -1);
+    }
+
+    if (rtype == mql_result_dontcare)
+        rtype = mql_result_event;
+
+    if (!(cb = calloc(1, sizeof(mql_callback_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    cb->refcnt    = 0;
+    cb->name      = strdup(name);
+    cb->rtype     = rtype;
+    cb->function  = function;
+    cb->user_data = user_data;
+
+    if (!cb->name || mdb_hash_add(callbacks, 0,cb->name, cb) < 0) {
+        free(cb->name);
+        free(cb);
+        return -1;
+    }
+
+    return 0;
+}
+
+
+int mql_unregister_callback(const char *name)
+{
+    mql_callback_t *cb;
+
+    MDB_CHECKARG(name, -1);
+
+    if (!(cb = mdb_hash_delete(callbacks, 0,(void *)name)))
+        return -1;
+
+    return unref_callback(cb);
+}
+
+
+mql_callback_t *mql_find_callback(char *name)
+{
+    mql_callback_t *cb;
+
+    MDB_CHECKARG(name, NULL);
+    MDB_PREREQUISITE(callbacks, NULL);
+
+    cb = mdb_hash_get_data(callbacks, 0,name);
+
+    return cb;
+}
+
+int mql_create_column_trigger(char              *name,
+                              mqi_handle_t       table,
+                              int                colidx,
+                              mqi_data_type_t    coltyp,
+                              mql_callback_t    *callback,
+                              int                nselcol,
+                              char             **selcolnams,
+                              mqi_column_desc_t *selcoldscs,
+                              mqi_data_type_t   *selcoltypes,
+                              int               *selcolsizes,
+                              int                rowsize)
+{
+    column_trigger_t *tr;
+    size_t nlens[MQI_COLUMN_MAX];
+    size_t asiz;
+    size_t nsiz;
+    size_t dsiz;
+    size_t tsiz;
+    size_t ssiz;
+    size_t size;
+    uint8_t *data;
+    int sts;
+    int i;
+
+    MDB_CHECKARG(name && table != MQI_HANDLE_INVALID && callback &&
+                 (!nselcol || (nselcol > 0 && nselcol < MQI_COLUMN_MAX &&
+                  selcoldscs && selcolsizes && rowsize > 0)), -1);
+
+    if (!triggers) {
+        triggers = MDB_HASH_TABLE_CREATE(string, MQL_TRIGGER_HASH_CHAINS);
+        MDB_PREREQUISITE(triggers, -1);
+    }
+
+    if (!nselcol) {
+        nsiz = asiz = dsiz = tsiz = ssiz = 0;
+        size = sizeof(column_trigger_t);
+    }
+    else {
+        nsiz = asiz = sizeof(char *) * nselcol;
+
+        for (i = 0;  i < nselcol;  i++)
+            nsiz += (nlens[i] = strlen(selcolnams[i]) + 1);
+
+        dsiz = sizeof(mqi_column_desc_t) * (nselcol ? nselcol + 1 : 0);
+        tsiz = sizeof(mqi_data_type_t) * nselcol;
+        ssiz = sizeof(int) * nselcol;
+        size = sizeof(column_trigger_t) + nsiz + dsiz + tsiz + ssiz;
+    }
+
+    if (!(tr = calloc(1, size))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    tr->name     = strdup(name);
+    tr->type     = trigger_column;
+    tr->callback = ref_callback(callback);
+
+    tr->table = table;
+
+    tr->column.index = colidx;
+    tr->column.type  = coltyp;
+
+    if (nselcol > 0) {
+        data = tr->data;
+
+        tr->select.column.ncol  = nselcol;
+        tr->select.column.names = (char **)data;
+        tr->select.column.descs = (mqi_column_desc_t *)(data += asiz);
+        tr->select.column.types = (mqi_data_type_t *)(data += dsiz);
+        tr->select.column.sizes = (int *)(data += tsiz);
+
+        tr->select.strpool.addr = (char *)(data += ssiz);
+        tr->select.strpool.size = nsiz - asiz;
+
+        tr->select.rowsize = rowsize;
+
+        memcpy(tr->select.column.descs, selcoldscs , dsiz);
+        memcpy(tr->select.column.types, selcoltypes, tsiz);
+        memcpy(tr->select.column.sizes, selcolsizes, ssiz);
+
+        for (i = 0;   i < nselcol;   i++) {
+            tr->select.column.names[i] = (char *)data;
+            memcpy(data, selcolnams[i], nlens[i]);
+            data += nlens[i];
+        }
+    }
+
+    if (!tr->name || mdb_hash_add(triggers, 0,tr->name, tr) < 0) {
+        free(tr->name);
+        free(tr);
+        return -1;
+    }
+
+    sts = mqi_create_column_trigger(table, colidx, column_event_callback, tr,
+                                    tr->select.column.descs);
+    return sts;
+}
+
+
+
+int mql_create_row_trigger(char              *name,
+                           mqi_handle_t       table,
+                           mql_callback_t    *callback,
+                           int                nselcol,
+                           char             **selcolnams,
+                           mqi_column_desc_t *selcoldscs,
+                           mqi_data_type_t   *selcoltypes,
+                           int               *selcolsizes,
+                           int                rowsize)
+{
+    row_trigger_t *tr;
+    size_t nlens[MQI_COLUMN_MAX];
+    size_t asiz;
+    size_t nsiz;
+    size_t dsiz;
+    size_t tsiz;
+    size_t ssiz;
+    size_t size;
+    uint8_t *data;
+    int sts;
+    int i;
+
+    MDB_CHECKARG(name && table != MQI_HANDLE_INVALID && callback &&
+                 nselcol > 0 && nselcol < MQI_COLUMN_MAX &&
+                 selcoldscs && selcolsizes && rowsize > 0, -1);
+
+    if (!triggers) {
+        triggers = MDB_HASH_TABLE_CREATE(string, MQL_TRIGGER_HASH_CHAINS);
+        MDB_PREREQUISITE(triggers, -1);
+    }
+
+    nsiz = asiz = sizeof(char *) * nselcol;
+
+    for (i = 0;  i < nselcol;  i++)
+        nsiz += (nlens[i] = strlen(selcolnams[i]) + 1);
+
+    dsiz = sizeof(mqi_column_desc_t) * (nselcol ? nselcol + 1 : 0);
+    tsiz = sizeof(mqi_data_type_t) * nselcol;
+    ssiz = sizeof(int) * nselcol;
+    size = sizeof(row_trigger_t) + nsiz + dsiz + tsiz + ssiz;
+
+    if (!(tr = calloc(1, size))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    tr->name     = strdup(name);
+    tr->type     = trigger_row;
+    tr->callback = ref_callback(callback);
+
+    tr->table = table;
+
+    data = tr->data;
+
+    tr->select.column.ncol  = nselcol;
+    tr->select.column.names = (char **)data;
+    tr->select.column.descs = (mqi_column_desc_t *)(data += asiz);
+    tr->select.column.types = (mqi_data_type_t *)(data += dsiz);
+    tr->select.column.sizes = (int *)(data += tsiz);
+
+    tr->select.strpool.addr = (char *)(data += ssiz);
+    tr->select.strpool.size = nsiz - asiz;
+
+    tr->select.rowsize = rowsize;
+
+    memcpy(tr->select.column.descs, selcoldscs , dsiz);
+    memcpy(tr->select.column.types, selcoltypes, tsiz);
+    memcpy(tr->select.column.sizes, selcolsizes, ssiz);
+
+    for (i = 0;   i < nselcol;   i++) {
+        tr->select.column.names[i] = (char *)data;
+        memcpy(data, selcolnams[i], nlens[i]);
+        data += nlens[i];
+    }
+
+    if (!tr->name || mdb_hash_add(triggers, 0,tr->name, tr) < 0) {
+        free(tr->name);
+        free(tr);
+        return -1;
+    }
+
+    sts = mqi_create_row_trigger(table, row_event_callback, tr,
+                                 tr->select.column.descs);
+    return sts;
+}
+
+
+int mql_create_table_trigger(char *name, mql_callback_t *callback)
+{
+    table_trigger_t *tr;
+    int sts;
+
+    MDB_CHECKARG(name && callback, -1);
+
+    if (!triggers) {
+        triggers = MDB_HASH_TABLE_CREATE(string, MQL_TRIGGER_HASH_CHAINS);
+        MDB_PREREQUISITE(triggers, -1);
+    }
+
+    if (!(tr = calloc(1, sizeof(table_trigger_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    tr->name     = strdup(name);
+    tr->type     = trigger_table;
+    tr->callback = ref_callback(callback);
+
+    sts = mqi_create_table_trigger(table_event_callback, tr);
+
+    return sts;
+}
+
+
+int mql_create_transaction_trigger(char *name, mql_callback_t *callback)
+{
+    transact_trigger_t *tr;
+    int sts;
+
+    MDB_CHECKARG(name && callback, -1);
+
+    if (!triggers) {
+        triggers = MDB_HASH_TABLE_CREATE(string, MQL_TRIGGER_HASH_CHAINS);
+        MDB_PREREQUISITE(triggers, -1);
+    }
+
+    if (!(tr = calloc(1, sizeof(transact_trigger_t)))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    tr->name     = strdup(name);
+    tr->type     = trigger_transaction;
+    tr->callback = ref_callback(callback);
+
+    sts = mqi_create_transaction_trigger(transaction_event_callback, tr);
+
+    return sts;
+}
+
+
+static mql_callback_t *ref_callback(mql_callback_t *cb)
+{
+    cb->refcnt++;
+    return cb;
+}
+
+static int unref_callback(mql_callback_t *cb)
+{
+    if (cb->refcnt > 0)
+        cb->refcnt--;
+    else {
+        free(cb->name);
+        free(cb);
+    }
+
+    return 0;
+}
+
+
+static void column_event_callback(mqi_event_t *evt, void *user_data)
+{
+    mqi_column_event_t *ce;
+    column_trigger_t   *tr;
+    mql_callback_t     *cb;
+    select_t           *s;
+    mql_result_t       *rsel;
+    mql_result_t       *rslt;
+
+    if (!evt || !user_data)
+        return;
+
+    ce = &evt->column;
+    tr = (column_trigger_t *)user_data;
+    cb = tr->callback;
+
+    if (ce->event  != mqi_column_changed ||
+        tr->type   != trigger_column ||
+        (cb->rtype != mql_result_event &&
+         cb->rtype != mql_result_string))
+    {
+        return;
+    }
+
+    rsel = rslt = NULL;
+
+    if (tr->select.column.ncol <= 0) {
+        if (cb->rtype == mql_result_event) {
+            rslt = mql_result_event_column_change_create(ce->table.handle,
+                                                         ce->column.index,
+                                                         &ce->value,
+                                                         NULL);
+        }
+        else {
+            rslt = mql_result_string_create_column_change(ce->table.name,
+                                                          ce->column.name,
+                                                          &ce->value,
+                                                          NULL);
+        }
+    }
+    else {
+        s = &tr->select;
+
+        if (cb->rtype == mql_result_event) {
+
+            rsel = mql_result_rows_create(s->column.ncol,
+                                          s->column.descs,
+                                          s->column.types,
+                                          s->column.sizes,
+                                          1,
+                                          s->rowsize,
+                                          ce->select.data);
+
+            if (mql_result_is_success(rsel)) {
+                rslt = mql_result_event_column_change_create(ce->table.handle,
+                                                             ce->column.index,
+                                                             &ce->value,
+                                                             rsel);
+            }
+        }
+        else {
+            rsel = mql_result_string_create_row_list(s->column.ncol,
+                                                     s->column.names,
+                                                     s->column.descs,
+                                                     s->column.types,
+                                                     s->column.sizes,
+                                                     1,
+                                                     s->rowsize,
+                                                     ce->select.data);
+
+            if (mql_result_is_success(rsel)) {
+                rslt = mql_result_string_create_column_change(ce->table.name,
+                                                              ce->column.name,
+                                                              &ce->value,
+                                                              rsel);
+            }
+        }
+    }
+
+    if (!rslt)
+        free(rsel);
+    else {
+        cb->function(rslt, cb->user_data);
+        free(rsel);
+        free(rslt);
+    }
+}
+
+
+
+static void row_event_callback(mqi_event_t *evt, void *user_data)
+{
+    mqi_row_event_t *re;
+    row_trigger_t   *tr;
+    mql_callback_t  *cb;
+    select_t        *s;
+    mql_result_t    *rsel;
+    mql_result_t    *rslt;
+
+    if (!evt || !user_data)
+        return;
+
+    re = &evt->row;
+    tr = (row_trigger_t *)user_data;
+    cb = tr->callback;
+    s  = &tr->select;
+
+    if ((re->event != mqi_row_inserted && re->event != mqi_row_deleted) ||
+        tr->type   != trigger_row                                       ||
+        (cb->rtype != mql_result_event && cb->rtype != mql_result_string))
+    {
+        return;
+    }
+
+
+    rsel = rslt = NULL;
+
+
+    if (cb->rtype == mql_result_event) {
+        rsel = mql_result_rows_create(s->column.ncol,
+                                      s->column.descs,
+                                      s->column.types,
+                                      s->column.sizes,
+                                      1,
+                                      s->rowsize,
+                                      re->select.data);
+
+        if (mql_result_is_success(rsel)) {
+            rslt = mql_result_event_row_change_create(re->event,
+                                                      re->table.handle,
+                                                      rsel);
+        }
+    }
+    else {
+        rsel = mql_result_string_create_row_list(s->column.ncol,
+                                                 s->column.names,
+                                                 s->column.descs,
+                                                 s->column.types,
+                                                 s->column.sizes,
+                                                 1,
+                                                 s->rowsize,
+                                                 re->select.data);
+
+        if (mql_result_is_success(rsel)) {
+            rslt = mql_result_string_create_row_change(re->event,
+                                                       re->table.name,
+                                                       rsel);
+        }
+    }
+
+    if (!rslt)
+        free(rsel);
+    else {
+        cb->function(rslt, cb->user_data);
+        free(rsel);
+        free(rslt);
+    }
+}
+
+
+
+static void table_event_callback(mqi_event_t *evt, void *user_data)
+{
+    mqi_table_event_t *te;
+    table_trigger_t   *tr;
+    mql_callback_t    *cb;
+    mql_result_t      *rslt;
+
+    if (!evt || !user_data)
+        return;
+
+    te = &evt->table;
+    tr = (table_trigger_t *)user_data;
+    cb = tr->callback;
+
+    if ((te->event != mqi_table_created && te->event != mqi_table_dropped) ||
+        tr->type   != trigger_table                           ||
+        (cb->rtype != mql_result_event && cb->rtype != mql_result_string))
+    {
+        return;
+    }
+
+    if (cb->rtype == mql_result_event)
+        rslt = mql_result_event_table_create(te->event, te->table.handle);
+    else
+        rslt = mql_result_string_create_table_change(te->event,te->table.name);
+
+    if (rslt) {
+        cb->function(rslt, cb->user_data);
+        free(rslt);
+    }
+}
+
+
+
+static void transaction_event_callback(mqi_event_t *evt, void *user_data)
+{
+    mqi_transact_event_t *te;
+    table_trigger_t      *tr;
+    mql_callback_t       *cb;
+    mql_result_t         *rslt;
+
+    if (!evt || !user_data)
+        return;
+
+    te = &evt->transact;
+    tr = (transact_trigger_t *)user_data;
+    cb = tr->callback;
+
+    if ((te->event != mqi_transaction_start &&
+         te->event != mqi_transaction_end    ) ||
+        tr->type   != trigger_transaction       ||
+        (cb->rtype != mql_result_event &&
+         cb->rtype != mql_result_string  )       )
+    {
+        return;
+    }
+
+    if (cb->rtype == mql_result_event)
+        rslt = mql_result_event_transaction_create(te->event);
+    else
+        rslt = mql_result_string_create_transaction_change(te->event);
+
+    if (rslt) {
+        cb->function(rslt, cb->user_data);
+        free(rslt);
+    }
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/murphy-db.pc.in b/src/murphy-db/murphy-db.pc.in
new file mode 100644 (file)
index 0000000..f41cf24
--- /dev/null
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+includedir=@includedir@/murphy-db
+libdir=@libdir@/murphy
+
+Name: Murphy DB
+Description: Database for the Murphy policy engine
+URL: github/otcshare/murphy
+Version: @PACKAGE_VERSION@
+Cflags: -I${includedir}
+Libs: -L${libdir} -lmqi -lmql -lmdb
+Libs.private:
diff --git a/src/murphy-db/tests/Makefile.am b/src/murphy-db/tests/Makefile.am
new file mode 100644 (file)
index 0000000..96aab16
--- /dev/null
@@ -0,0 +1,47 @@
+CHECK_LIBMDB_LOG = check-libmdb.log
+CHECK_LIBMQI_LOG = check-libmqi.log
+CHECK_LIBMQL_LOG = check-libmql.log
+
+MDB_LIBS = ../mdb/libmdb.la
+MQI_LIBS = ../mqi/libmqi.la
+MQL_LIBS = ../mql/libmql.la
+
+if HAVE_CHECK
+TESTS = check-libmdb check-libmqi check-libmql
+else
+TESTS =
+endif
+
+noinst_PROGRAMS = $(TESTS)
+
+#
+# MDB tests
+#
+check_libmdb_SOURCES = check-libmdb.c
+check_libmdb_CFLAGS  = @CHECK_CFLAGS@ -I../include \
+                       -DLOGFILE=\"$(CHECK_LIBMDB_LOG)\"
+check_libmdb_LDADD   = @CHECK_LIBS@ $(MDB_LIBS)
+
+AM_CFLAGS = -g3 -O0
+
+#
+# MQI tests
+#
+check_libmqi_SOURCES = check-libmqi.c
+check_libmqi_CFLAGS  = @CHECK_CFLAGS@ -I../include \
+                       -DLOGFILE=\"$(CHECK_LIBMQI_LOG)\"
+check_libmqi_LDADD   = @CHECK_LIBS@ $(MQI_LIBS) $(MDB_LIBS) 
+
+
+#
+# MQL tests
+#
+check_libmql_SOURCES = check-libmql.c
+check_libmql_CFLAGS  = @CHECK_CFLAGS@ -I../include \
+                       -DLOGFILE=\"$(CHECK_LIBMQL_LOG)\"
+check_libmql_LDADD   = @CHECK_LIBS@ $(MQL_LIBS) $(MQI_LIBS) $(MDB_LIBS) 
+
+
+clean-local:
+       rm -f $(CHECK_LIBMDB_LOG) $(CHECK_LIBMQI_LOG) $(CHECK_LIBMQL_LOG) \
+              $(TESTS) *~
diff --git a/src/murphy-db/tests/check-libmdb.c b/src/murphy-db/tests/check-libmdb.c
new file mode 100644 (file)
index 0000000..f08ecdb
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <check.h>
+
+#ifndef LOGFILE
+#define LOGFILE  "check_libmdb.log"
+#endif
+
+#define ADD_TEST_CASE(s,t)                      \
+    do {                                        \
+        TCase *tc = tcase_create(#t);           \
+        tcase_add_test(tc, t);                  \
+        suite_add_tcase(s, tc);                 \
+    } while (0)
+
+
+static Suite *libmdb_suite(void);
+
+
+int main()
+{
+    Suite   *s  = libmdb_suite();
+    SRunner *sr = srunner_create(s);
+    int      nf;
+
+    srunner_set_log(sr, LOGFILE);
+
+    srunner_run_all(sr, CK_NORMAL);
+
+    nf = srunner_ntests_failed(sr);
+
+    srunner_free(sr);
+    // suite_free(s);
+
+    return (nf == 0) ? 0 : 1;
+}
+
+START_TEST(create_table)
+{
+    fail_unless(1==1, "create table test");
+}
+END_TEST
+
+
+static Suite *libmdb_suite(void)
+{
+    Suite *s = suite_create("Memory Database - libmdb");
+
+    ADD_TEST_CASE(s, create_table);
+
+    return s;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/tests/check-libmqi.c b/src/murphy-db/tests/check-libmqi.c
new file mode 100644 (file)
index 0000000..907094a
--- /dev/null
@@ -0,0 +1,1181 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <libgen.h>
+
+#include <check.h>
+
+#include <murphy-db/mqi.h>
+
+#ifndef LOGFILE
+#define LOGFILE  "check_libmqi.log"
+#endif
+
+#define PREREQUISITE(t)   t(_i)
+
+#define TRIGGER_DATA(idx)   (void *)0xdeadbeef##idx
+#define TRANSACT_TRIGGER_DATA TRIGGER_DATA(1)
+#define TABLE_TRIGGER_DATA    TRIGGER_DATA(2)
+#define ROW_TRIGGER_DATA      TRIGGER_DATA(3)
+#define COLUMN_TRIGGER_DATA   TRIGGER_DATA(4)
+
+typedef struct {
+    mqi_event_type_t  event;
+    struct {
+        mqi_handle_t handle;
+        char name[256];
+    } table;
+    struct {
+        uint32_t id;
+        char     first_name[14];
+        char     family_name[14];
+    } row;
+    struct {
+        int index;
+        char name[14];
+        char value[32];
+    } col;
+} trigger_t;
+
+typedef struct {
+    const char  *sex;
+    const char  *first_name;
+    const char  *family_name;
+    uint32_t     id;
+    const char  *email;
+} record_t;
+
+typedef struct {
+    uint32_t       id;
+    const char    *family_name;
+    const char    *first_name;
+} query_t;
+
+
+MQI_COLUMN_DEFINITION_LIST(persons_coldefs,
+    MQI_COLUMN_DEFINITION( "sex"        , MQI_VARCHAR(6)  ),
+    MQI_COLUMN_DEFINITION( "family_name", MQI_VARCHAR(12) ),
+    MQI_COLUMN_DEFINITION( "first_name" , MQI_VARCHAR(12) ),
+    MQI_COLUMN_DEFINITION( "id"         , MQI_UNSIGNED    ),
+    MQI_COLUMN_DEFINITION( "email"      , MQI_VARCHAR(24) )
+);
+
+MQI_INDEX_DEFINITION(persons_indexdef,
+    MQI_INDEX_COLUMN("first_name")
+    MQI_INDEX_COLUMN("family_name")
+);
+
+MQI_COLUMN_SELECTION_LIST(persons_insert_columns,
+    MQI_COLUMN_SELECTOR( 0, record_t, sex         ),
+    MQI_COLUMN_SELECTOR( 2, record_t, first_name  ),
+    MQI_COLUMN_SELECTOR( 1, record_t, family_name ),
+    MQI_COLUMN_SELECTOR( 3, record_t, id          ),
+    MQI_COLUMN_SELECTOR( 4, record_t, email       )
+);
+
+MQI_COLUMN_SELECTION_LIST(persons_select_columns,
+    MQI_COLUMN_SELECTOR( 3, query_t, id         ),
+    MQI_COLUMN_SELECTOR( 1, query_t, family_name ),
+    MQI_COLUMN_SELECTOR( 2, query_t, first_name  )
+);
+
+static record_t chuck = {"male"  , "Chuck", "Norris" , 1100, "cno@texas.us"  };
+static record_t gary  = {"male"  , "Gary", "Cooper"  ,  700, "gco@heaven.org"};
+static record_t elvis = {"male"  , "Elvis", "Presley",  600, "epr@heaven.org"};
+static record_t tom   = {"male"  , "Tom", "Cruise"   ,  500, "tcr@foo.com"   };
+static record_t greta = {"female", "Greta", "Garbo"  , 2000, "gga@heaven.org"};
+static record_t rita  = {"female", "Rita", "Hayworth",   44, "rha@heaven.org"};
+
+static record_t *artists[] = {&chuck, &gary, &elvis, &tom, &greta, &rita,NULL};
+
+
+
+static int          verbose;
+static mqi_handle_t transactions[MQI_TXDEPTH_MAX - 1];
+static int          txdepth;
+static mqi_handle_t persons = MQI_HANDLE_INVALID;
+static int          columns_no_in_persons = -1;
+static int          rows_no_in_persons = -1;
+
+static int          ntrigger;
+static trigger_t    triggers[256];
+static int          nseq = 32;
+static int          nnest = MQI_TXDEPTH_MAX - 1;
+
+
+static Suite *libmqi_suite(void);
+static TCase *basic_tests(void);
+static void   print_rows(int, query_t *);
+static void   print_triggers(void);
+static void   transaction_event_cb(mqi_event_t *, void *);
+static void   table_event_cb(mqi_event_t *, void *);
+static void   row_event_cb(mqi_event_t *, void *);
+static void   column_event_cb(mqi_event_t *, void *);
+
+
+int main(int argc, char **argv)
+{
+    Suite   *s  = libmqi_suite();
+    SRunner *sr = srunner_create(s);
+    int      nf;
+    int      i;
+
+    for (i = 1;  i < argc;  i++) {
+        if (!strcmp("-v", argv[i]))
+            verbose = 1;
+        else if (!strcmp("-f", argv[i]))
+            srunner_set_fork_status(sr, CK_NOFORK);
+        else if (!strcmp("-nseq", argv[i]) && i < argc - 1) {
+            nseq = atoi(argv[i + 1]);
+            i++;
+        }
+        else if (!strcmp("-nnest", argv[i]) && i < argc - 1) {
+            nnest = atoi(argv[i + 1]);
+            i++;
+        }
+        else {
+            printf("Usage: %s [-h] [-v] [-f]\n"
+                   "  -h     prints this message\n"
+                   "  -v     sets verbose mode\n"
+                   "  -f     forces no-forking mode\n"
+                   "  -nseq  number of sequential transactions\n"
+                   "  -nnest number of nested transactions (1 - %d)\n",
+                   basename(argv[0]), MQI_TXDEPTH_MAX - 1);
+            exit(strcmp("-h", argv[i]) ? 1 : 0);
+        }
+    }
+
+    srunner_set_log(sr, LOGFILE);
+
+    srunner_run_all(sr, CK_NORMAL);
+
+    nf = srunner_ntests_failed(sr);
+
+    srunner_free(sr);
+
+    return (nf == 0) ? 0 : 1;
+}
+
+START_TEST(open_db)
+{
+    int sts = mqi_open();
+
+    fail_if(sts, "db open test");
+}
+END_TEST
+
+
+
+START_TEST(create_table_persons)
+{
+    if (persons == MQI_HANDLE_INVALID) {
+        PREREQUISITE(open_db);
+
+        persons = MQI_CREATE_TABLE("persons", MQI_TEMPORARY,
+                                   persons_coldefs, persons_indexdef);
+
+        fail_if(persons == MQI_HANDLE_INVALID, "errno (%s)", strerror(errno));
+
+        columns_no_in_persons = MQI_DIMENSION(persons_coldefs) - 1;
+    }
+}
+END_TEST
+
+
+
+START_TEST(table_handle)
+{
+    mqi_handle_t handle = MQI_HANDLE_INVALID;
+
+    PREREQUISITE(create_table_persons);
+
+    handle = mqi_get_table_handle("persons");
+
+    fail_if(handle == MQI_HANDLE_INVALID, "failed to obtain handle for "
+            "'persons' (%s)", strerror(errno));
+
+    fail_if(handle != persons, "handle mismatch (0x%x vs. 0x%x)",
+            persons, handle);
+}
+END_TEST
+
+
+START_TEST(describe_persons)
+{
+    mqi_column_def_t  cols[32];
+    mqi_column_def_t *def, *col;
+    int deflgh;
+    int i,ncolumn;
+
+    PREREQUISITE(create_table_persons);
+
+    ncolumn = MQI_DESCRIBE(persons, cols);
+
+    fail_if(ncolumn < 0, "errno (%s)", strerror(errno));
+
+    fail_if(ncolumn != columns_no_in_persons, "mismatching column number "
+            "(%d vs. %d)", columns_no_in_persons, ncolumn);
+
+    if (verbose) {
+        printf("-----------------------------\n");
+        printf("name         type      length\n");
+        printf("-----------------------------\n");
+        for (i = 0;  i < ncolumn;  i++) {
+            col = cols + i;
+            printf("%-12s %-9s     %2d\n", col->name,
+                   mqi_data_type_str(col->type), col->length);
+        }
+        printf("-----------------------------\n");
+    }
+
+    for (i = 0;  i < ncolumn;  i++) {
+        def = persons_coldefs + i;
+        col = cols + i;
+
+        fail_if(strcmp(def->name, col->name), "mismatching column names @ "
+                "column %d ('%s' vs. '%s')", i, def->name, col->name);
+
+        fail_if(def->type != col->type, "mismatching column types @ "
+                "column %d (%d/'%s' vs. %d/'%s')", i,
+                def->type, mqi_data_type_str(def->type),
+                col->type, mqi_data_type_str(col->type));
+
+        switch (def->type) {
+        case mqi_varchar:   deflgh = def->length;       break;
+        case mqi_integer:   deflgh = sizeof(int32_t);   break;
+        case mqi_unsignd:   deflgh = sizeof(uint32_t);  break;
+        case mqi_floating:  deflgh = sizeof(double);    break;
+        case mqi_blob:      deflgh = def->length;       break;
+        default:            deflgh = -1;                break;
+        };
+
+        fail_if(deflgh != col->length, "mismatching column length @ "
+                "column %d (%d vs. %d)", i, deflgh, col->length);
+    }
+}
+END_TEST
+
+
+START_TEST(insert_into_persons)
+{
+    int n;
+
+    PREREQUISITE(create_table_persons);
+
+    n = MQI_INSERT_INTO(persons, persons_insert_columns, artists);
+
+    fail_if(n < 0, "errno (%s)", strerror(errno));
+
+    fail_if(n != MQI_DIMENSION(artists)-1, "some insertion failed. "
+            "Attempted %d succeeded %d", MQI_DIMENSION(artists)-1, n);
+
+    rows_no_in_persons = n;
+}
+END_TEST
+
+
+START_TEST(row_count_in_persons)
+{
+    int n;
+
+    PREREQUISITE(insert_into_persons);
+
+    n = mqi_get_table_size(persons);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    fail_if(n != rows_no_in_persons, "mismatch in row numbers: "
+            "Inserted %d reported %d", rows_no_in_persons, n);
+}
+END_TEST
+
+START_TEST(insert_duplicate_into_persons)
+{
+    static record_t  gary = {"male", "Gary","Cooper", 200, "gary@att.com"};
+    static record_t *duplicate[] = {&gary, NULL};
+
+    int n;
+
+    PREREQUISITE(insert_into_persons);
+
+    n = MQI_INSERT_INTO(persons, persons_insert_columns, duplicate);
+
+    fail_if(n == 1, "managed to insert a duplicate");
+
+    fail_if(n < 0 && errno != EEXIST, "error (%s)", strerror(errno));
+}
+END_TEST
+
+START_TEST(transaction_begin)
+{
+    mqi_handle_t tx;
+
+    fail_if(txdepth >= (int)MQI_DIMENSION(transactions), "too many nested "
+            "transactions. Only %d allowed", MQI_DIMENSION(transactions));
+
+    tx = MQI_BEGIN;
+
+    fail_if(tx == MQI_HANDLE_INVALID, "error (%d)", strerror(errno));
+
+    transactions[txdepth++] = tx;
+}
+END_TEST
+
+
+START_TEST(replace_in_persons)
+{
+    static record_t  gary = {"male", "Gary","Cooper", 200, "gary@att.com"};
+    static record_t *duplicate[] = {&gary, NULL};
+
+    int n;
+
+    PREREQUISITE(insert_into_persons);
+    PREREQUISITE(transaction_begin);
+
+    n = MQI_REPLACE(persons, persons_insert_columns, duplicate);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    fail_if(n == 1, "duplicate was inserted instead of replacement");
+}
+END_TEST
+
+START_TEST(filtered_select_from_persons)
+{
+    static char     *initial = "G";
+    static uint32_t  idlimit = 200;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_GREATER( MQI_COLUMN(1), MQI_STRING_VAR(initial)   ) MQI_AND
+        MQI_GREATER( MQI_COLUMN(3), MQI_UNSIGNED_VAR(idlimit) )
+    );
+
+    query_t rows[32];
+    int n;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_SELECT(persons_select_columns, persons, where, rows);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    fail_if(n != 3, "selcted %d rows but the right number would be 3", n);
+}
+END_TEST
+
+
+START_TEST(full_select_from_persons)
+{
+    query_t *r, rows[32];
+    int i, n;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "error (%s)", strerror(errno));
+
+    if (verbose) {
+        printf("   id first name      family name     \n");
+        printf("--------------------------------------\n");
+
+        if (!n)
+            printf("no rows\n");
+        else {
+            for (i = 0; i < n;  i++) {
+                r = rows + i;
+                printf("%5d %-15s %-15s\n", r->id,
+                       r->first_name, r->family_name);
+            }
+        }
+
+        printf("--------------------------------------\n");
+    }
+
+    fail_if(n != 6, "selcted %d rows but the right number would be 3", n);
+}
+END_TEST
+
+
+
+START_TEST(select_from_persons_by_index)
+{
+    MQI_INDEX_VALUE(index,
+        MQI_STRING_VAL(elvis.family_name)
+        MQI_STRING_VAL(elvis.first_name)
+    );
+
+    query_t row;
+    int n;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_SELECT_BY_INDEX(persons_select_columns, persons, index, &row);
+
+    fail_if(n < 0, "errno (%s)", strerror(errno));
+
+    fail_if(!n, "could not select %s %s", elvis.first_name, elvis.family_name);
+
+    fail_if(strcmp(row.first_name, elvis.first_name), "mismatching first "
+            "name ('%s' vs. '%s')", elvis.first_name, row.first_name);
+
+    fail_if(strcmp(row.family_name, elvis.family_name), "mismatching family "
+            "name ('%s' vs. '%s')", elvis.family_name, row.family_name);
+
+    fail_if(row.id != elvis.id, "mismatching id (%u vs. %u)",
+            elvis.id, row.id);
+}
+END_TEST
+
+
+
+START_TEST(update_in_persons)
+{
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(1), MQI_STRING_VAR(elvis.family_name) ) MQI_AND
+        MQI_EQUAL( MQI_COLUMN(2), MQI_STRING_VAR(elvis.first_name ) )
+    );
+
+    static query_t kalle = {1, "Korhonen", "Kalle"};
+
+    query_t *r, rows[32];
+    int i,n;
+    int found;
+
+    PREREQUISITE(replace_in_persons);
+
+    n = MQI_UPDATE(persons, persons_select_columns, &kalle, where);
+
+    fail_if(n  < 0, "errno (%s)", strerror(errno));
+    fail_if(n != 1, "updated %d row but supposed to just 1", n);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "select for checking failed (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    for (found = 0, i = 0;  i < n;  i++) {
+        r = rows + i;
+
+        fail_if(r->id == elvis.id, "found the original id %u what supposed "
+                "to change to %u", elvis.id, kalle.id);
+
+        fail_if(!strcmp(r->first_name, elvis.first_name), "found the original "
+                "first name '%s' what supposed to change to '%s'",
+                elvis.first_name, kalle.first_name);
+
+        fail_if(!strcmp(r->family_name, elvis.family_name),"found the original"
+                " family name '%s' what supposed to change to '%s'",
+                elvis.family_name, kalle.family_name);
+
+        if (r->id == kalle.id &&
+            !strcmp(r->first_name, kalle.first_name) &&
+            !strcmp(r->family_name, kalle.family_name))
+        {
+            found = 1;
+        }
+    }
+
+    fail_unless(found, "could not find the updated row");
+}
+END_TEST
+
+
+
+START_TEST(delete_from_persons)
+{
+    static uint32_t idlimit = 200;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_LESS( MQI_COLUMN(3), MQI_UNSIGNED_VAR(idlimit) )
+    );
+
+    query_t *r, rows[32];
+    int i,n;
+
+    PREREQUISITE(update_in_persons);
+
+    n = MQI_DELETE(persons, where);
+
+    fail_if(n  < 0, "errno (%s)", strerror(errno));
+    fail_if(n != 2, "deleted %d rows but sopposed to 2", n);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "verification select failed (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    for (i = 0;  i < n;  i++) {
+        r = rows + i;
+
+        fail_if(r->id < idlimit, "found row with id %u what is smaller than "
+                "the limit %u", r->id, idlimit);
+    }
+}
+END_TEST
+
+
+START_TEST(delete_all_persons)
+{
+    query_t rows[32];
+    int     nrow, n;
+
+    nrow = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+    fail_if(nrow < 0, "select for checking failed (%s)", strerror(errno));
+
+    n = MQI_DELETE(persons, MQI_ALL);
+    fail_if(n != nrow, "deleted %d rows instead of the expected %d", n, nrow);
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+    fail_if(n != 0, "verification select failed (%s)", strerror(errno));
+}
+END_TEST
+
+
+START_TEST(transaction_rollback)
+{
+    record_t *a;
+    query_t *r, rows[32];
+    int i,j,n;
+    int sts;
+    int found;
+
+    PREREQUISITE(delete_from_persons);
+
+    fail_unless(txdepth > 0, "actually there is no transaction");
+
+    sts = MQI_ROLLBACK(transactions[--txdepth]);
+
+    fail_if(sts < 0, "errno (%s)", strerror(errno));
+
+    n = MQI_SELECT(persons_select_columns, persons, MQI_ALL, rows);
+
+    fail_if(n < 0, "verification select failed (%s)", strerror(errno));
+
+    if (verbose)
+        print_rows(n, rows);
+
+    fail_if(n != MQI_DIMENSION(artists)-1, "mismatching row numbers: currently"
+            " %d supposed to be %d", n, MQI_DIMENSION(artists)-1);
+
+
+    for (i = 0;  i < n;  i++) {
+        r = rows + i;
+
+        for (found = 0, j = 0;  j < (int)MQI_DIMENSION(artists)-1;  j++) {
+            a = artists[j];
+
+            if (a->id == r->id &&
+                !strcmp(a->first_name, r->first_name) &&
+                !strcmp(a->family_name, r->family_name))
+            {
+                found = 1;
+                break;
+            }
+        }
+
+        fail_unless(found, "after rolling back can't find %s %s (id %u) "
+                    "any more", r->first_name, r->family_name, r->id);
+    }
+}
+END_TEST
+
+START_TEST(table_trigger)
+{
+    int sts;
+
+    PREREQUISITE(open_db);
+
+    sts = mqi_create_table_trigger(table_event_cb, TABLE_TRIGGER_DATA);
+
+    fail_if(sts < 0, "errno (%s)", strerror(errno));
+
+    PREREQUISITE(create_table_persons);
+
+    if (verbose)
+        print_triggers();
+
+    fail_unless(ntrigger == 1, "no callback after table creation");
+    fail_unless(triggers->event == mqi_table_created,
+                "wrong event type %d", triggers->event);
+    fail_unless(triggers->table.handle == persons,
+                "wrong table handle (0x%x vs. 0x%x)",
+                triggers->table.handle, persons);
+    fail_unless(!strcmp(triggers->table.name, "persons"),
+                "wrong table name ('%s' vs. 'persons')",
+                triggers->table.name);
+}
+END_TEST
+
+START_TEST(row_trigger)
+{
+    mqi_handle_t trh;
+    record_t *rec;
+    trigger_t *trig;
+    int sts;
+    int i;
+
+    PREREQUISITE(create_table_persons);
+
+    sts = mqi_create_transaction_trigger(transaction_event_cb,
+                                         TRANSACT_TRIGGER_DATA);
+
+    fail_if(sts < 0, "create transaction trigger failed: errno (%s)",
+            strerror(errno));
+
+    sts = mqi_create_row_trigger(persons, row_event_cb, ROW_TRIGGER_DATA,
+                                 persons_select_columns);
+
+    fail_if(sts < 0, "create row trigger failed: errno (%s)", strerror(errno));
+
+    trh = mqi_begin_transaction();
+
+    fail_if(trh == MQI_HANDLE_INVALID, "begin failed: errno(%s)",
+            strerror(errno));
+
+    PREREQUISITE(insert_into_persons);
+
+    sts = mqi_commit_transaction(trh);
+
+    fail_if(sts < 0, "commit failed: errno (%s)", strerror(errno));
+
+    if (verbose)
+        print_triggers();
+
+    fail_unless(ntrigger == rows_no_in_persons + 2,
+                "wrong number of callbacks (%d vs. %d)",
+                ntrigger, rows_no_in_persons);
+
+    for (i = 0;  i < ntrigger-2;  i++) {
+        trig = triggers + (i + 1);
+        rec  = artists[i];
+
+        fail_unless(trig->event == mqi_row_inserted,
+                    "wrong event type (%d vs %d) @ callback %d",
+                    trig->event, mqi_row_inserted, i);
+        fail_unless(trig->table.handle == persons,
+                    "wrong table handle (0x%x vs. 0x%x) @ callback %d",
+                    trig->table.handle, persons, i);
+        fail_unless(!strcmp(trig->table.name, "persons"),
+                    "wrong table name ('%s' vs. 'persons') @ callback %d",
+                    trig->table.name, persons, i);
+        fail_unless(trig->row.id == rec->id,
+                    "id column mismatch (%d vs %s) @ callback %d",
+                    trig->row.id, rec->id, i);
+        fail_unless(!strcmp(trig->row.first_name, rec->first_name),
+                    "first name mismatch ('%s' vs. '%s') @ callback %d",
+                    trig->row.first_name, rec->first_name);
+        fail_unless(!strcmp(trig->row.family_name, rec->family_name),
+                    "first name mismatch ('%s' vs. '%s') @ callback %d",
+                    trig->row.family_name, rec->family_name);
+    }
+
+}
+END_TEST
+
+
+
+START_TEST(column_trigger)
+{
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(1), MQI_STRING_VAR(elvis.family_name) ) MQI_AND
+        MQI_EQUAL( MQI_COLUMN(2), MQI_STRING_VAR(elvis.first_name ) )
+    );
+
+    static query_t kalle = {1, "Korhonen", "Kalle"};
+
+    mqi_handle_t trh;
+    trigger_t *trig;
+    int sts;
+    int i, n;
+
+    PREREQUISITE(insert_into_persons);
+
+    sts = mqi_create_column_trigger(persons, 1, column_event_cb,
+                                    COLUMN_TRIGGER_DATA,
+                                    persons_select_columns);
+
+    fail_if(sts < 0, "create column trigger failed: errno (%s)",
+            strerror(errno));
+
+    sts = mqi_create_column_trigger(persons, 2, column_event_cb,
+                                    COLUMN_TRIGGER_DATA,
+                                    persons_select_columns);
+
+    fail_if(sts < 0, "create column trigger failed: errno (%s)",
+            strerror(errno));
+
+    trh = mqi_begin_transaction();
+
+    fail_if(trh == MQI_HANDLE_INVALID, "begin failed: errno(%s)",
+            strerror(errno));
+
+    n = MQI_UPDATE(persons, persons_select_columns, &kalle, where);
+
+    fail_if(n  < 0, "update failed: errno (%s)", strerror(errno));
+    fail_if(n != 1, "updated %d row but supposed to just 1", n);
+
+    sts = mqi_commit_transaction(trh);
+
+    fail_if(sts < 0, "commit failed: errno (%s)", strerror(errno));
+
+    if (verbose)
+        print_triggers();
+
+    fail_unless(ntrigger == 2,
+                "wrong number of callbacks (%d vs. 2)",
+                ntrigger);
+
+    for (i = 0;  i < ntrigger;  i++) {
+        trig = triggers + i;
+
+        fail_unless(trig->event == mqi_column_changed,
+                    "wrong event type (%d vs %d) @ callback %d",
+                    trig->event, mqi_column_changed, i);
+        fail_unless(trig->table.handle == persons,
+                    "wrong table handle (0x%x vs. 0x%x) @ callback %d",
+                    trig->table.handle, persons, i);
+        fail_unless(!strcmp(trig->table.name, "persons"),
+                    "wrong table name ('%s' vs. 'persons') @ callback %d",
+                    trig->table.name, persons, i);
+        fail_unless(trig->row.id == kalle.id,
+                    "id column mismatch (%d vs %d) @ callback %d",
+                    trig->row.id, kalle.id, i);
+        fail_unless(!strcmp(trig->row.first_name, kalle.first_name),
+                    "first name mismatch ('%s' vs. '%s') @ callback %d",
+                    trig->row.first_name, kalle.first_name);
+        fail_unless(!strcmp(trig->row.family_name, kalle.family_name),
+                    "first name mismatch ('%s' vs. '%s') @ callback %d",
+                    trig->row.family_name, kalle.family_name);
+    }
+}
+END_TEST
+
+START_TEST(sequential_transactions)
+{
+    mqi_handle_t  trh;
+    int           sts, i;
+    const char   *kind;
+
+    PREREQUISITE(create_table_persons);
+
+    for (i = 0; i < nseq; i++) {
+        trh = mqi_begin_transaction();
+
+        fail_if(trh == MQI_HANDLE_INVALID,
+                "failed to create %d. transaction : errno (%s)",
+                i + 1, strerror(errno));
+
+        if (i & 0x1)
+            PREREQUISITE(delete_all_persons);
+        else
+            PREREQUISITE(insert_into_persons);
+
+        if (!(i & 0x3)) {
+            kind = "rollback";
+            sts  = mqi_rollback_transaction(trh);
+        }
+        else {
+            kind = "commit";
+            sts  = mqi_commit_transaction(trh);
+        }
+
+        fail_if(sts < 0, "%s failed: errno (%s)", kind, strerror(errno));
+    }
+}
+END_TEST
+
+
+START_TEST(nested_transactions)
+{
+    mqi_handle_t  txids[MQI_TXDEPTH_MAX - 1];
+    mqi_handle_t  trh;
+    int           sts, tx, i, cnt;
+    const char   *kind;
+
+    PREREQUISITE(create_table_persons);
+
+    if (nnest > (int)(sizeof(txids) / sizeof(txids[0])))
+        nnest = sizeof(txids) / sizeof(txids[0]);
+
+    for (cnt = 0; cnt < 16; cnt++) {
+        for (tx = 0; tx < nnest; tx++) {
+            trh = txids[tx] = mqi_begin_transaction();
+
+            fail_if(trh == MQI_HANDLE_INVALID,
+                    "couldn't create transaction: errno (%s)", strerror(errno));
+
+            for (i = 0; i < nseq; i++) {
+                if (i & 0x1)
+                    PREREQUISITE(delete_all_persons);
+                else
+                    PREREQUISITE(insert_into_persons);
+            }
+        }
+
+        for (tx = nnest - 1; tx >= 0; tx--) {
+            trh = txids[tx];
+
+            if (!(tx & 0x1) && 0) {
+                kind = "rollback";
+                sts = mqi_rollback_transaction(trh);
+            }
+            else {
+                kind = "commit";
+                sts = mqi_commit_transaction(trh);
+            }
+
+            fail_if(sts < 0, "%s %u failed: errno (%s)", kind, trh,
+                    strerror(errno));
+        }
+    }
+}
+END_TEST
+
+
+
+static Suite *libmqi_suite(void)
+{
+    Suite *s = suite_create("Murphy Query Interface - libmqi");
+    TCase *tc_basic = basic_tests();
+
+    suite_add_tcase(s, tc_basic);
+
+    return s;
+}
+
+static TCase *basic_tests(void)
+{
+    TCase *tc = tcase_create("basic tests");
+
+    tcase_add_test(tc, open_db);
+    tcase_add_test(tc, create_table_persons);
+    tcase_add_test(tc, table_handle);
+    tcase_add_test(tc, describe_persons);
+    tcase_add_test(tc, insert_into_persons);
+    tcase_add_test(tc, row_count_in_persons);
+    tcase_add_test(tc, insert_duplicate_into_persons);
+    tcase_add_test(tc, replace_in_persons);
+    tcase_add_test(tc, filtered_select_from_persons);
+    tcase_add_test(tc, full_select_from_persons);
+    tcase_add_test(tc, select_from_persons_by_index);
+    tcase_add_test(tc, update_in_persons);
+    tcase_add_test(tc, delete_from_persons);
+    tcase_add_test(tc, transaction_rollback);
+    tcase_add_test(tc, table_trigger);
+    tcase_add_test(tc, row_trigger);
+    tcase_add_test(tc, column_trigger);
+    tcase_add_test(tc, sequential_transactions);
+    tcase_add_test(tc, nested_transactions);
+
+    return tc;
+}
+
+static void print_rows(int n, query_t *rows)
+{
+    query_t *r;
+    int i;
+
+    printf("   id first name      family name     \n");
+    printf("--------------------------------------\n");
+
+    if (!n)
+        printf("no rows\n");
+    else {
+        for (i = 0; i < n;  i++) {
+            r = rows + i;
+            printf("%5d %-15s %-15s\n", r->id,
+                   r->first_name, r->family_name);
+        }
+    }
+
+    printf("--------------------------------------\n");
+}
+
+
+static void print_triggers(void)
+{
+    static char *separator = "+---------------+-------------------+"
+                             "-------------------------------------+"
+                             "--------------------------------------"
+                             "--------+\n";
+    trigger_t *trig;
+    enum {err, tra, tbl, row, col} t;
+    char *ev;
+    int i;
+
+    printf(separator);
+    printf("| trigger       |      table        |"
+           "      selected columns in row        |"
+           "    altered column                            |\n");
+    printf("| event         |  handle name      |"
+           "   id first_name      family_name    |"
+           " idx name         value                       |\n");
+    printf(separator);
+
+    if (!ntrigger) {
+        printf("|-<no events>---|-------------------|"
+               "-------------------------------------|"
+               "----------------------------------------------|\n");
+    }
+    else {
+        for (i = 0;  i < ntrigger;  i++) {
+            trig = triggers + i;
+
+            switch (trig->event) {
+            case mqi_column_changed:    t = col; ev = "column_changed";  break;
+            case mqi_row_inserted:      t = row; ev = "row_inserted";    break;
+            case mqi_row_deleted:       t = row; ev = "row_deleted";     break;
+            case mqi_table_created:     t = tbl; ev = "table_created";   break;
+            case mqi_table_dropped:     t = tbl; ev = "table_dropped";   break;
+            case mqi_transaction_start: t = tra; ev = "transact start";  break;
+            case mqi_transaction_end:   t = tra; ev = "transact end";    break;
+            default:                    t = err; ev = "<unknown>";       break;
+            }
+
+
+            printf("| %-14s", ev);
+
+            if (t == tbl || t == row || t == col)
+                printf("|%8x %-10s", trig->table.handle, trig->table.name);
+            else
+                printf("|                   ");
+
+            if (t == row || t == col)
+                printf("|%5d %-15s %-15s", trig->row.id, trig->row.first_name,
+                       trig->row.family_name);
+            else
+                printf("|                                     ");
+
+            if (t == col)
+                printf("| %3d %-12s %-28s", trig->col.index, trig->col.name,
+                       trig->col.value);
+            else
+                printf("|                                              ");
+
+            printf("|\n");
+        }
+    }
+
+    printf(separator);
+}
+
+static void transaction_event_cb(mqi_event_t *evt, void *user_data)
+{
+    mqi_event_type_t      event = evt->event;
+ /* mqi_transact_event_t *te    = &evt->transact; */
+    trigger_t            *trig;
+
+    if (ntrigger >= (int)MQI_DIMENSION(triggers)) {
+        if (verbose)
+            printf("test framework error: trigger log overflow\n");
+        return;
+    }
+
+    trig = triggers + ntrigger++;
+
+    if (event != mqi_transaction_start && event != mqi_transaction_end) {
+        if (verbose)
+            printf("invalid event %d for transaction trigger\n", event);
+        return;
+    }
+
+    if (user_data != TRANSACT_TRIGGER_DATA) {
+        if (verbose)
+            printf("invalid user_data %p for transaction trigger\n",
+                   user_data);
+        return;
+    }
+
+    trig->event = event;
+}
+
+static void table_event_cb(mqi_event_t *evt, void *user_data)
+{
+    mqi_event_type_t   event = evt->event;
+    mqi_table_event_t *te    = &evt->table;
+    trigger_t         *trig;
+
+    if (ntrigger >= (int)MQI_DIMENSION(triggers)) {
+        if (verbose)
+            printf("test framework error: trigger log overflow\n");
+        return;
+    }
+
+    trig = triggers + ntrigger++;
+
+    if (event != mqi_table_created && event != mqi_table_dropped) {
+        if (verbose)
+            printf("invalid event %d for table trigger\n", event);
+        return;
+    }
+
+    if (user_data != TABLE_TRIGGER_DATA) {
+        if (verbose)
+            printf("invalid user_data %p for table trigger\n", user_data);
+        return;
+    }
+
+
+    trig->event = event;
+    trig->table.handle = te->table.handle;
+    strncpy(trig->table.name, te->table.name,
+            MQI_DIMENSION(trig->table.name) - 1);
+}
+
+static void row_event_cb(mqi_event_t *evt, void *user_data)
+{
+    mqi_event_type_t  event = evt->event;
+    mqi_row_event_t  *re    = &evt->row;
+    trigger_t        *trig;
+    query_t          *row;
+
+    if (ntrigger >= (int)MQI_DIMENSION(triggers)) {
+        if (verbose)
+            printf("test framework error: trigger log overflow\n");
+        return;
+    }
+
+    trig = triggers + ntrigger++;
+
+    if (event != mqi_row_inserted && event != mqi_row_deleted) {
+        if (verbose)
+            printf("invalid event %d for row trigger\n", event);
+        return;
+    }
+
+    if (user_data != ROW_TRIGGER_DATA) {
+        if (verbose)
+            printf("invalid user_data %p for row trigger\n", user_data);
+        return;
+    }
+
+    if (!(row = (query_t *)re->select.data)) {
+        if (verbose)
+            printf("no selected data\n");
+        return;
+    }
+
+
+    trig->event = event;
+    trig->table.handle = re->table.handle;
+    strncpy(trig->table.name, re->table.name,
+            MQI_DIMENSION(trig->table.name) - 1);
+    trig->row.id = row->id;
+    strncpy(trig->row.first_name, row->first_name,
+            MQI_DIMENSION(trig->row.first_name) - 1);
+    strncpy(trig->row.family_name, row->family_name,
+            MQI_DIMENSION(trig->row.family_name) - 1);
+}
+
+static void column_event_cb(mqi_event_t *evt, void *user_data)
+{
+    mqi_event_type_t    event = evt->event;
+    mqi_column_event_t *ce    = &evt->column;
+    trigger_t          *trig;
+    query_t            *row;
+
+    if (ntrigger >= (int)MQI_DIMENSION(triggers)) {
+        if (verbose)
+            printf("test framework error: trigger log overflow\n");
+        return;
+    }
+
+    trig = triggers + ntrigger++;
+
+    if (event != mqi_column_changed) {
+        if (verbose)
+            printf("invalid event %d for column trigger\n", event);
+        return;
+    }
+
+    if (user_data != COLUMN_TRIGGER_DATA) {
+        if (verbose)
+            printf("invalid user_data %p for column trigger\n", user_data);
+        return;
+    }
+
+    if (!(row = (query_t *)ce->select.data)) {
+        if (verbose)
+            printf("no selected data\n");
+        return;
+    }
+
+
+    trig->event = event;
+    trig->table.handle = ce->table.handle;
+    strncpy(trig->table.name, ce->table.name,
+            MQI_DIMENSION(trig->table.name) - 1);
+    trig->row.id = row->id;
+    strncpy(trig->row.first_name, row->first_name,
+            MQI_DIMENSION(trig->row.first_name) - 1);
+    strncpy(trig->row.family_name, row->family_name,
+            MQI_DIMENSION(trig->row.family_name) - 1);
+    trig->col.index = ce->column.index;
+    strncpy(trig->col.name, ce->column.name,
+            MQI_DIMENSION(trig->col.name) - 1);
+
+#define PRINT_VALUE(fmt,t) \
+    snprintf(trig->col.value, MQI_DIMENSION(trig->col.value) - 1, \
+             fmt " => " fmt, ce->value.old.t, ce->value.new_.t)
+#define PRINT_INVALID \
+    snprintf(trig->col.value, MQI_DIMENSION(trig->col.value) - 1, \
+             "<invalid> => <invalid>")
+
+    switch(ce->value.type) {
+    case mqi_varchar:     PRINT_VALUE("'%s'" , varchar );     break;
+    case mqi_integer:     PRINT_VALUE("%d"   , integer );     break;
+    case mqi_unsignd:     PRINT_VALUE("%u"   , unsignd );     break;
+    case mqi_floating:    PRINT_VALUE("%.2lf", floating);     break;
+    case mqi_blob:        PRINT_INVALID;                      break;
+    default:              PRINT_INVALID;                      break;
+    }
+
+#undef PRINT_INVALID
+#undef PRINT_VALUE
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/murphy-db/tests/check-libmql.c b/src/murphy-db/tests/check-libmql.c
new file mode 100644 (file)
index 0000000..543c1f1
--- /dev/null
@@ -0,0 +1,985 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <libgen.h>
+
+#include <check.h>
+
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+
+
+#ifndef LOGFILE
+#define LOGFILE  "check_libmql.log"
+#endif
+
+#define PREREQUISITE(t)   t(_i)
+
+#define TRIGGER_DATA(idx) (void *)0xdeadbeef##idx
+#define TRANSACT_TRIGGER_DATA TRIGGER_DATA(1)
+#define TABLE_TRIGGER_DATA    TRIGGER_DATA(2)
+#define ROW_TRIGGER_DATA      TRIGGER_DATA(3)
+#define COLUMN_TRIGGER_DATA   TRIGGER_DATA(4)
+
+
+typedef struct {
+    const char  *sex;
+    const char  *first_name;
+    const char  *family_name;
+    uint32_t     id;
+    const char  *email;
+} record_t;
+
+static mqi_column_def_t persons_columns[] = {
+    {"sex"        , mqi_varchar,  6, 0},
+    {"family_name", mqi_varchar, 12, 0},
+    {"first_name" , mqi_varchar, 12, 0},
+    {"id"         , mqi_unsignd,  4, 0},
+    {"email"      , mqi_varchar, 24, 0}
+};
+static int persons_ncolumn = MQI_DIMENSION(persons_columns);
+
+static record_t persons_rows[] = {
+    {"male"  , "Chuck", "Norris" , 1100, "cno@texas.us"  },
+    {"male"  , "Gary", "Cooper"  ,  700, "gco@heaven.org"},
+    {"male"  , "Elvis", "Presley",  600, "epr@heaven.org"},
+    {"male"  , "Tom", "Cruise"   ,  500, "tcr@foo.com"   },
+    {"female", "Greta", "Garbo"  , 2000, "gga@heaven.org"},
+    {"female", "Rita", "Hayworth",   44, "rha@heaven.org"}
+};
+static int persons_nrow = MQI_DIMENSION(persons_rows);
+
+
+static int verbose;
+static struct {
+    mql_statement_t *begin;
+    mql_statement_t *commit;
+    mql_statement_t *rollback;
+    mql_statement_t *filtered_select;
+    mql_statement_t *full_select;
+    mql_statement_t *update;
+    mql_statement_t *delete;
+    mql_statement_t *insert;
+} persons;
+
+
+static Suite *libmql_suite(void);
+static TCase *basic_tests(void);
+
+static void transaction_event_cb(mql_result_t *, void *);
+static void table_event_cb(mql_result_t *, void *);
+static void row_event_cb(mql_result_t *, void *);
+static void column_event_cb(mql_result_t *, void *);
+
+
+
+int main(int argc, char **argv)
+{
+    Suite   *s  = libmql_suite();
+    SRunner *sr = srunner_create(s);
+    int      nf;
+    int      i;
+
+    for (i = 1;  i < argc;  i++) {
+        if (!strcmp("-v", argv[i]))
+            verbose = 1;
+        else if (!strcmp("-f", argv[i]))
+            srunner_set_fork_status(sr, CK_NOFORK);
+        else {
+            printf("Usage: %s [-h] [-v] [-f]\n"
+                   "  -h  prints this message\n"
+                   "  -v  sets verbose mode\n"
+                   "  -f  forces no-forking mode\n",
+                   basename(argv[0]));
+            exit(strcmp("-h", argv[i]) ? 1 : 0);
+        }
+    }
+
+    srunner_set_log(sr, LOGFILE);
+
+    srunner_run_all(sr, CK_NORMAL);
+
+    nf = srunner_ntests_failed(sr);
+
+    srunner_free(sr);
+    // suite_free(s);
+
+    return (nf == 0) ? 0 : 1;
+}
+
+
+START_TEST(open_db)
+{
+    int sts = mqi_open();
+
+    fail_if(sts, "db open test");
+}
+END_TEST
+
+
+
+START_TEST(create_table_persons)
+{
+    mql_result_t *r;
+
+    PREREQUISITE(open_db);
+
+    r = mql_exec_string(mql_result_string,
+                        "CREATE TEMPORARY TABLE persons ("
+                        "   sex          VARCHAR(6), "
+                        "   family_name  VARCHAR(12),"
+                        "   first_name   VARCHAR(12),"
+                        "   id           UNSIGNED,   "
+                        "   email        VARCHAR(24) "
+                        ")"
+    );
+
+    fail_unless(mql_result_is_success(r), "error: %s",
+                mql_result_error_get_message(r));
+
+    mql_result_free(r);
+}
+END_TEST
+
+
+
+START_TEST(describe_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_columns;
+    mqi_column_def_t *cd;
+    mql_result_t *r;
+    mqi_data_type_t type;
+    const char *name;
+    int length;
+    int i,n;
+
+    PREREQUISITE(create_table_persons);
+
+    r = mql_exec_string(rt, "DESCRIBE persons");
+
+    fail_unless(mql_result_is_success(r), "error: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        n = mql_result_columns_get_column_count(r);
+
+        fail_if(n  < 1, "invalid column count %d", n);
+        fail_if(n != persons_ncolumn, "coulumn count is %d but "
+                "it supposed to be %d", n, persons_ncolumn);
+
+        for (i = 0;   i < n;   i++) {
+            cd = persons_columns + i;
+            name = mql_result_columns_get_name(r, i);
+            type = mql_result_columns_get_type(r, i);
+            length = mql_result_columns_get_length(r, i);
+
+            fail_if(strcmp(name, cd->name), "column%d name mismatch "
+                    "('%s' vs. '%s')", i, cd->name, name);
+
+            fail_if(type != cd->type, "column%d type mismatch (%s vs. %s)",
+                    i, mqi_data_type_str(cd->type), mqi_data_type_str(type));
+
+            fail_if(length != cd->length, "column%d length mismatch "
+                    "(%d vs. %d)", i, cd->length, length);
+        }
+    }
+
+    mql_result_free(r);
+}
+END_TEST
+
+
+
+
+START_TEST(create_index_on_persons)
+{
+    static bool done;
+
+    mql_result_t *r;
+
+    if (!done) {
+        PREREQUISITE(create_table_persons);
+
+        r = mql_exec_string(mql_result_string,
+                    "CREATE INDEX ON persons (family_name, first_name)");
+
+        fail_unless(mql_result_is_success(r), "error: %s",
+                    mql_result_error_get_message(r));
+
+        done = true;
+    }
+}
+END_TEST
+
+
+
+START_TEST(insert_into_persons)
+{
+    mql_result_t *r;
+    record_t *p;
+    char statement[512];
+    int i;
+
+    PREREQUISITE(create_index_on_persons);
+
+    for (i = 0;  i < persons_nrow;  i++) {
+        p = persons_rows + i;
+
+        snprintf(statement, sizeof(statement),
+                 "INSERT INTO persons VALUES ('%s', '%s', '%s', %u, '%s')",
+                 p->sex, p->family_name, p->first_name, p->id, p->email);
+
+        r = mql_exec_string(mql_result_string, statement);
+
+        fail_unless(mql_result_is_success(r), "error @ row%d: %s",
+                    i, mql_result_error_get_message(r));
+    }
+}
+END_TEST
+
+
+START_TEST(make_persons)
+{
+    static int done;
+
+    if (!done) {
+        PREREQUISITE(insert_into_persons);
+        done = 1;
+    }
+}
+END_TEST
+
+START_TEST(precompile_transaction_statements)
+{
+#define TRID "transaction_1"
+
+    static char *string[] = {
+        "BEGIN "    TRID,
+        "COMMIT "   TRID,
+        "ROLLBACK " TRID
+    };
+
+    static mql_statement_t **stmnt[] = {
+        &persons.begin,
+        &persons.commit,
+        &persons.rollback
+    };
+
+    static int done;
+
+    int i;
+
+    if (!done) {
+        fail_unless(MQI_DIMENSION(string) == MQI_DIMENSION(stmnt),
+                    "internal error: dimension mismatch in %s()", __FILE__);
+
+        for (i = 0;  i < (int)MQI_DIMENSION(string);  i++) {
+            if (!(*(stmnt[i]) = mql_precompile(string[i]))) {
+                fail("precompilation error of '%s' (%s)",
+                     string[i], strerror(errno));
+            }
+        }
+    }
+
+#undef TRID
+}
+END_TEST
+
+
+START_TEST(precompile_filtered_person_select)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    stmnt = mql_precompile("SELECT id, first_name, family_name FROM persons"
+                           " WHERE id > %u & id <= %u");
+
+    fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+    persons.filtered_select = stmnt;
+}
+END_TEST
+
+
+
+START_TEST(precompile_full_person_select)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.full_select) {
+        stmnt = mql_precompile("SELECT id, first_name, family_name"
+                               " FROM persons");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.full_select = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(precompile_update_persons)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.update) {
+        stmnt = mql_precompile("UPDATE persons "
+                               "  SET family_name = %s,"
+                               "      first_name  = %s"
+                               "  WHERE id = %u");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.update = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(precompile_delete_from_persons)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.delete) {
+        stmnt = mql_precompile("DELETE FROM persons WHERE family_name = %s");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.delete = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(precompile_insert_into_persons)
+{
+    mql_statement_t *stmnt;
+
+    PREREQUISITE(make_persons);
+
+    if (!persons.insert) {
+        stmnt = mql_precompile("INSERT INTO persons VALUES ("
+                               " 'male', 'Baltzar','Veijo', 855, 'vba@pdf.org'"
+                               ")");
+
+        fail_if(!stmnt, "precompilation error (%s)", strerror(errno));
+
+        persons.insert = stmnt;
+    }
+}
+END_TEST
+
+
+
+START_TEST(exec_precompiled_filtered_select_from_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    int n;
+
+    PREREQUISITE(precompile_filtered_person_select);
+
+    if (mql_bind_value(persons.filtered_select, 1, mqi_unsignd,  200) < 0 ||
+        mql_bind_value(persons.filtered_select, 2, mqi_unsignd, 1100) < 0  )
+    {
+        fail("bind error (%s)", strerror(errno));
+    }
+
+    r = mql_exec_statement(rt, persons.filtered_select);
+
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        if ((n = mql_result_rows_get_row_count(r)) != 4)
+            fail("row number mismatch (4 vs. %d)", n);
+    }
+
+    mql_result_free(r);
+
+    mql_statement_free(persons.filtered_select);
+    persons.filtered_select = NULL;
+}
+END_TEST
+
+
+
+START_TEST(exec_precompiled_full_select_from_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    int n;
+
+    PREREQUISITE(precompile_full_person_select);
+
+    r = mql_exec_statement(rt, persons.full_select);
+
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        if ((n = mql_result_rows_get_row_count(r)) != persons_nrow)
+            fail("row number mismatch (%d vs. %d)", persons_nrow, n);
+    }
+
+    mql_result_free(r);
+
+    mql_statement_free(persons.full_select);
+    persons.full_select = NULL;
+}
+END_TEST
+
+START_TEST(exec_precompiled_update_persons)
+{
+    static uint32_t    id         = 2000;
+    static const char *new_first  = "Marilyn";
+    static const char *new_family = "Monroe";
+
+    PREREQUISITE(precompile_update_persons);
+
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    record_t *p;
+    const char *first;
+    const char *family;
+    int updated;
+    int i, n;
+
+    /* 2000: Greta Garbo => Marilyn Monroe */
+    if (mql_bind_value(persons.update, 1, mqi_string , new_family) < 0 ||
+        mql_bind_value(persons.update, 2, mqi_string ,  new_first) < 0 ||
+        mql_bind_value(persons.update, 3, mqi_unsignd,         id) < 0   )
+    {
+        fail("bind error (%s)", strerror(errno));
+    }
+
+
+    r = mql_exec_statement(mql_result_string, persons.update);
+
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    mql_result_free(r);
+
+    /* verification */
+    r = mql_exec_string(rt, "SELECT id, first_name, family_name FROM persons");
+
+    fail_unless(mql_result_is_success(r), "exec error @ verifying select: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        for (p = NULL, i = 0;  i < persons_nrow;  i++) {
+            if (persons_rows[i].id == id) {
+                p = persons_rows + i;
+                break;
+            }
+        }
+
+        n = mql_result_rows_get_row_count(r);
+
+        for (updated = 0, i = 0;   i < n;   i++) {
+            family = mql_result_rows_get_string(r, 1, i, NULL,0);
+            first  = mql_result_rows_get_string(r, 2, i, NULL,0);
+
+            if (p) {
+                fail_if(!strcmp(first, p->first_name), "found original "
+                        "first name '%s'", p->first_name);
+                fail_if(!strcmp(family, p->family_name), "found original "
+                        "family name '%s'", p->family_name);
+            }
+            else {
+                fail_if(!strcmp(first, new_first), "found new "
+                        "first name '%s'", first);
+                fail_if(!strcmp(family, new_family), "found new "
+                        "family name '%s'", family);
+            }
+
+            if (id == mql_result_rows_get_unsigned(r, 0, i)) {
+                if (strcmp(first, new_first) || strcmp(family, new_family)) {
+                    updated = 1;
+                }
+            }
+        }
+
+        if (p)
+            fail_unless(updated, "result is success but no actual update");
+        else
+            fail_unless(!updated, "update happened but it not supposed to");
+    }
+
+    mql_result_free(r);
+
+
+    mql_statement_free(persons.update);
+    persons.update = NULL;
+}
+END_TEST
+
+
+START_TEST(exec_precompiled_delete_from_persons)
+{
+    const char *del_family = "Cruise";
+
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    record_t *p;
+    uint32_t id;
+    const char *first;
+    const char *family;
+    int i,n;
+
+    PREREQUISITE(precompile_delete_from_persons);
+
+    /* delete Tom Cruise */
+    if (mql_bind_value(persons.delete, 1, mqi_string , del_family) < 0)
+        fail("bind error (%s)", strerror(errno));
+
+    r = mql_exec_statement(mql_result_string, persons.delete);
+
+    fail_unless(mql_result_is_success(r), "exec error: %s",
+                mql_result_error_get_message(r));
+
+    mql_result_free(r);
+
+
+    /* verification */
+    r = mql_exec_string(rt, "SELECT id, first_name, family_name FROM persons");
+
+    fail_unless(mql_result_is_success(r), "exec error @ verifying select: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        for (p = NULL, i = 0;  i < persons_nrow;  i++) {
+            if (!strcmp(persons_rows[i].family_name, del_family)) {
+                p = persons_rows + i;
+                break;
+            }
+        }
+
+        n = mql_result_rows_get_row_count(r);
+
+        for (i = 0;   i < n;   i++) {
+            id     = mql_result_rows_get_unsigned(r, 0, i);
+            first  = mql_result_rows_get_string(r, 1, i, NULL,0);
+            family = mql_result_rows_get_string(r, 2, i, NULL,0);
+
+            if (p) {
+                /* supposed to be deleted */
+                fail_if(id == p->id, "found id %u of the presumably "
+                        "deleted row", id);
+                fail_if(!strcmp(first, p->first_name), "found first name '%s' "
+                        "of the presumably deleted row", first);
+                fail_if(!strcmp(family, p->family_name), "found family name "
+                        "'%s' of the presumably deleted row", family);
+            }
+            else {
+                /* nothing supposed to be deleted */
+                fail_if(!strcmp(family, del_family), "found family name '%s'"
+                        "what not supposed to be there", family);
+            }
+        }
+   }
+
+    mql_result_free(r);
+
+    mql_statement_free(persons.delete);
+    persons.delete = NULL;
+}
+END_TEST
+
+
+
+START_TEST(exec_precompiled_insert_into_persons)
+{
+    mql_result_type_t rt = verbose ? mql_result_string : mql_result_rows;
+    mql_result_t *r;
+    record_t *p;
+    const char *first;
+    const char *family;
+    int inserted;
+    int i,n;
+
+    PREREQUISITE(precompile_insert_into_persons);
+
+
+    for (p = NULL, i = 0;  i < persons_nrow;  i++) {
+        if (!strcmp(persons_rows[i].family_name, "Baltzar") &&
+            !strcmp(persons_rows[i].first_name ,   "Veijo")   )
+        {
+            p = persons_rows + i;
+            break;
+        }
+    }
+
+    /* insert Veijo Baltzar */
+    r = mql_exec_statement(mql_result_string, persons.insert);
+
+    if (p)
+        fail_if(mql_result_is_success(r), "manage to insert a duplicate");
+    else {
+        fail_unless(mql_result_is_success(r), "exec error: %s",
+                    mql_result_error_get_message(r));
+    }
+
+    mql_result_free(r);
+
+
+    /* verification */
+    r = mql_exec_string(rt, "SELECT id, first_name, family_name FROM persons");
+
+    fail_unless(mql_result_is_success(r), "exec error @ verifying select: %s",
+                mql_result_error_get_message(r));
+
+    if (verbose)
+        printf("%s\n", mql_result_string_get(r));
+    else {
+        if (!p) {
+            n = mql_result_rows_get_row_count(r);
+
+            for (inserted = 0, i = 0;   i < n;   i++) {
+                first  = mql_result_rows_get_string(r, 1, i, NULL,0);
+                family = mql_result_rows_get_string(r, 2, i, NULL,0);
+
+                if (!strcmp(first, "Veijo") && !strcmp(family, "Baltzar")) {
+                    inserted = 1;
+                    break;
+                }
+            }
+
+            fail_unless(inserted, "Veijo does not seem to be an the artist");
+        }
+    }
+
+
+
+
+    mql_result_free(r);
+
+
+    mql_statement_free(persons.insert);
+    persons.insert = NULL;
+}
+END_TEST
+
+START_TEST(register_transaction_event_cb)
+{
+    int sts;
+
+    PREREQUISITE(open_db);
+
+    sts = mql_register_callback("transaction_event_cb", mql_result_string,
+                                transaction_event_cb, TRANSACT_TRIGGER_DATA);
+
+    fail_if(sts < 0, "failed to create 'table_event_cb': %s",
+            strerror(errno));
+}
+END_TEST
+
+
+START_TEST(register_table_event_cb)
+{
+    int sts;
+
+    sts = mql_register_callback("table_event_cb", mql_result_string,
+                                table_event_cb, TABLE_TRIGGER_DATA);
+
+    fail_if(sts < 0, "failed to create 'table_event_cb': %s",
+            strerror(errno));
+}
+END_TEST
+
+START_TEST(register_row_event_cb)
+{
+    int sts;
+
+    PREREQUISITE(make_persons);
+
+    sts = mql_register_callback("row_event_cb", mql_result_string,
+                                row_event_cb, ROW_TRIGGER_DATA);
+
+    fail_if(sts < 0, "failed to create 'row_event_cb': %s",
+            strerror(errno));
+}
+END_TEST
+
+START_TEST(register_column_event_cb)
+{
+    int sts;
+
+    PREREQUISITE(make_persons);
+
+    sts = mql_register_callback("column_event_cb", mql_result_string,
+                                column_event_cb, COLUMN_TRIGGER_DATA);
+
+    fail_if(sts < 0, "failed to create 'column_event_cb': %s",
+            strerror(errno));
+}
+END_TEST
+
+START_TEST(table_trigger)
+{
+    static char *mqlstr = "CREATE TRIGGER table_trigger"
+                          " ON TABLES CALLBACK table_event_cb";
+
+    mql_result_t *r;
+
+    PREREQUISITE(open_db);
+    PREREQUISITE(register_table_event_cb);
+
+    r = mql_exec_string(mql_result_dontcare, mqlstr);
+
+    fail_unless(mql_result_is_success(r),"failed to exec '%s': (%d) %s",mqlstr,
+                mql_result_error_get_code(r), mql_result_error_get_message(r));
+
+    PREREQUISITE(make_persons);
+}
+END_TEST
+
+START_TEST(row_trigger)
+{
+    static char *mqlstr = "CREATE TRIGGER row_trigger"
+                          " ON ROWS IN persons"
+                          " CALLBACK row_event_cb"
+                          " SELECT id, first_name, family_name";
+
+
+    mql_result_t *r;
+
+    PREREQUISITE(register_row_event_cb);
+    PREREQUISITE(precompile_transaction_statements);
+
+    r = mql_exec_statement(mql_result_string, persons.begin);
+
+    fail_unless(mql_result_is_success(r), "failed to begin transaction: %s",
+                strerror(errno));
+
+    r = mql_exec_string(mql_result_dontcare, mqlstr);
+
+    fail_unless(mql_result_is_success(r),"failed to exec '%s': (%d) %s",mqlstr,
+                mql_result_error_get_code(r), mql_result_error_get_message(r));
+
+    PREREQUISITE(exec_precompiled_insert_into_persons);
+    PREREQUISITE(exec_precompiled_delete_from_persons);
+
+    r = mql_exec_statement(mql_result_string, persons.commit);
+
+    fail_unless(mql_result_is_success(r), "failed to commit transaction: %s",
+                strerror(errno));
+}
+END_TEST
+
+START_TEST(column_trigger)
+{
+    static char *mqlstr = "CREATE TRIGGER column_trigger"
+                          " ON COLUMN first_name IN persons"
+                          " CALLBACK column_event_cb"
+                          " SELECT id, first_name, family_name";
+
+    mql_result_t *r;
+
+    PREREQUISITE(register_column_event_cb);
+    PREREQUISITE(precompile_transaction_statements);
+
+    r = mql_exec_statement(mql_result_string, persons.begin);
+
+    fail_unless(mql_result_is_success(r), "failed to begin transaction: %s",
+                strerror(errno));
+
+    r = mql_exec_string(mql_result_dontcare, mqlstr);
+
+    fail_unless(mql_result_is_success(r),"failed to exec '%s': (%d) %s",mqlstr,
+                mql_result_error_get_code(r), mql_result_error_get_message(r));
+
+    PREREQUISITE(exec_precompiled_update_persons);
+
+    r = mql_exec_statement(mql_result_string, persons.commit);
+
+    fail_unless(mql_result_is_success(r), "failed to commit transaction: %s",
+                strerror(errno));
+}
+END_TEST
+
+
+START_TEST(transaction_trigger)
+{
+    static char *mqlstr = "CREATE TRIGGER transaction_trigger ON TRANSACTIONS"
+                          " CALLBACK transaction_event_cb";
+
+    mql_result_t *r;
+
+    PREREQUISITE(register_transaction_event_cb);
+
+    r = mql_exec_string(mql_result_dontcare, mqlstr);
+
+    fail_unless(mql_result_is_success(r),"failed to exec '%s': (%d) %s",mqlstr,
+                mql_result_error_get_code(r), mql_result_error_get_message(r));
+
+    PREREQUISITE(column_trigger);
+}
+END_TEST
+
+static Suite *libmql_suite(void)
+{
+    Suite *s = suite_create("Murphy Query Language - libmql");
+    TCase *tc_basic = basic_tests();
+
+    suite_add_tcase(s, tc_basic);
+
+    return s;
+}
+
+
+static TCase *basic_tests(void)
+{
+    TCase *tc = tcase_create("basic tests");
+
+    tcase_add_test(tc, open_db);
+    tcase_add_test(tc, create_table_persons);
+    tcase_add_test(tc, describe_persons);
+    tcase_add_test(tc, create_index_on_persons);
+    tcase_add_test(tc, insert_into_persons);
+    tcase_add_test(tc, precompile_transaction_statements);
+    tcase_add_test(tc, precompile_filtered_person_select);
+    tcase_add_test(tc, precompile_full_person_select);
+    tcase_add_test(tc, precompile_update_persons);
+    tcase_add_test(tc, precompile_delete_from_persons);
+    tcase_add_test(tc, precompile_insert_into_persons);
+    tcase_add_test(tc, exec_precompiled_filtered_select_from_persons);
+    tcase_add_test(tc, exec_precompiled_full_select_from_persons);
+    tcase_add_test(tc, exec_precompiled_update_persons);
+    tcase_add_test(tc, exec_precompiled_delete_from_persons);
+    tcase_add_test(tc, exec_precompiled_insert_into_persons);
+    tcase_add_test(tc, register_transaction_event_cb);
+    tcase_add_test(tc, register_table_event_cb);
+    tcase_add_test(tc, register_row_event_cb);
+    tcase_add_test(tc, register_column_event_cb);
+    tcase_add_test(tc, table_trigger);
+    tcase_add_test(tc, row_trigger);
+    tcase_add_test(tc, column_trigger);
+    tcase_add_test(tc, transaction_trigger);
+
+    return tc;
+}
+
+
+static void transaction_event_cb(mql_result_t *result, void *user_data)
+{
+    MQI_UNUSED(user_data);
+
+    if (result->type == mql_result_string) {
+        if (verbose)
+            printf("---\n%s\n", mql_result_string_get(result));
+    }
+    else if (result->type == mql_result_event) {
+    }
+    else {
+        if (verbose)
+            printf("%s: invalid result type %d\n", __FUNCTION__, result->type);
+    }
+}
+
+static void table_event_cb(mql_result_t *result, void *user_data)
+{
+    MQI_UNUSED(user_data);
+
+    if (result->type == mql_result_string) {
+        if (verbose)
+            printf("---\n%s\n", mql_result_string_get(result));
+    }
+    else if (result->type == mql_result_event) {
+    }
+    else {
+        if (verbose)
+            printf("%s: invalid result type %d\n", __FUNCTION__, result->type);
+    }
+}
+
+static void row_event_cb(mql_result_t *result, void *user_data)
+{
+    MQI_UNUSED(user_data);
+
+    if (result->type == mql_result_string) {
+        if (verbose)
+            printf("---\n%s\n", mql_result_string_get(result));
+    }
+    else if (result->type == mql_result_event) {
+    }
+    else {
+        if (verbose)
+            printf("%s: invalid result type %d\n", __FUNCTION__, result->type);
+    }
+}
+
+static void column_event_cb(mql_result_t *result, void *user_data)
+{
+    MQI_UNUSED(user_data);
+
+    if (result->type == mql_result_string) {
+        if (verbose)
+            printf("---\n%s\n", mql_result_string_get(result));
+    }
+    else if (result->type == mql_result_event) {
+    }
+    else {
+        if (verbose)
+            printf("%s: invalid result type %d\n", __FUNCTION__, result->type);
+    }
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/Makefile b/src/plugins/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/plugins/console-protocol.h b/src/plugins/console-protocol.h
new file mode 100644 (file)
index 0000000..9d49cec
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_CONSOLE_PROTOCOL_H__
+#define __MURPHY_CONSOLE_PROTOCOL_H__
+
+#define MRP_CONSOLE_INPUT  0x1
+#define MRP_CONSOLE_OUTPUT 0x2
+#define MRP_CONSOLE_PROMPT 0x3
+#define MRP_CONSOLE_BYE    0x4
+
+
+#endif /* __MURPHY_CONSOLE_PROTOCOL_H__ */
diff --git a/src/plugins/console/Makefile b/src/plugins/console/Makefile
new file mode 100644 (file)
index 0000000..efba399
--- /dev/null
@@ -0,0 +1,13 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
+
+%.crt:
+       cert="$@";                                                          \
+       make -f /etc/ssl/certs/Makefile $@ &&                               \
+       mv $${cert%.crt}.key $${cert%.crt}.key.protected &&                 \
+       openssl rsa -in $${cert%.crt}.key.protected -out $${cert%.crt}.key
diff --git a/src/plugins/console/console.html b/src/plugins/console/console.html
new file mode 100644 (file)
index 0000000..56c50f7
--- /dev/null
@@ -0,0 +1,92 @@
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">
+
+<style type="text/css">
+textarea.console_input {
+    font-size: 12pt;
+    font-family: monospace;
+    background-color: black;
+    padding: 2px;
+    margin: 0 0 0 0;
+    color: white;
+    resize: none;
+    overflow: visible;
+}
+
+textarea.console_output {
+    font-size: 12pt;
+    font-family: monospace;
+    background-color: black;
+    padding: 2px;
+    margin: 0 0 0 0;
+    color: white;
+    resize: none;
+}
+</style>
+
+<script type="text/javascript" src="console.js"></script>
+
+<title>Murphy Console (disconnected)</title>
+</head>
+
+
+<body onLoad="setupConsole();">
+
+<script type="text/javascript">
+
+var mrpc = null;
+
+function setupConsole () {
+    var cmds = {
+        connect:    cmd_connect,
+        disconnect: cmd_disconnect,
+        resize:     cmd_resize
+    };
+
+    mrpc = new MrpConsole("console_div", cmds);
+    var addr = mrpc.socketUri(document.URL);
+
+    console.log("Trying to connect to Murphy @ " + addr);
+
+    mrpc.connect(addr, "murphy");
+    mrpc.focus();
+
+    mrpc.onconnected = function () {
+        document.title = "Murphy Console @ " + addr;
+        mrpc.append("Connection to Murphy console established.\n");
+    };
+
+    mrpc.onclosed = function () {
+        document.title = "Murphy Console (disconnected)";
+        mrpc.append("Murphy console connection closed.\n");
+        mrpc.append("Use 'connect' to try to reconnect.\n");
+    };
+}
+
+
+function cmd_connect(args) {
+    var addr;
+
+    addr = mrpc.socketUri(document.URL);
+    console.log("Trying to reconnect...");
+    mrpc.connect(addr, "murphy");
+}
+
+
+function cmd_disconnect() {
+
+    console.log("Disconnecting...");
+    addr = mrpc.disconnect();
+}
+
+
+function cmd_resize(args) {
+    mrpc.resize(args[0], args[1]);
+}
+
+
+</script>
+
+<div id="console_div"></div>
+</body></html>
diff --git a/src/plugins/console/console.js b/src/plugins/console/console.js
new file mode 100644 (file)
index 0000000..f3b6f4c
--- /dev/null
@@ -0,0 +1,451 @@
+/*
+ * An Ode to My Suckage in Javascript...
+ *
+ * It'd be nice if someone wrote a relatively simple readline-like + output
+ * javascript package (ie. not a full VT100 terminal emulator like termlib).
+ */
+
+
+/*
+ * custom console error type
+ */
+
+function MrpConsoleError(message) {
+    this.name    = "Murphy Console Error";
+    this.message = message;
+}
+
+
+/** Create a Murphy console, put it after next_elem_id. */
+function MrpConsole(next_elem_id, user_commands) {
+    var sck, input, output, div, next_elem;
+
+    this.reset();
+    this.commands = user_commands;
+
+    /* create output text area */
+    output = document.createElement("textarea");
+    output.console = this;
+    this.output    = output;
+
+    output.setAttribute("class"     , "console_output");
+    output.setAttribute("cols"      , 80);
+    output.setAttribute("rows"      , 25);
+    output.setAttribute("readonly"  , true);
+    output.setAttribute("disabled"  , true);
+    output.setAttribute("spellcheck", false);
+    output.nline   = 0;
+    output.onfocus = function () { return false; }
+
+    /* create input text area, hook up input handler */
+    input = document.createElement("textarea");
+    input.console = this;
+    this.input    = input;
+
+    input.setAttribute("class"     , "console_input");
+    input.setAttribute("cols"      , 81);
+    input.setAttribute("rows"      ,  1);
+    input.setAttribute("spellcheck", false);
+    input.setAttribute("autofocus" , "autofocus");
+
+    input.onkeyup    = this.onkeyup;
+    input.onkeypress = this.onkeypress;
+
+    next_elem = document.getElementById(next_elem_id);
+
+    if (!next_elem)
+        throw new MrpConsoleError("element " + next_elem_id + " not found");
+
+    div = document.createElement("div");
+    div.appendChild(output);
+    div.appendChild(input);
+
+    /* insert console div to document */
+    document.body.insertBefore(div, next_elem);
+
+    this.setInput("");
+}
+
+
+/** Reset/initialize internal state to the disconnected defaults. */
+MrpConsole.prototype.reset = function () {
+    this.connected = false;
+    this.server    = null;
+    this.sck       = null;
+
+    this.history    = new Array ();
+    this.histidx    = 0;
+    if (!this.input)
+        this.prompt     = "disconnected";
+    else
+        this.setPrompt("disconnected");
+}
+
+
+/** Resize the console. */
+MrpConsole.prototype.resize = function (width, height) {
+    if (width && width > 0) {
+        this.output.cols = width;
+        this.input.cols  = width;
+    }
+    if (height && height > 0)
+        this.output.rows = height;
+}
+
+
+/** Get the focus to the console. */
+MrpConsole.prototype.focus = function () {
+    if (this.input)
+        this.input.focus();
+}
+
+
+/** Write output to the console, replacing its current contents. */
+MrpConsole.prototype.write = function (text, noscroll) {
+    var out = this.output;
+
+    out.value = text;
+    out.nline = text.split("\n").length;
+
+    if (!noscroll)
+        this.scrollBottom();
+}
+
+
+/** Append output to the console. */
+MrpConsole.prototype.append = function (text, noscroll) {
+    var out = this.output;
+
+    out.value += text;
+    out.nline += text.split("\n").length;
+
+    if (!noscroll)
+        this.scrollBottom();
+}
+
+
+/** Set the content of the input field to 'prompt> text'. */
+MrpConsole.prototype.setInput = function (text) {
+    this.input.value = this.prompt + "> " + text;
+}
+
+
+/** Get the current input value (without the prompt). */
+MrpConsole.prototype.getInput = function () {
+    if (this.input)
+        return this.input.value.slice(this.prompt.length + 2).split("\n")[0];
+    else
+        return "";
+}
+
+
+/** Set the input prompt to the given value. */
+MrpConsole.prototype.setPrompt = function (prompt) {
+    var value = this.getInput();
+
+    if (!this.input)
+        this.prompt = prompt;
+    else {
+        this.prompt = prompt;
+        this.input.value = this.prompt + "> " + value;
+    }
+}
+
+
+/** Scroll the output window up or down the given amount of lines. */
+MrpConsole.prototype.scroll = function (amount) {
+    var out = this.output;
+    var pxl = (out.nline ? (out.scrollHeight / out.nline) : 0);
+    var top = out.scrollTop + (amount * pxl);
+
+    if (top < 0)
+        top = 0;
+    if (top > out.scrollHeight)
+        top = out.scrollHeight;
+
+    out.scrollTop = top;
+}
+
+
+/** Scroll the output window up or down by a 'page'. */
+MrpConsole.prototype.scrollPage = function (dir) {
+    var out   = this.output;
+    var nline = 2 * 25 / 3;
+
+    if (dir < 0)
+        dir = -1;
+    else
+        dir = +1;
+
+    this.scroll(dir * nline);
+}
+
+
+/** Scroll to the bottom. */
+MrpConsole.prototype.scrollBottom = function () {
+    this.output.scrollTop = this.output.scrollHeight;
+}
+
+
+/** Add a new entry to the history. */
+MrpConsole.prototype.historyAppend = function (entry) {
+    if (entry.length > 0) {
+        this.history.push(entry);
+        this.histidx = this.history.length;
+
+        this.setInput("");
+    }
+}
+
+
+/** Go to the previous history entry. */
+MrpConsole.prototype.historyShow = function (dir) {
+    var idx = this.histidx + dir;
+
+    if (0 <= idx && idx < this.history.length) {
+        if (this.histidx == this.history.length &&
+            this.input.value.length > 0) {
+            /* Hmm... autoinsert to history, not the Right Thing To Do... */
+            this.historyAppend(this.getInput());
+        }
+
+        this.histidx = idx;
+        this.setInput(this.history[this.histidx]);
+    }
+    else if (idx >= this.history.length) {
+        this.histidx = this.history.length;
+        this.setInput("");
+    }
+}
+
+
+/** Make sure the input position never enters the prompt. */
+MrpConsole.prototype.checkInputPosition = function () {
+    var pos = this.input.selectionStart;
+
+    if (pos <= this.prompt.length + 2) {
+        this.input.selectionStart = this.prompt.length + 2;
+        this.input.selectionEnd   = this.prompt.length + 2;
+        return false;
+    }
+    else
+        return true;
+}
+
+
+/** Key up handler. */
+MrpConsole.prototype.onkeyup = function (e) {
+    var c = this.console;
+    var l;
+
+    /*console.log("got key " + e.which);*/
+
+    switch (e.keyCode) {
+    case e.DOM_VK_RETURN:
+        if (c.input.value.length > c.prompt.length + 2) {
+            l = c.getInput();
+            c.historyAppend(l);
+            c.processCmd(l);
+            c.setInput("");
+        }
+        break;
+    case e.DOM_VK_PAGE_UP:
+        if (e.shiftKey)
+            c.scrollPage(-1);
+        break;
+    case e.DOM_VK_PAGE_DOWN:
+        if (e.shiftKey)
+            c.scrollPage(+1);
+        break;
+
+    case e.DOM_VK_UP:
+        if (!e.shiftKey)
+            c.historyShow(-1);
+        else
+            c.scroll(-1);
+        break;
+    case e.DOM_VK_DOWN:
+        if (!e.shiftKey)
+            c.historyShow(+1);
+        else
+            c.scroll(+1);
+        break;
+    case e.DOM_VK_LEFT:
+    case e.DOM_VK_BACK_SPACE:
+        if (!c.checkInputPosition())
+            return false;
+        break;
+    }
+
+    return true;
+}
+
+
+/** Key-press handler. */
+MrpConsole.prototype.onkeypress = function (e) {
+    var c = this.console;
+    var rows;
+
+    switch (e.which) {
+    case e.DOM_VK_LEFT:
+    case e.DOM_VK_BACK_SPACE:
+        if (!c.checkInputPosition())
+            return false;
+    }
+
+    rows = Math.floor(1 + (c.input.value.length / c.input.cols));
+
+    if (c.input.rows < rows)
+        c.input.rows = rows;
+    else if (c.input.rows > rows)
+        c.input.rows = rows;
+
+    return true;
+}
+
+
+/** Connect to the Murphy daemon running at the given address. */
+MrpConsole.prototype.connect = function (address) {
+    var c = this.console;
+
+    if (this.connected)
+        throw new MrpConsoleError("already connected to " + this.address);
+    else {
+        this.server    = address;
+        this.connected = false;
+
+        this.setPrompt("connecting");
+
+        if (typeof MozWebSocket != "undefined")
+            sck = new MozWebSocket(this.server, "murphy");
+        else
+            sck = new WebSocket(this.server, "murphy");
+
+        this.sck      = sck;
+        sck.console   = this;
+        sck.onopen    = this.sckconnect;
+        sck.onclose   = this.sckclosed;
+        sck.onerror   = this.sckerror;
+        sck.onmessage = this.sckmessage;
+    }
+}
+
+
+/** Close the console connection. */
+MrpConsole.prototype.disconnect = function () {
+    if (this.connected) {
+        this.sck.close();
+    }
+}
+
+
+/** Connection established event handler. */
+MrpConsole.prototype.sckconnect = function () {
+    var c = this.console;
+
+    c.connected = true;
+    c.setPrompt("connected");
+
+    if (c.onconnected)
+        c.onconnected();
+}
+
+
+/** Connection shutdown event handler. */
+MrpConsole.prototype.sckclosed = function () {
+    var c = this.console;
+
+    console.log("socket closed");
+
+    c.reset();
+
+    if (c.onclosed)
+        c.onclosed();
+}
+
+
+/** Socket error event handler. */
+MrpConsole.prototype.sckerror = function (e) {
+    var c = this.console;
+
+    c.reset();
+
+    if (c.onerror)
+        c.onerror();
+
+    if (c.onclosed)
+        c.onclosed();
+}
+
+
+/** Socket message event handler. */
+MrpConsole.prototype.sckmessage = function (message) {
+    var c   = this.console;
+    var msg = JSON.parse(message.data);
+
+    if (msg.prompt)
+        c.setPrompt(msg.prompt);
+    else {
+        if (msg.output) {
+            c.append(msg.output);
+        }
+    }
+}
+
+
+/** Send a request to the server. */
+MrpConsole.prototype.send_request = function (req) {
+    var sck = this.sck;
+
+    sck.send(JSON.stringify(req));
+}
+
+
+/** Process a command entered by the user. */
+MrpConsole.prototype.processCmd = function (cmd) {
+    var c, l, cb, args;
+
+    for (c in this.commands) {
+        l  = c.length;
+        cb = this.commands[c];
+        if (cmd.substring(0, l) == c && (cmd.length == l ||
+                                         cmd.substring(l, l + 1) == ' ')) {
+            args = cmd.split(' ').splice(1);
+
+            this.commands[c](args);
+
+            return;
+        }
+    }
+
+    this.append(this.prompt + "> " + cmd + "\n");
+    this.send_request({ input: cmd });
+
+    if (cmd == 'help') {
+        if (this.commands && Object.keys(this.commands).length > 0) {
+            this.append("Web console commands:\n");
+            for (c in this.commands) {
+                this.append("    " + c + "\n");
+            }
+        }
+        else
+            this.append("No Web console commands.");
+    }
+}
+
+
+/** Determine a WebSocket URI based on an HTTP URI. */
+MrpConsole.prototype.socketUri = function (http_uri) {
+    var proto, colon, rest;
+
+    colon = http_uri.indexOf(':');           /* get first colon */
+    proto = http_uri.substring(0, colon);    /* get protocol */
+    rest  = http_uri.substring(colon + 3);   /* get URI sans protocol:// */
+    addr  = rest.split("/")[0];              /* strip URI path from address */
+
+    switch (proto) {
+    case "http":  return "ws://"  + addr;
+    case "https": return "wss://" + addr;
+    default:      return null;
+    }
+}
diff --git a/src/plugins/console/plugin-console.c b/src/plugins/console/plugin-console.c
new file mode 100644 (file)
index 0000000..13fa207
--- /dev/null
@@ -0,0 +1,821 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <murphy/common.h>
+#include <murphy/core.h>
+
+#include "config.h"
+
+#ifdef WEBSOCKETS_ENABLED
+#    include <murphy/common/wsck-transport.h>
+#    include <murphy/common/json.h>
+#endif
+
+#include <murphy/plugins/console-protocol.h>
+
+#define DEFAULT_ADDRESS "unxs:@murphy-console"    /* default console address */
+
+#ifdef MURPHY_DATADIR                             /* default content dir */
+#    define DEFAULT_HTTPDIR MURPHY_DATADIR"/webconsole"
+#else
+#    define DEFAULT_HTTPDIR "/usr/share/murphy/webconsole"
+#endif
+
+enum {
+    DEBUG_NONE    = 0x0,
+    DEBUG_FUNC    = 0x1,
+    DEBUG_FILE    = 0x2,
+    DEBUG_LINE    = 0x4,
+    DEBUG_DEFAULT = DEBUG_FUNC
+};
+
+
+/*
+ * an active console instance
+ */
+
+typedef struct {
+    mrp_console_t   *mc;                 /* associated murphy console */
+    mrp_transport_t *t;                  /* associated transport */
+    mrp_sockaddr_t   addr;               /* for temp. datagram 'connection' */
+    socklen_t        alen;               /* address length if any */
+    int              id;                 /* console ID for log redirection */
+    int              dbgmeta;            /* debug metadata to show */
+} console_t;
+
+
+/*
+ * console plugin data
+ */
+
+typedef struct {
+    const char      *address;            /* console address */
+    mrp_transport_t *t;                  /* transport we're listening on */
+    mrp_context_t   *ctx;                /* murphy context */
+    mrp_list_hook_t  clients;            /* active console clients */
+    mrp_sockaddr_t   addr;               /* resolved transport address */
+    socklen_t        alen;               /* address length */
+    console_t       *c;                  /* datagram console being served */
+    const char      *httpdir;            /* WRT console agent directory */
+    const char      *sslcert;            /* path to SSL certificate */
+    const char      *sslpkey;            /* path to SSL private key */
+    const char      *sslca;              /* path to SSL CA */
+} data_t;
+
+
+
+static int next_id = 1;
+
+static ssize_t write_req(mrp_console_t *mc, void *buf, size_t size)
+{
+    console_t *c = (console_t *)mc->backend_data;
+    mrp_msg_t *msg;
+    uint16_t   tag, type;
+    uint32_t   len;
+
+    tag  = MRP_CONSOLE_OUTPUT;
+    type = MRP_MSG_FIELD_BLOB;
+    len  = size;
+    msg  = mrp_msg_create(tag, type, len, buf, NULL);
+
+    if (msg != NULL) {
+        mrp_transport_send(c->t, msg);
+        mrp_msg_unref(msg);
+
+        return size;
+    }
+    else
+        return -1;
+}
+
+
+static void logger(void *data, mrp_log_level_t level, const char *file,
+                   int line, const char *func, const char *format, va_list ap)
+{
+    console_t  *c = (console_t *)data;
+    va_list     cp;
+    const char *prefix;
+    char        buf[256], lnstr[64];
+
+    MRP_UNUSED(file);
+    MRP_UNUSED(line);
+    MRP_UNUSED(func);
+
+    switch (level) {
+    case MRP_LOG_ERROR:   prefix = "[log] E: "; break;
+    case MRP_LOG_WARNING: prefix = "[log] W: "; break;
+    case MRP_LOG_INFO:    prefix = "[log] I: "; break;
+    case MRP_LOG_DEBUG:
+        if (c->dbgmeta & DEBUG_LINE)
+            snprintf(lnstr, sizeof(lnstr), ":%d", line);
+        else
+            lnstr[0] = '\0';
+        snprintf(buf, sizeof(buf), "[log] D: %s%s%s%s%s%s%s",
+                 c->dbgmeta ? "[" : "",
+                 c->dbgmeta & DEBUG_FUNC ? func  : "",
+                 c->dbgmeta & DEBUG_FILE ? "@"   : "",
+                 c->dbgmeta & DEBUG_FILE ? file  : "",
+                 c->dbgmeta & DEBUG_LINE ? lnstr : "",
+                 c->dbgmeta ? "]" : "",
+                 c->dbgmeta ? " " : "");
+        prefix = buf;
+        break;
+    default:              prefix = "[log] ?: ";
+    }
+
+    va_copy(cp, ap);
+    mrp_console_printf(c->mc, "%s", prefix);
+    mrp_console_vprintf(c->mc, format, cp);
+    mrp_console_printf(c->mc, "\n");
+    va_end(cp);
+}
+
+
+static void debug_cb(mrp_console_t *mc, void *user_data, int argc, char **argv)
+{
+    console_t  *c = (console_t *)mc->backend_data;
+    int         debug;
+    const char *p, *n;
+    int         i, l;
+
+    MRP_UNUSED(user_data);
+
+    debug = 0;
+    for (i = 2; i < argc; i++) {
+        p = argv[i];
+        while (p && *p) {
+            if ((n = strchr(p, ',')) != NULL)
+                l = n - p;
+            else
+                l = strlen(p);
+
+            if      (!strncmp(p, "function", l) ||
+                     !strncmp(p, "func"    , l)) debug |= DEBUG_FUNC;
+            else if (!strncmp(p, "file"    , l)) debug |= DEBUG_FILE;
+            else if (!strncmp(p, "line"    , l)) debug |= DEBUG_LINE;
+            else
+                mrp_log_warning("Unknown console debug flag '%*.*s'.", l, l, p);
+
+            if ((p = n) != NULL)
+                p++;
+        }
+    }
+
+    c->dbgmeta = debug & ((debug & ~DEBUG_LINE) ? -1 : ~DEBUG_LINE);
+
+    if (c->dbgmeta != debug)
+        mrp_log_warning("Orphan console debug flag 'line' forced off.");
+}
+
+
+static void register_logger(console_t *c)
+{
+    char name[32];
+
+    if (!c->id)
+        return;
+
+    snprintf(name, sizeof(name), "console/%d", c->id);
+    mrp_log_register_target(name, logger, c);
+}
+
+
+static void unregister_logger(console_t *c)
+{
+    char name[32];
+
+    if (!c->id)
+        return;
+
+    snprintf(name, sizeof(name), "console/%d", c->id);
+    mrp_log_unregister_target(name);
+}
+
+
+static void set_prompt_req(mrp_console_t *mc, const char *prompt)
+{
+    console_t *c = (console_t *)mc->backend_data;
+    mrp_msg_t *msg;
+    uint16_t   tag, type;
+
+    tag  = MRP_CONSOLE_PROMPT;
+    type = MRP_MSG_FIELD_STRING;
+    msg  = mrp_msg_create(tag, type, prompt, NULL);
+
+    if (msg != NULL) {
+        mrp_transport_send(c->t, msg);
+        mrp_msg_unref(msg);
+    }
+}
+
+
+static void free_req(void *backend_data)
+{
+    mrp_free(backend_data);
+}
+
+
+static void recv_cb(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    console_t       *c = (console_t *)user_data;
+    mrp_msg_field_t *f;
+    char            *input;
+    size_t           size;
+
+    MRP_UNUSED(t);
+
+    if ((f = mrp_msg_find(msg, MRP_CONSOLE_INPUT)) != NULL) {
+        if (f->type == MRP_MSG_FIELD_BLOB) {
+            input = f->str;
+            size  = f->size[0];
+
+            if (size > 0) {
+                MRP_CONSOLE_BUSY(c->mc, {
+                        c->mc->evt.input(c->mc, input, size);
+                    });
+
+                c->mc->check_destroy(c->mc);
+            }
+
+            return;
+        }
+    }
+
+    mrp_log_warning("Ignoring malformed message from console/%d...", c->id);
+}
+
+
+static void recvfrom_cb(mrp_transport_t *t, mrp_msg_t *msg,
+                        mrp_sockaddr_t *addr, socklen_t alen, void *user_data)
+{
+    console_t       *c = (console_t *)user_data;
+    mrp_sockaddr_t   obuf;
+    socklen_t        olen;
+    mrp_msg_field_t *f;
+    char            *input;
+    size_t           size;
+
+    MRP_UNUSED(t);
+
+    if ((f = mrp_msg_find(msg, MRP_CONSOLE_INPUT)) != NULL) {
+        if (f->type == MRP_MSG_FIELD_BLOB) {
+            input = f->str;
+            size  = f->size[0];
+
+            if (size > 0) {
+                mrp_sockaddr_cpy(&obuf, &c->addr, olen = c->alen);
+                mrp_sockaddr_cpy(&c->addr, addr, c->alen = alen);
+                mrp_transport_connect(t, addr, alen);
+
+                MRP_CONSOLE_BUSY(c->mc, {
+                        c->mc->evt.input(c->mc, input, size);
+                    });
+
+                c->mc->check_destroy(c->mc);
+
+
+                mrp_transport_disconnect(t);
+
+                if (olen) {
+                    mrp_transport_connect(t, &obuf, olen);
+                    mrp_sockaddr_cpy(&c->addr, &obuf, c->alen = olen);
+                }
+
+                return;
+            }
+        }
+    }
+
+    mrp_log_warning("Ignoring malformed message from console/%d...", c->id);
+}
+
+
+/*
+ * generic stream transport
+ */
+
+#define stream_write_req      write_req
+#define stream_set_prompt_req set_prompt_req
+#define stream_free_req       free_req
+#define stream_recv_cb        recv_cb
+
+static void stream_close_req(mrp_console_t *mc)
+{
+    console_t *c = (console_t *)mc->backend_data;
+
+    if (c->t != NULL) {
+        mrp_transport_disconnect(c->t);
+        mrp_transport_destroy(c->t);
+        unregister_logger(c);
+
+        c->t = NULL;
+    }
+}
+
+
+static void stream_connection_cb(mrp_transport_t *lt, void *user_data)
+{
+    static mrp_console_req_t req;
+
+    data_t    *data = (data_t *)user_data;
+    console_t *c;
+    int        flags;
+
+    if ((c = mrp_allocz(sizeof(*c))) != NULL) {
+        flags = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+        c->t  = mrp_transport_accept(lt, c, flags);
+
+        if (c->t != NULL) {
+            req.write      = stream_write_req;
+            req.close      = stream_close_req;
+            req.free       = stream_free_req;
+            req.set_prompt = stream_set_prompt_req;
+
+            c->mc = mrp_create_console(data->ctx, &req, c);
+
+            if (c->mc != NULL) {
+                c->id      = next_id++;
+                c->dbgmeta = DEBUG_DEFAULT;
+                register_logger(c);
+
+                return;
+            }
+            else {
+                mrp_transport_destroy(c->t);
+                c->t = NULL;
+            }
+        }
+    }
+}
+
+
+static void stream_closed_cb(mrp_transport_t *t, int error, void *user_data)
+{
+    console_t *c = (console_t *)user_data;
+
+    if (error)
+        mrp_log_error("Connection to console/%d closed with error %d (%s).",
+                      c->id, error, strerror(error));
+    else {
+        mrp_log_info("console/%d has closed the connection.", c->id);
+
+        mrp_transport_disconnect(t);
+        mrp_transport_destroy(t);
+        unregister_logger(c);
+        c->t = NULL;
+    }
+}
+
+
+static int stream_setup(data_t *data)
+{
+    static mrp_transport_evt_t evt;
+
+    mrp_mainloop_t  *ml = data->ctx->ml;
+    mrp_transport_t *t;
+    const char      *type;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    int              flags;
+
+    t    = NULL;
+    alen = sizeof(addr);
+    alen = mrp_transport_resolve(NULL, data->address, &addr, alen, &type);
+
+    if (alen <= 0) {
+        mrp_log_error("Failed to resolve console transport address '%s'.",
+                      data->address);
+
+        return FALSE;
+    }
+
+    evt.connection  = stream_connection_cb;
+    evt.closed      = stream_closed_cb;
+    evt.recvmsg     = stream_recv_cb;
+    evt.recvmsgfrom = NULL;
+
+    flags = MRP_TRANSPORT_REUSEADDR;
+    t     = mrp_transport_create(ml, type, &evt, data, flags);
+
+    if (t != NULL) {
+        if (mrp_transport_bind(t, &addr, alen) && mrp_transport_listen(t, 1)) {
+            data->t = t;
+
+            return TRUE;
+        }
+        else {
+            mrp_log_error("Failed to bind console to '%s'.", data->address);
+            mrp_transport_destroy(t);
+        }
+    }
+    else
+        mrp_log_error("Failed to create console transport.");
+
+    return FALSE;
+}
+
+
+/*
+ * datagram transports
+ */
+
+#define dgram_write_req       write_req
+#define dgram_free_req        free_req
+#define dgram_set_prompt_req  set_prompt_req
+
+#define dgram_recv_cb         recv_cb
+#define dgram_recvfrom_cb     recvfrom_cb
+
+
+static void dgram_close_req(mrp_console_t *mc)
+{
+    console_t *c = (console_t *)mc->backend_data;
+    mrp_msg_t *msg;
+    uint16_t   tag, type;
+
+    tag  = MRP_CONSOLE_BYE;
+    type = MRP_MSG_FIELD_BOOL;
+    msg  = mrp_msg_create(tag, type, TRUE, NULL);
+
+    if (msg != NULL) {
+        mrp_transport_send(c->t, msg);
+        mrp_msg_unref(msg);
+    }
+
+    mrp_transport_disconnect(c->t);
+}
+
+
+static int dgram_setup(data_t *data)
+{
+    static mrp_transport_evt_t evt;
+    static mrp_console_req_t   req;
+
+    mrp_mainloop_t  *ml = data->ctx->ml;
+    mrp_transport_t *t;
+    const char      *type;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    int              flags;
+    console_t       *c;
+
+    t    = NULL;
+    alen = sizeof(addr);
+    alen = mrp_transport_resolve(NULL, data->address, &addr, alen, &type);
+
+    if (alen <= 0) {
+        mrp_log_error("Failed to resolve console transport address '%s'.",
+                      data->address);
+
+        return FALSE;
+    }
+
+    c = mrp_allocz(sizeof(*c));
+
+    if (c != NULL) {
+        evt.recvmsg     = dgram_recv_cb;
+        evt.recvmsgfrom = dgram_recvfrom_cb;
+        evt.connection  = NULL;
+        evt.closed      = NULL;
+
+        flags = MRP_TRANSPORT_REUSEADDR;
+        t     = mrp_transport_create(ml, type, &evt, c, flags);
+
+        if (t != NULL) {
+            if (mrp_transport_bind(t, &addr, alen)) {
+                req.write      = dgram_write_req;
+                req.close      = dgram_close_req;
+                req.free       = dgram_free_req;
+                req.set_prompt = dgram_set_prompt_req;
+
+                c->t  = t;
+                c->mc = mrp_create_console(data->ctx, &req, c);
+
+                if (c->mc != NULL) {
+                    data->c         = c;
+                    c->mc->preserve = TRUE;
+
+                    return TRUE;
+                }
+                else
+                    mrp_log_error("Failed to create console.");
+            }
+            else
+                mrp_log_error("Failed to bind console to '%s'.", data->address);
+
+            c->t = NULL;
+            mrp_transport_destroy(t);
+        }
+        else
+            mrp_log_error("Failed to create console transport.");
+
+        mrp_free(c);
+    }
+
+    return FALSE;
+}
+
+
+#ifdef WEBSOCKETS_ENABLED
+
+/*
+ * websocket transport
+ */
+
+#define wsock_close_req stream_close_req
+#define wsock_free_req  free_req
+#define wsock_closed_cb stream_closed_cb
+
+static ssize_t wsock_write_req(mrp_console_t *mc, void *buf, size_t size)
+{
+    console_t  *c = (console_t *)mc->backend_data;
+    mrp_json_t *msg;
+
+    msg = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (msg != NULL) {
+        if (mrp_json_add_string_slice(msg, "output", buf, size))
+            mrp_transport_sendcustom(c->t, msg);
+
+        mrp_json_unref(msg);
+
+        return size;
+    }
+    else
+        return -1;
+}
+
+
+static void wsock_set_prompt_req(mrp_console_t *mc, const char *prompt)
+{
+    console_t  *c = (console_t *)mc->backend_data;
+    mrp_json_t *msg;
+
+    msg = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (msg != NULL) {
+        if (mrp_json_add_string(msg, "prompt", prompt))
+            mrp_transport_sendcustom(c->t, msg);
+
+        mrp_json_unref(msg);
+    }
+}
+
+
+static void wsock_recv_cb(mrp_transport_t *t, void *data, void *user_data)
+{
+    console_t  *c   = (console_t *)user_data;
+    mrp_json_t *msg = (mrp_json_t *)data;
+    const char *s;
+    char       *input;
+    size_t      size;
+
+    MRP_UNUSED(t);
+
+    s = mrp_json_object_to_string((mrp_json_t *)data);
+
+    mrp_debug("recived WRT console message:");
+    mrp_debug("  %s", s);
+
+    if (mrp_json_get_string(msg, "input", &input)) {
+        size = strlen(input);
+
+        if (size > 0) {
+            MRP_CONSOLE_BUSY(c->mc, {
+                    c->mc->evt.input(c->mc, input, size);
+                });
+
+            c->mc->check_destroy(c->mc);
+        }
+    }
+}
+
+
+static void wsock_connection_cb(mrp_transport_t *lt, void *user_data)
+{
+    static mrp_console_req_t req;
+    data_t    *data = (data_t *)user_data;
+    console_t *c;
+
+    mrp_debug("incoming web console connection...");
+
+    if ((c = mrp_allocz(sizeof(*c))) != NULL) {
+        c->t = mrp_transport_accept(lt, c, 0);
+
+        if (c->t != NULL) {
+            req.write      = wsock_write_req;
+            req.close      = wsock_close_req;
+            req.free       = wsock_free_req;
+            req.set_prompt = wsock_set_prompt_req;
+
+            c->mc = mrp_create_console(data->ctx, &req, c);
+
+            if (c->mc != NULL) {
+                c->id = next_id++;
+                register_logger(c);
+
+                return;
+            }
+            else {
+                mrp_transport_destroy(c->t);
+                c->t = NULL;
+            }
+        }
+    }
+}
+
+
+static int wsock_setup(data_t *data)
+{
+    static mrp_transport_evt_t evt;
+
+    mrp_mainloop_t  *ml   = data->ctx->ml;
+    const char      *cert = data->sslcert;
+    const char      *pkey = data->sslpkey;
+    const char      *ca   = data->sslca;
+    mrp_transport_t *t;
+    const char      *type;
+    mrp_sockaddr_t   addr;
+    socklen_t        alen;
+    int              flags;
+
+    t    = NULL;
+    alen = sizeof(addr);
+    alen = mrp_transport_resolve(NULL, data->address, &addr, alen, &type);
+
+    if (alen <= 0) {
+        mrp_log_error("Failed to resolve console transport address '%s'.",
+                      data->address);
+
+        return FALSE;
+    }
+
+    evt.connection  = wsock_connection_cb;
+    evt.closed      = wsock_closed_cb;
+    evt.recvcustom  = wsock_recv_cb;
+    evt.recvmsgfrom = NULL;
+
+    flags = MRP_TRANSPORT_MODE_CUSTOM;
+    t     = mrp_transport_create(ml, type, &evt, data, flags);
+
+    if (t != NULL) {
+        if (cert || pkey || ca) {
+            mrp_transport_setopt(t, MRP_WSCK_OPT_SSL_CERT, cert);
+            mrp_transport_setopt(t, MRP_WSCK_OPT_SSL_PKEY, pkey);
+            mrp_transport_setopt(t, MRP_WSCK_OPT_SSL_CA  , ca);
+        }
+
+        if (mrp_transport_bind(t, &addr, alen) && mrp_transport_listen(t, 1)) {
+            mrp_transport_setopt(t, MRP_WSCK_OPT_HTTPDIR, data->httpdir);
+            data->t = t;
+
+            return TRUE;
+        }
+        else {
+            mrp_log_error("Failed to bind console to '%s'.", data->address);
+            mrp_transport_destroy(t);
+        }
+    }
+    else
+        mrp_log_error("Failed to create console transport.");
+
+    return FALSE;
+}
+
+#endif /* WEBSOCKETS_ENABLED */
+
+
+enum {
+    ARG_ADDRESS,                         /* console transport address */
+    ARG_HTTPDIR,                         /* content directory for HTTP */
+    ARG_SSLCERT,                         /* path to SSL certificate */
+    ARG_SSLPKEY,                         /* path to SSL private key */
+    ARG_SSLCA                            /* path to SSL CA */
+};
+
+
+
+static int console_init(mrp_plugin_t *plugin)
+{
+    data_t *data;
+    int     ok;
+
+    if ((data = mrp_allocz(sizeof(*data))) != NULL) {
+        mrp_list_init(&data->clients);
+
+        data->ctx     = plugin->ctx;
+        data->address = plugin->args[ARG_ADDRESS].str;
+        data->httpdir = plugin->args[ARG_HTTPDIR].str;
+        data->sslcert = plugin->args[ARG_SSLCERT].str;
+        data->sslpkey = plugin->args[ARG_SSLPKEY].str;
+        data->sslca   = plugin->args[ARG_SSLCA].str;
+
+        mrp_log_info("Using console address '%s'...", data->address);
+
+        if (!strncmp(data->address, "wsck:", 5)) {
+            if (data->httpdir != NULL)
+                mrp_log_info("Using '%s' for serving console Web agent...",
+                             data->httpdir);
+            else
+                mrp_log_info("Not serving console Web agent...");
+        }
+
+        if (!strncmp(data->address, "tcp4:", 5) ||
+            !strncmp(data->address, "tcp6:", 5) ||
+            !strncmp(data->address, "unxs:", 5))
+            ok = stream_setup(data);
+#ifdef WEBSOCKETS_ENABLED
+        else if (!strncmp(data->address, "wsck:", 5))
+            ok = wsock_setup(data);
+#endif
+        else
+            ok = dgram_setup(data);
+
+        if (ok) {
+            plugin->data = data;
+
+            return TRUE;
+        }
+    }
+
+    mrp_free(data);
+
+    return FALSE;
+}
+
+
+static void console_exit(mrp_plugin_t *plugin)
+{
+    mrp_log_info("Cleaning up %s...", plugin->instance);
+}
+
+
+#define CONSOLE_DESCRIPTION "A debug console for Murphy."
+#define CONSOLE_HELP \
+    "The debug console provides a telnet-like remote session and a\n"     \
+    "simple shell-like command interpreter with commands to help\n"       \
+    "development, debugging, and trouble-shooting. The set of commands\n" \
+    "can be dynamically extended by registering new commands from\n"      \
+    "other plugins."
+
+#define CONSOLE_VERSION MRP_VERSION_INT(0, 0, 1)
+#define CONSOLE_AUTHORS "Krisztian Litkey <kli@iki.fi>"
+
+
+static mrp_plugin_arg_t console_args[] = {
+    MRP_PLUGIN_ARGIDX(ARG_ADDRESS , STRING, "address", DEFAULT_ADDRESS),
+    MRP_PLUGIN_ARGIDX(ARG_HTTPDIR , STRING, "httpdir", DEFAULT_HTTPDIR),
+    MRP_PLUGIN_ARGIDX(ARG_SSLCERT , STRING, "sslcert", NULL),
+    MRP_PLUGIN_ARGIDX(ARG_SSLPKEY , STRING, "sslpkey", NULL),
+    MRP_PLUGIN_ARGIDX(ARG_SSLCA   , STRING, "sslca"  , NULL)
+};
+
+
+MRP_CONSOLE_GROUP(console_commands, "console", NULL, NULL, {
+        MRP_TOKENIZED_CMD("debug", debug_cb, FALSE,
+                          "debug [function] [file] [line]",
+                          "set debug metadata to show",
+                          "Set what metadata to show for debug messages."),
+});
+
+MURPHY_REGISTER_CORE_PLUGIN("console",
+                            CONSOLE_VERSION, CONSOLE_DESCRIPTION,
+                            CONSOLE_AUTHORS, CONSOLE_HELP, MRP_MULTIPLE,
+                            console_init, console_exit,
+                            console_args, MRP_ARRAY_SIZE(console_args),
+                            NULL, 0, NULL, 0, &console_commands);
diff --git a/src/plugins/domain-control/Makefile b/src/plugins/domain-control/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/plugins/domain-control/client.c b/src/plugins/domain-control/client.c
new file mode 100644 (file)
index 0000000..6f8e096
--- /dev/null
@@ -0,0 +1,807 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <alloca.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/transport.h>
+
+#include "domain-control-types.h"
+#include "message.h"
+#include "table.h"
+#include "client.h"
+
+
+/*
+ * mark an enforcement point busy (typically while executing a callback)
+ */
+
+#define DOMCTL_MARK_BUSY(dc, ...) do {             \
+        (dc)->busy++;                              \
+        __VA_ARGS__                                \
+        (dc)->busy--;                              \
+        check_destroyed(dc);                       \
+    } while (0)
+
+
+/*
+ * a pending request
+ */
+
+typedef struct {
+    mrp_list_hook_t         hook;        /* hook to pending request queue */
+    uint32_t                seqno;       /* sequence number/request id */
+    int                     invoke : 1;  /* whether a pending invocation */
+    union {
+        mrp_domctl_status_cb_t  status;  /* request completion callback */
+        mrp_domctl_return_cb_t  ret;     /* invocation return cb */
+    } cb;
+    void                   *user_data;   /* opaque callback data */
+} pending_request_t;
+
+
+/*
+ * a registered proxied method
+ */
+
+typedef struct {
+    mrp_list_hook_t         hook;        /* to list of methods */
+    char                   *name;        /* method name */
+    size_t                  max_out;     /* max return arguments */
+    mrp_domctl_invoke_cb_t  cb;          /* handler callback */
+    void                   *user_data;   /* opaque handler data */
+} method_t;
+
+static void recv_cb(mrp_transport_t *t, mrp_msg_t *msg, void *user_data);
+static void recvfrom_cb(mrp_transport_t *t, mrp_msg_t *msg,
+                        mrp_sockaddr_t *addr, socklen_t addrlen,
+                        void *user_data);
+static void closed_cb(mrp_transport_t *t, int error, void *user_data);
+
+
+static int queue_pending(mrp_domctl_t *dc, uint32_t seq,
+                         mrp_domctl_status_cb_t cb, void *user_data);
+static int notify_pending(mrp_domctl_t *dc, msg_t *msg);
+static int queue_invoke(mrp_domctl_t *dc, uint32_t seq,
+                        mrp_domctl_return_cb_t cb, void *user_data);
+static void purge_pending(mrp_domctl_t *dc);
+
+
+
+
+mrp_domctl_t *mrp_domctl_create(const char *name, mrp_mainloop_t *ml,
+                                mrp_domctl_table_t *tables, int ntable,
+                                mrp_domctl_watch_t *watches, int nwatch,
+                                mrp_domctl_connect_cb_t connect_cb,
+                                mrp_domctl_watch_cb_t watch_cb, void *user_data)
+{
+    mrp_domctl_t       *dc;
+    mrp_domctl_table_t *st, *dt;
+    mrp_domctl_watch_t *sw, *dw;
+    int              i;
+
+    dc = mrp_allocz(sizeof(*dc));
+
+    if (dc != NULL) {
+        mrp_list_init(&dc->pending);
+        dc->ml = ml;
+
+        dc->name    = mrp_strdup(name);
+        dc->tables  = mrp_allocz_array(typeof(*dc->tables) , ntable);
+        dc->watches = mrp_allocz_array(typeof(*dc->watches), nwatch);
+
+        if (dc->name != NULL &&
+            (dc->tables  != NULL || ntable == 0) &&
+            (dc->watches != NULL || nwatch == 0)) {
+            for (i = 0; i < ntable; i++) {
+                st = tables + i;
+                dt = dc->tables + i;
+
+                dt->table       = mrp_strdup(st->table);
+                dt->mql_columns = mrp_strdup(st->mql_columns);
+                dt->mql_index   = mrp_strdup(st->mql_index ? st->mql_index:"");
+
+                if (!dt->table || !dt->mql_columns || !dt->mql_index)
+                    break;
+
+                dc->ntable++;
+            }
+
+            for (i = 0; i < nwatch; i++) {
+                sw = watches + i;
+                dw = dc->watches + i;
+
+                dw->table       = mrp_strdup(sw->table);
+                dw->mql_columns = mrp_strdup(sw->mql_columns);
+                dw->mql_where   = mrp_strdup(sw->mql_where ? sw->mql_where:"");
+                dw->max_rows    = sw->max_rows;
+
+                if (!dw->table || !dw->mql_columns || !dw->mql_where)
+                    break;
+
+                dc->nwatch++;
+            }
+
+            dc->connect_cb = connect_cb;
+            dc->watch_cb   = watch_cb;
+            dc->user_data  = user_data;
+            dc->seqno      = 1;
+
+            mrp_list_init(&dc->methods);
+
+            return dc;
+        }
+
+        mrp_domctl_destroy(dc);
+    }
+
+    return NULL;
+}
+
+
+static void destroy_domctl(mrp_domctl_t *dc)
+{
+    int i;
+
+    purge_pending(dc);
+
+    for (i = 0; i < dc->ntable; i++) {
+        mrp_free((char *)dc->tables[i].table);
+        mrp_free((char *)dc->tables[i].mql_columns);
+        mrp_free((char *)dc->tables[i].mql_index);
+    }
+    mrp_free(dc->tables);
+
+    for (i = 0; i < dc->nwatch; i++) {
+        mrp_free((char *)dc->watches[i].table);
+        mrp_free((char *)dc->watches[i].mql_columns);
+        mrp_free((char *)dc->watches[i].mql_where);
+    }
+    mrp_free(dc->watches);
+
+    mrp_free(dc->name);
+    mrp_free(dc);
+}
+
+
+static inline void check_destroyed(mrp_domctl_t *dc)
+{
+    if (dc->destroyed && dc->busy <= 0) {
+        destroy_domctl(dc);
+    }
+}
+
+
+void mrp_domctl_destroy(mrp_domctl_t *dc)
+{
+    if (dc != NULL) {
+        mrp_domctl_disconnect(dc);
+
+        if (dc->busy <= 0)
+            destroy_domctl(dc);
+        else
+            dc->destroyed = TRUE;
+    }
+}
+
+
+static void notify_disconnect(mrp_domctl_t *dc, uint32_t errcode,
+                              const char *errmsg)
+{
+    DOMCTL_MARK_BUSY(dc, {
+            dc->connected = FALSE;
+            dc->connect_cb(dc, FALSE, errcode, errmsg, dc->user_data);
+        });
+}
+
+
+static void notify_connect(mrp_domctl_t *dc)
+{
+    DOMCTL_MARK_BUSY(dc, {
+            dc->connected = TRUE;
+            dc->connect_cb(dc, TRUE, 0, NULL, dc->user_data);
+        });
+}
+
+
+static int domctl_register(mrp_domctl_t *dc)
+{
+    register_msg_t  reg;
+    mrp_msg_t      *msg;
+    int             success;
+
+    mrp_clear(&reg);
+    reg.type    = MSG_TYPE_REGISTER;
+    reg.seq     = 0;
+    reg.name    = dc->name;
+    reg.tables  = dc->tables;
+    reg.ntable  = dc->ntable;
+    reg.watches = dc->watches;
+    reg.nwatch  = dc->nwatch;
+
+    msg = msg_encode_message((msg_t *)&reg);
+
+    if (msg != NULL) {
+        success = mrp_transport_send(dc->t, msg);
+        mrp_msg_unref(msg);
+    }
+    else
+        success = FALSE;
+
+    return success;
+}
+
+
+static int try_connect(mrp_domctl_t *dc)
+{
+    static mrp_transport_evt_t evt;
+
+    evt.closed      = closed_cb;
+    evt.recvmsg     = recv_cb;
+    evt.recvmsgfrom = recvfrom_cb;
+
+    dc->t = mrp_transport_create(dc->ml, dc->ttype, &evt, dc, 0);
+
+    if (dc->t != NULL) {
+        if (mrp_transport_connect(dc->t, &dc->addr, dc->addrlen))
+            if (domctl_register(dc))
+                return TRUE;
+
+        mrp_transport_destroy(dc->t);
+        dc->t = NULL;
+    }
+
+    return FALSE;
+}
+
+
+static void stop_reconnect(mrp_domctl_t *dc)
+{
+    mrp_del_timer(dc->ctmr);
+    dc->ctmr = NULL;
+}
+
+
+static void reconnect_cb(mrp_timer_t *t, void *user_data)
+{
+    mrp_domctl_t *dc = (mrp_domctl_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    if (try_connect(dc))
+        stop_reconnect(dc);
+}
+
+
+static int start_reconnect(mrp_domctl_t *dc)
+{
+    int interval;
+
+    if (dc->ctmr == NULL && dc->cival >= 0) {
+        interval = dc->cival ? 1000 * dc->cival : 5000;
+        dc->ctmr = mrp_add_timer(dc->ml, interval, reconnect_cb, dc);
+
+        if (dc->ctmr == NULL)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+int mrp_domctl_connect(mrp_domctl_t *dc, const char *address, int interval)
+{
+    mrp_sockaddr_t  addr;
+    socklen_t       addrlen;
+    const char     *type;
+
+    if (dc == NULL)
+        return FALSE;
+
+    addrlen = mrp_transport_resolve(NULL, address, &addr, sizeof(addr), &type);
+
+    if (addrlen > 0) {
+        dc->addr    = addr;
+        dc->addrlen = addrlen;
+        dc->cival   = interval;
+        dc->ttype   = type;
+
+        if (try_connect(dc))
+            return TRUE;
+
+        if (interval >= 0)
+            return start_reconnect(dc);
+    }
+
+    return FALSE;
+}
+
+
+void mrp_domctl_disconnect(mrp_domctl_t *dc)
+{
+    if (dc->t != NULL) {
+        stop_reconnect(dc);
+        mrp_transport_destroy(dc->t);
+        dc->t         = NULL;
+        dc->connected = FALSE;
+    }
+}
+
+
+int mrp_domctl_set_data(mrp_domctl_t *dc, mrp_domctl_data_t *tables, int ntable,
+                     mrp_domctl_status_cb_t cb, void *user_data)
+{
+    set_msg_t  set;
+    mrp_msg_t *msg;
+    uint32_t   seq = dc->seqno++;
+    int        success, i;
+
+    if (!dc->connected)
+        return FALSE;
+
+    for (i = 0; i < ntable; i++) {
+        if (tables[i].id < 0 || tables[i].id >= dc->ntable)
+            return FALSE;
+    }
+
+    mrp_clear(&set);
+    set.type   = MSG_TYPE_SET;
+    set.seq    = seq;
+    set.tables = tables;
+    set.ntable = ntable;
+
+    msg = msg_encode_message((msg_t *)&set);
+
+    if (msg != NULL) {
+        success = mrp_transport_send(dc->t, msg);
+        mrp_msg_unref(msg);
+
+        if (success)
+            queue_pending(dc, seq, cb, user_data);
+
+        return success;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_domctl_invoke(mrp_domctl_t *dc, const char *name, int narg,
+                      mrp_domctl_arg_t *args, mrp_domctl_return_cb_t reply_cb,
+                      void *user_data)
+{
+    invoke_msg_t  invoke;
+    mrp_msg_t    *msg;
+    uint32_t      seq = dc->seqno++;
+    int           success;
+
+    if (!dc->connected)
+        return FALSE;
+
+    if (reply_cb == NULL && user_data != NULL)
+        return FALSE;
+
+    mrp_clear(&invoke);
+    invoke.type  = MSG_TYPE_INVOKE;
+    invoke.seq   = seq;
+    invoke.name  = name;
+    invoke.noret = reply_cb ? TRUE : FALSE;
+    invoke.narg  = narg;
+    invoke.args  = args;
+
+    msg = msg_encode_message((msg_t *)&invoke);
+
+    if (msg != NULL) {
+        success = mrp_transport_send(dc->t, msg);
+        mrp_msg_unref(msg);
+
+        if (success)
+            queue_invoke(dc, seq, reply_cb, user_data);
+
+        return success;
+    }
+    else
+        return FALSE;
+}
+
+
+int mrp_domctl_register_methods(mrp_domctl_t *dc, mrp_domctl_method_def_t *defs,
+                                size_t ndef)
+{
+    mrp_domctl_method_def_t *def;
+    method_t                *m;
+    size_t                   i;
+
+    for (i = 0, def = defs; i < ndef; i++, def++) {
+        m = mrp_allocz(sizeof(*m));
+
+        if (m == NULL)
+            return FALSE;
+
+        mrp_list_init(&m->hook);
+
+        m->name      = mrp_strdup(def->name);
+        m->max_out   = def->max_out;
+        m->cb        = def->cb;
+        m->user_data = def->user_data;
+
+        if (m->name == NULL) {
+            mrp_free(m);
+            return FALSE;
+        }
+
+        mrp_list_append(&dc->methods, &m->hook);
+    }
+
+    return TRUE;
+}
+
+
+static method_t *find_method(mrp_domctl_t *dc, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    method_t        *m;
+
+    mrp_list_foreach(&dc->methods, p, n) {
+        m = mrp_list_entry(p, typeof(*m), hook);
+
+        if (!strcmp(m->name, name))
+            return m;
+    }
+
+    return NULL;
+}
+
+
+static void process_ack(mrp_domctl_t *dc, ack_msg_t *ack)
+{
+    if (ack->seq != 0)
+        notify_pending(dc, (msg_t *)ack);
+    else
+        notify_connect(dc);
+}
+
+
+static void process_nak(mrp_domctl_t *dc, nak_msg_t *nak)
+{
+    if (nak->seq != 0)
+        notify_pending(dc, (msg_t *)nak);
+    else
+        notify_disconnect(dc, nak->error, nak->msg);
+}
+
+
+static void process_notify(mrp_domctl_t *dc, notify_msg_t *notify)
+{
+    dc->watch_cb(dc, notify->tables, notify->ntable, dc->user_data);
+}
+
+
+static void process_invoke(mrp_domctl_t *dc, invoke_msg_t *invoke)
+{
+    method_t         *m;
+    mrp_domctl_arg_t *args, error;
+    int               narg;
+    return_msg_t      ret;
+    mrp_msg_t        *msg;
+    int               i;
+
+    mrp_clear(&ret);
+
+    m = find_method(dc, invoke->name);
+
+    ret.type = MSG_TYPE_RETURN;
+    ret.seq  = invoke->seq;
+
+    if (m == NULL) {
+        ret.error = MRP_DOMCTL_NOTFOUND;
+        args = NULL;
+        narg = 0;
+    }
+    else {
+        ret.error = MRP_DOMCTL_OK;
+
+        narg = ret.narg = m->max_out;
+
+        if (narg > 0) {
+            args = ret.args = alloca(narg * sizeof(args[0]));
+            memset(args, 0, narg * sizeof(args[0]));
+        }
+        else
+            args = NULL;
+
+        ret.retval = m->cb(dc, invoke->narg, invoke->args,
+                           &ret.narg, ret.args, m->user_data);
+    }
+
+    msg = msg_encode_message((msg_t *)&ret);
+
+    if (msg == NULL) {
+        error.type = MRP_DOMCTL_STRING;
+        error.str  = "failed to encode return message (arguments)";
+        ret.error  = MRP_DOMAIN_FAILED;
+        ret.narg   = 1;
+        ret.args   = &error;
+
+        msg = msg_encode_message((msg_t *)&ret);
+
+        ret.narg = 0;
+        ret.args = args = NULL;
+    }
+
+    if (msg != NULL) {
+        mrp_transport_send(dc->t, msg);
+        mrp_msg_unref(msg);
+    }
+
+    narg = ret.narg;
+    for (i = 0; i < narg; i++) {
+        if (args[i].type == MRP_DOMCTL_STRING)
+            mrp_free((char *)args[i].str);
+        else if (MRP_DOMCTL_IS_ARRAY(args[i].type)) {
+            uint32_t j;
+
+            for (j = 0; j < args[i].size; j++)
+                if (MRP_DOMCTL_ARRAY_TYPE(args[i].type) == MRP_DOMCTL_STRING)
+                    mrp_free(((char **)args[i].arr)[j]);
+
+            mrp_free(args[i].arr);
+        }
+    }
+}
+
+
+static void process_return(mrp_domctl_t *dc, return_msg_t *ret)
+{
+    notify_pending(dc, (msg_t *)ret);
+}
+
+
+static void recv_cb(mrp_transport_t *t, mrp_msg_t *tmsg, void *user_data)
+{
+    mrp_domctl_t  *dc = (mrp_domctl_t *)user_data;
+    msg_t         *msg;
+
+    MRP_UNUSED(t);
+
+    /*
+      mrp_log_info("Received message:");
+      mrp_msg_dump(msg, stdout);
+    */
+
+    msg = msg_decode_message(tmsg);
+
+    if (msg != NULL) {
+        switch (msg->any.type) {
+        case MSG_TYPE_NOTIFY:
+            process_notify(dc, &msg->notify);
+            break;
+        case MSG_TYPE_ACK:
+            process_ack(dc, &msg->ack);
+            break;
+        case MSG_TYPE_NAK:
+            process_nak(dc, &msg->nak);
+            break;
+        case MSG_TYPE_INVOKE:
+            process_invoke(dc, &msg->invoke);
+            break;
+        case MSG_TYPE_RETURN:
+            process_return(dc, &msg->ret);
+            break;
+        default:
+            mrp_domctl_disconnect(dc);
+            notify_disconnect(dc, EINVAL, "unexpected message from server");
+            break;
+        }
+
+        msg_free_message(msg);
+    }
+    else {
+        mrp_domctl_disconnect(dc);
+        notify_disconnect(dc, EINVAL, "invalid message from server");
+    }
+}
+
+
+static void recvfrom_cb(mrp_transport_t *t, mrp_msg_t *msg,
+                        mrp_sockaddr_t *addr, socklen_t addrlen,
+                        void *user_data)
+{
+    MRP_UNUSED(t);
+    MRP_UNUSED(msg);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+    MRP_UNUSED(user_data);
+
+    /* XXX TODO:
+     *    This should neither be called nor be necessary to specify.
+     *    However, currently the transport layer mandates having to
+     *    give both recv and recvfrom event callbacks if no connection
+     *    event callback is given. However this is not correct because
+     *    on a client side one wants to be able to create a connection-
+     *    oriented transport without either connection or recvfrom event
+     *    callbacks. This needs to be fixed in transport by moving the
+     *    appropriate callback checks lower in the stack to the actual
+     *    transport backends.
+     */
+
+    mrp_log_error("Whoa... recvfrom called for a connected transport.");
+    exit(1);
+}
+
+
+static void closed_cb(mrp_transport_t *t, int error, void *user_data)
+{
+    mrp_domctl_t *dc = (mrp_domctl_t *)user_data;
+
+    MRP_UNUSED(t);
+    MRP_UNUSED(dc);
+
+    if (error)
+        notify_disconnect(dc, error, strerror(error));
+    else {
+        notify_disconnect(dc, ECONNRESET, "server has closed the connection");
+        start_reconnect(dc);
+    }
+}
+
+
+static int queue_pending(mrp_domctl_t *dc, uint32_t seq,
+                         mrp_domctl_status_cb_t cb, void *user_data)
+{
+    pending_request_t *pending;
+
+    pending = mrp_allocz(sizeof(*pending));
+
+    if (pending != NULL) {
+        mrp_list_init(&pending->hook);
+
+        pending->invoke    = false;
+        pending->seqno     = seq;
+        pending->cb.status = cb;
+        pending->user_data = user_data;
+
+        mrp_list_append(&dc->pending, &pending->hook);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int notify_pending(mrp_domctl_t *dc, msg_t *msg)
+{
+    mrp_list_hook_t   *p, *n;
+    pending_request_t *pending;
+    uint32_t           seq;
+    int                error, status;
+    const char        *message;
+    int                narg;
+    mrp_domctl_arg_t  *args;
+    int                success;
+
+    seq = msg->any.seq;
+
+    mrp_list_foreach(&dc->pending, p, n) {
+        pending = mrp_list_entry(p, typeof(*pending), hook);
+
+        if (pending->seqno != seq)
+            continue;
+
+        if (!pending->invoke) {
+            switch (msg->any.type) {
+            case MSG_TYPE_ACK:
+                error   = 0;
+                message = NULL;
+                goto notify;
+            case MSG_TYPE_NAK:
+                error   = msg->nak.error;
+                message = msg->nak.msg;
+            notify:
+                DOMCTL_MARK_BUSY(dc, {
+                        pending->cb.status(dc, error, message,
+                                           pending->user_data);
+                    });
+                success = TRUE;
+                break;
+            default:
+                success = FALSE;
+                break;
+            }
+        }
+        else {
+            if (msg->any.type == MSG_TYPE_RETURN) {
+                error  = msg->ret.error;
+                status = msg->ret.retval;
+                narg   = msg->ret.narg;
+                args   = msg->ret.args;
+
+                DOMCTL_MARK_BUSY(dc, {
+                        pending->cb.ret(dc, error, status, narg, args,
+                                        pending->user_data);
+                    });
+                success = TRUE;
+            }
+            else
+                success = FALSE;
+        }
+
+        mrp_list_delete(&pending->hook);
+        mrp_free(pending);
+
+        return success;
+    }
+
+    return FALSE;
+}
+
+
+static int queue_invoke(mrp_domctl_t *dc, uint32_t seq,
+                        mrp_domctl_return_cb_t cb, void *user_data)
+{
+    pending_request_t *pending;
+
+    if (cb == NULL)
+        return TRUE;
+
+    pending = mrp_allocz(sizeof(*pending));
+
+    if (pending != NULL) {
+        mrp_list_init(&pending->hook);
+
+        pending->invoke    = true;
+        pending->seqno     = seq;
+        pending->cb.ret    = cb;
+        pending->user_data = user_data;
+
+        mrp_list_append(&dc->pending, &pending->hook);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static void purge_pending(mrp_domctl_t *dc)
+{
+    mrp_list_hook_t   *p, *n;
+    pending_request_t *pending;
+
+    mrp_list_foreach(&dc->pending, p, n) {
+        pending = mrp_list_entry(p, typeof(*pending), hook);
+
+        mrp_list_delete(&pending->hook);
+        mrp_free(pending);
+    }
+}
diff --git a/src/plugins/domain-control/client.h b/src/plugins/domain-control/client.h
new file mode 100644 (file)
index 0000000..5a90b6d
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_CLIENT_H__
+#define __MURPHY_DOMAIN_CONTROL_CLIENT_H__
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mainloop.h>
+#include <murphy-db/mqi.h>
+#include <murphy/core/domain-types.h>
+
+MRP_CDECL_BEGIN
+
+#define MRP_DEFAULT_DOMCTL_ADDRESS "unxs:@murphy-domctrl"
+
+
+/*
+ * a table owned by a domain controller
+ */
+
+typedef struct {
+    const char *table;                   /* table name */
+    const char *mql_columns;             /* column definition scriptlet */
+    const char *mql_index;               /* index column list */
+} mrp_domctl_table_t;
+
+#define MRP_DOMCTL_TABLE(_table, _columns, _index) \
+    { .table = _table, .mql_columns = _columns, .mql_index = _index }
+
+
+/*
+ * a table tracked by a domain controller
+ */
+
+typedef struct {
+    const char *table;                   /* table name */
+    const char *mql_columns;             /* column list for select */
+    const char *mql_where;               /* where clause for select */
+    int         max_rows;                /* max number of rows to select */
+} mrp_domctl_watch_t;
+
+#define MRP_DOMCTL_WATCH(_table, _columns, _where, _max_rows) {       \
+        .table       = _table                  ,                      \
+        .mql_columns = _columns ? _columns : "",                      \
+        .mql_where   = _where   ? _where   : "",                      \
+        .max_rows    = _max_rows               ,                      \
+    }
+
+
+/*
+ * table data
+ */
+
+typedef struct {
+    int                  id;             /* table id */
+    mqi_column_def_t    *coldefs;        /* column definitions */
+    int                  ncolumn;        /* columns per row */
+    mrp_domctl_value_t **rows;           /* row data */
+    int                  nrow;           /* number of rows */
+} mrp_domctl_data_t;
+
+
+/** Opaque policy domain controller type. */
+typedef struct mrp_domctl_s mrp_domctl_t;
+
+/** Callback type for connection state notifications. */
+typedef void (*mrp_domctl_connect_cb_t)(mrp_domctl_t *dc, int connection,
+                                        int errcode, const char *errmsg,
+                                        void *user_data);
+
+/** Callback type for request status notifications. */
+typedef void (*mrp_domctl_status_cb_t)(mrp_domctl_t *dc, int errcode,
+                                       const char *errmsg, void *user_data);
+
+/** Callback type for data change notifications. */
+typedef void (*mrp_domctl_watch_cb_t)(mrp_domctl_t *dc,
+                                      mrp_domctl_data_t *tables, int ntable,
+                                      void *user_data);
+
+/** Callback type for return of/reply to a proxied method invocation. */
+typedef void (*mrp_domctl_return_cb_t)(mrp_domctl_t *dc, int error, int retval,
+                                       uint32_t narg, mrp_domctl_arg_t *args,
+                                       void *user_data);
+
+/** Callback type for a proxied method invocation. */
+typedef int (*mrp_domctl_invoke_cb_t)(mrp_domctl_t *dc, uint32_t narg,
+                                      mrp_domctl_arg_t *args,
+                                      uint32_t *nout, mrp_domctl_arg_t *outs,
+                                      void *user_data);
+
+/*
+ * proxied invocation errors
+ */
+
+typedef enum {
+    MRP_DOMCTL_OK       = MRP_DOMAIN_OK,
+    MRP_DOMCTL_NOTFOUND = MRP_DOMAIN_NOTFOUND,
+    MRP_DOMCTL_NOMETHOD = MRP_DOMAIN_NOMETHOD,
+} mrp_domctl_error_t;
+
+/*
+ * a domain controller method definition
+ */
+
+typedef struct {
+    const char             *name;
+    size_t                  max_out;
+    mrp_domctl_invoke_cb_t  cb;
+    void                   *user_data;
+} mrp_domctl_method_def_t;
+
+
+/** Create a new policy domain controller. */
+mrp_domctl_t *mrp_domctl_create(const char *name, mrp_mainloop_t *ml,
+                                mrp_domctl_table_t *tables, int ntable,
+                                mrp_domctl_watch_t *watches, int nwatch,
+                                mrp_domctl_connect_cb_t connect_cb,
+                                mrp_domctl_watch_cb_t watch_cb,
+                                void *user_data);
+
+/** Destroy the given policy domain controller. */
+void mrp_domctl_destroy(mrp_domctl_t *dc);
+
+/**
+ * Connect and register the given controller to the server. If timeout
+ * is non-negative, it will be used to automatically attempt re-connecting
+ * to the server this often (in seconds) whenever the connection goes down.
+ */
+int mrp_domctl_connect(mrp_domctl_t *dc, const char *address, int timeout);
+
+/** Close the connection to the server. */
+void mrp_domctl_disconnect(mrp_domctl_t *dc);
+
+/** Set the content of the given tables to the provided data. */
+int mrp_domctl_set_data(mrp_domctl_t *dc, mrp_domctl_data_t *tables, int ntable,
+                        mrp_domctl_status_cb_t status_cb, void *user_data);
+
+/** Invoke a proxied method. */
+int mrp_domctl_invoke(mrp_domctl_t *dc, const char *method, int narg,
+                      mrp_domctl_arg_t *args, mrp_domctl_return_cb_t return_cb,
+                      void *user_data);
+
+/** Register a proxied method handler. */
+int mrp_domctl_register_methods(mrp_domctl_t *dc, mrp_domctl_method_def_t *defs,
+                                size_t ndef);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_DOMAIN_CONTROL_CLIENT_H__ */
diff --git a/src/plugins/domain-control/domain-control-api.js b/src/plugins/domain-control/domain-control-api.js
new file mode 100644 (file)
index 0000000..9177be8
--- /dev/null
@@ -0,0 +1,430 @@
+/*
+ * debugging
+ */
+
+var DOMCTL_COMM   = 0;                      /* message/communication */
+var DOMCTL_NOTIFY = 1;                      /* data notification */
+var DOMCTL_MISC   = 2;                      /* other debug messages */
+
+var domctl_debug_names = [ 'COMM', 'NOTIFY', 'MISC'];
+var domctl_debug_mask  = 0x0;
+
+
+function domctl_debug_index_of(name) {
+    for (var idx in domctl_debug_names) {
+        if (domctl_debug_names[idx] == name)
+            return idx;
+    }
+
+    return -1;
+}
+
+
+function domctl_debug_mask_of(name) {
+    var idx = domctl_debug_index_of(name);
+
+    if (idx >= 0)
+        return (1 << domctl_debug_index_of(name));
+    else
+        return 0;
+}
+
+
+function domctl_debug_set(flags, enable) {
+    var mask = 0;
+    var flag;
+
+    if (typeof flags == typeof "" || typeof flags == typeof 1)
+        flags = [ flags ];
+
+    for (var idx in flags) {
+        flag = flags[idx];
+
+        if (typeof flag == typeof "")
+            mask |= domctl_debug_mask_of(flag);
+        else
+            mask |= (1 << flag);
+    }
+
+    if (enable)
+        domctl_debug_mask |= mask;
+    else
+        domctl_debug_mask &= ~mask;
+}
+
+
+function domctl_debug_enable (flags) {
+    domctl_debug_set(flags, true);
+}
+
+
+function domctl_debug_disable(flags) {
+    domctl_debug_set(flags, false);
+}
+
+
+function domctl_debug() {
+    var flag, msg, i;
+
+    if (arguments.length >= 2) {
+        flag = arguments[0];
+        mask = (1 << flag);
+
+        if (!(mask & domctl_debug_mask))
+            return;
+
+        flag = domctl_debug_names[flag];
+
+        for (i = 1, msg = ""; i < arguments.length; i++) {
+            msg += arguments[i];
+        }
+
+        console.log("D: [" + flag + "] " + msg);
+    }
+    else {
+        if (arguments.length == 1)
+            console.log("D: [ALL] " + arguments[0]);
+    }
+}
+
+
+/*
+ * custom errors
+ */
+
+function DomainControllerError(message) {
+    this.name    = "Domain Control Error";
+    this.message = message;
+}
+
+
+/*
+ * pending requests
+ */
+
+/** Contruct a new pending request for the given request and controller. */
+function DomainControllerPendingRequest (controller, req) {
+    this.controller = controller;
+    this.req        = req;
+    this.reqno      = req.seq;
+}
+
+
+/** Deliver pending request reply notification. */
+DomainControllerPendingRequest.prototype.notify = function (message) {
+    var event;
+
+    switch (message.type) {
+    case 'ack':
+        if (this.onsuccess) {
+            event = { type: 'ack', seq: message.seq };
+            this.onsuccess(event);
+        }
+        break;
+
+    case 'nak':
+        if (this.onerror) {
+            event = { type: 'nak', seq: message.seq };
+            event.error  = message.error  ? message.error  : -1;
+            event.errmsg = message.errmsg ? message.errmsg : "<unknown error>";
+            this.onerror(message);
+        }
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+/*
+ * DomainController
+ */
+
+/** Reset controller state to defaults. */
+DomainController.prototype.reset = function () {
+    this.connected = false;
+    this.server    = null;
+    this.sck       = null;
+    this.reqno     = 1;
+    this.reqq      = [];
+    this.sets      = [];
+
+    this.ondisconnect = null;
+    this.onfailed     = null;
+    this.onevent      = null;
+}
+
+
+/** Ensure we have a connection, throw an error otherwise. */
+DomainController.prototype.check_connection = function () {
+    if (!this.connected)
+        throw new DomainControllerError("not connected");
+}
+
+
+/** Event handler for connection establishment. */
+DomainController.prototype.sckopen = function () {
+    var ctl = this.controller;
+
+    domctl_debug(DOMCTL_COMM, "connected to server " + ctl.server);
+
+    ctl.connected = true;
+    ctl.register();
+}
+
+
+/** Event handler for socket disconnection. */
+DomainController.prototype.sckclose = function () {
+    var ctl = this.controller;
+
+    domctl_debug(DOMCTL_COMM, "disconnected from server");
+
+    ctl.connected = false;
+
+    if (ctl.ondisconnect)                /* notify listener if any */
+        ctl.ondisconnect();
+
+    ctl.reset();
+}
+
+
+/** Event handler for connection error. */
+DomainController.prototype.sckerror = function () {
+    var ctl = this.controller;
+
+    domctl_debug(DOMCTL_COMM, "connection error");
+
+    ctl.connected = false;
+
+    if (ctl.onerror) {
+        ctl.ondisconnect = null;
+        ctl.onerror();
+    }
+}
+
+
+
+/** Event handler for receiving messages. */
+DomainController.prototype.sckmessage = function (message) {
+    var ctl = this.controller;
+    var msg = JSON.parse(message.data);
+    var seq = msg.seq;
+    var pending;
+
+    domctl_debug(DOMCTL_COMM, "received message: " + message.data);
+
+    switch (msg.type) {
+    case 'notify':
+        ctl.notify(msg);
+        break;
+
+    case 'ack':
+    case 'nak':
+        pending = ctl.deq(msg.seq);
+
+        if (pending) {
+            pending.notify(msg);
+        }
+        break;
+    }
+}
+
+
+/** Deliver domain controller event notification. */
+DomainController.prototype.notify = function (message) {
+    var idx, t, w;
+    var event;
+
+    if (this.onevent) {
+        event = {
+            type: 'notify',
+            tables: {}
+        };
+
+        for (idx in message.tables) {
+            t = message.tables[idx];
+            w = this.watches[idx];
+
+            if (w) {
+                event.tables[w.table] = {
+                    id:   w.id,
+                    rows: t.rows,
+                };
+            }
+        }
+
+        this.onevent(event);
+    }
+}
+
+
+/** Enqueue an outgoing request to the server. */
+DomainController.prototype.enq = function (req) {
+    var seq, pending;
+
+    seq     = req.seq = this.reqno++;
+    pending = new DomainControllerPendingRequest(this, req);
+
+    this.reqq[seq] = pending;
+
+    return pending;
+}
+
+
+/** Dequeue a pending request for the given sequence number. */
+DomainController.prototype.deq = function (seq) {
+    var pending;
+
+    pending = this.reqq[seq];
+
+    if (pending)
+        delete this.reqq[seq];
+
+    return pending;
+}
+
+
+/** Send a request to the server returning a pending object if appropriate. */
+DomainController.prototype.send_request = function (req) {
+    var pending;
+
+    if (req.type != 'register')
+        pending = this.enq(req);
+    else {
+        req.seq = 0;
+        pending = null;
+    }
+
+    domctl_debug(DOMCTL_COMM, "sending message: " + JSON.stringify(req));
+
+    this.sck.send(JSON.stringify(req));
+
+    return pending;
+}
+
+
+/** Send register message to the server. */
+DomainController.prototype.register = function () {
+    var f, ntable, nwatch, req;
+
+    ntable = 0;
+    for (f in this.tables) {
+        this.tables[f].id = ntable++;
+    }
+
+    nwatch = 0;
+    for (f in this.watches) {
+        this.watches[f].id = nwatch++;
+    }
+
+    req = {
+        type:    'register',
+        name:    this.name,
+        tables:  this.tables,
+        ntable:  ntable,
+        watches: this.watches,
+        nwatch:  nwatch,
+    };
+
+    this.send_request(req);
+}
+
+
+/** Create a new domain controller object. */
+function DomainController(name, tables, watches) {
+    this.reset();
+
+    this.name    = name;
+    this.tables  = tables;
+    this.watches = watches;
+}
+
+
+/** Determine a WebSocket URI based on an HTTP URI. */
+DomainController.prototype.socketUri = function (http_uri) {
+    var proto, colon, rest;
+
+    colon = http_uri.indexOf(':');           /* get first colon */
+    proto = http_uri.substring(0, colon);    /* get protocol */
+    rest  = http_uri.substring(colon + 3);   /* get URI sans protocol:// */
+    addr  = rest.split("/")[0];              /* strip URI path from address */
+
+    switch (proto) {
+    case "http":  return "ws://"  + addr;
+    case "https": return "wss://" + addr;
+    default:      return null;
+    }
+}
+
+
+/** Initiate connection to the given server. */
+DomainController.prototype.connect = function (server) {
+    if (this.connected)
+        throw new DomainControllerError("already connected to " + this.server);
+    else {
+        domctl_debug(DOMCTL_COMM, "trying to connect to " + server);
+        this.server = server;
+
+        if (typeof MozWebSocket != "undefined")
+            this.sck = new MozWebSocket(this.server, "murphy");
+        else
+            this.sck = new WebSocket(this.server, "murphy");
+
+        this.sck.controller = this;
+        this.sck.onopen     = this.sckopen;
+        this.sck.onclose    = this.sckclose;
+        this.sck.onerror    = this.sckerror;
+        this.sck.onmessage  = this.sckmessage;
+    }
+}
+
+
+/** Disconnect from the server. */
+DomainController.prototype.disconnect = function () {
+    this.check_connection();
+
+    domctl_debug(DOMCTL_COMM, "disconnecting from " + this.server);
+
+    this.sck.close();
+    delete this.sck;
+}
+
+
+/** Set data on server. */
+DomainController.prototype.set = function (table_data) {
+    var idx, id, name;
+    var table, ntbl, ntot, ncol, nrow;
+    var req;
+
+    req = { type: 'set', seq: 0, nchange: 0, ntotal: 0, tables: [] };
+
+    ntbl = ntot = 0;
+    for (idx in table_data) {
+        ntbl++;
+        data  = table_data[idx];
+        table = data.table;
+        rows  = data.rows;
+
+        ncol  = rows[0].length;
+        nrow  = rows.length;
+        ntot += ncol * nrow;
+
+        id = -1;
+        for (tbl in this.tables) {
+            if (this.tables[tbl].table == table)
+                id = this.tables[tbl].id;
+        }
+        if (id < 0)
+            throw new DomainControllerError("unknown table " + table);
+
+        name = this.tables[id].table;
+
+        req.tables[idx] = { id: id, nrow: nrow, ncol: ncol, rows: rows };
+    }
+
+    req.nchange = ntbl;
+    req.ntotal  = ntot;
+
+    return this.send_request(req);
+}
diff --git a/src/plugins/domain-control/domain-control-test.html b/src/plugins/domain-control/domain-control-test.html
new file mode 100644 (file)
index 0000000..8fdaca5
--- /dev/null
@@ -0,0 +1,224 @@
+<html lang="en">
+<head><title>Domain Controller Webruntime Test</title>
+<script src="domain-control-api.js"></script>
+<script>
+
+var ctx;
+
+
+function controllerDisconnected () {
+    setStatus("disconnected");
+    setStatus("unknown", 'audio_status');
+    setStatus("unknown", 'video_status');
+    setStatus("unknown", 'data_status');
+}
+
+
+function controllerFailed () {
+    console.log("connection failed");
+}
+
+
+function controllerEvent (event) {
+    var tidx, ridx;
+    var table, row;
+
+    setStatus('connected');
+    enableButton('disconnect');
+    enableButton('update');
+    console.log("received '" + event.type + "' event");
+
+    for (name in event.tables) {
+        table = event.tables[name];
+        rows  = table.rows;
+        id    = table.id;
+
+        status = "";
+        t      = "";
+        for (ridx in rows) {
+            row    = rows[ridx];
+            zoneid = row[0];
+            zone   = row[1];
+            appcls = row[2];
+            pid    = row[4];
+
+            status += t + zone + " zone: " + appcls;
+            if (pid)
+                status += " (pid " + pid + ")";
+        }
+
+        switch (name) {
+        case 'audio_playback_owner':
+            setStatus(status, 'audio_status');
+            break;
+        case 'video_playback_owner':
+            setStatus(status, 'video_status');
+            break;
+        default:
+            break;
+        }
+    }
+}
+
+
+function setContent(id, text) {
+    var elem = document.getElementById(id);
+
+    if (elem)
+        elem.textContent = text;
+}
+
+
+function setStatus(status, which) {
+    if (which)
+        setContent(which, status)
+    else
+        setContent('general_status', status);
+}
+
+
+function enableButton(name) {
+    var btn = document.getElementById(name);
+
+    if (btn)
+        btn.disabled = false;
+}
+
+
+function disableButton(name) {
+    var btn = document.getElementById(name);
+
+    if (btn)
+        btn.disabled = true;
+}
+
+
+function initialize() {
+    disableButton("disconnect");
+    disableButton("update");
+    enableButton("connect");
+}
+
+
+function connect() {
+    var ctl;
+
+    setStatus("connecting...");
+
+    domctl_debug_enable([DOMCTL_COMM, DOMCTL_NOTIFY, DOMCTL_MISC]);
+
+    ctx = {};
+    ctl = new DomainController('wrt-test', [
+          {
+              "table":   "wrt_test",
+              "columns": "count integer",
+              "index":   ""
+          }
+        ],
+        [ {
+              "table":   "audio_playback_owner",
+              "columns": "*",
+              "where":   "",
+              "maxrows": 0
+          },
+          {
+              "table":   "video_playback_owner",
+              "columns": "*",
+              "where":   "",
+              "maxrows": 0
+          }
+        ]);
+
+    ctl.ondisconnect = controllerDisconnected;
+    ctl.onerror      = controllerFailed;
+    ctl.onevent      = controllerEvent;
+    ctl.user_data    = ctx;
+
+    ctx.ctl = ctl;
+    ctx.cnt = 0;
+
+    console.log("server URI: " + ctl.socketUri(document.URL));
+    ctl.connect(ctl.socketUri(document.URL));
+}
+
+
+function disconnect() {
+    var ctl = ctx.ctl;
+
+    setStatus("disconnecting...");
+
+    ctl.disconnect();
+}
+
+
+function update_ack(e) {
+    console.log("Data updated successfully.");
+    setStatus(ctx.cnt - 1, "data_status");
+}
+
+
+function update_nak(e) {
+    var msg = e.errmsg ? e.errmsg : "unknown error";
+
+    console.log("Data update failed.");
+
+    setStatus("error (" + msg + ")", "data_status");
+}
+
+
+function update() {
+    var ctl = ctx.ctl;
+    var cnt = ctx.cnt++;
+    var pending;
+
+    try {
+        pending = ctl.set([{table: "wrt_test", rows: [[cnt]]}]);
+        pending.onsuccess = update_ack;
+        pending.onerror   = update_nak;
+    } catch (e) {
+        console.log("Failed to update data on server (" + e.message + ")");
+    }
+}
+
+
+</script>
+
+</head>
+
+<body onload="initialize();">
+
+
+<table>
+    <tr>
+        <th align=left>Actions:</th>
+        <td align=left>
+          <input type=button id=connect value="Connect"
+               onclick="connect();">
+          <input type=button id=disconnect value="Disconnect"
+               onclick="disconnect();" disabled>
+          <input type=button id=update value="Update Data"
+               onclick="update();" disabled>
+
+        </td>
+    </tr>
+    <tr>
+        <th align=left>Status:</th>
+       <td align=left><div id=general_status>disconnected</div></td>
+    </tr>
+    <tr>
+        <th align=left>Audio:</th>
+       <td align=left><div id=audio_status>unknown</div></td>
+    </tr>
+    <tr>
+        <th align=left>Video:</th>
+       <td align=left><div id=video_status>unknown</div></td>
+    </tr>
+    <tr>
+        <th align=left>Data:</th>
+       <td align=left><div id=data_status>unknown</div></td>
+    </tr>
+</table>
+
+
+</body>
+</html>
diff --git a/src/plugins/domain-control/domain-control-types.h b/src/plugins/domain-control/domain-control-types.h
new file mode 100644 (file)
index 0000000..bc418bf
--- /dev/null
@@ -0,0 +1,171 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_TYPES_H__
+#define __MURPHY_DOMAIN_CONTROL_TYPES_H__
+
+#include <stdbool.h>
+
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/core/context.h>
+#include <murphy-db/mql.h>
+
+#include "client.h"
+
+typedef struct pep_proxy_s pep_proxy_t;
+typedef struct pep_table_s pep_table_t;
+typedef struct pep_watch_s pep_watch_t;
+typedef struct pdp_s       pdp_t;
+typedef union  msg_u       msg_t;
+
+/*
+ * a domain controller (on the client side)
+ */
+
+struct mrp_domctl_s {
+    char                    *name;       /* enforcment point name */
+    mrp_mainloop_t          *ml;         /* main loop */
+    mrp_sockaddr_t           addr;       /* server address */
+    socklen_t                addrlen;    /* address length */
+    mrp_timer_t             *ctmr;       /* connection timer */
+    int                      cival;      /* connection attempt interval */
+    const char              *ttype;      /* transport type */
+    mrp_transport_t         *t;          /* transport towards murphy */
+    int                      connected;  /* transport is up */
+    mrp_domctl_table_t      *tables;     /* owned tables */
+    int                      ntable;     /* number of owned tables */
+    mrp_domctl_watch_t      *watches;    /* watched tables */
+    int                      nwatch;     /* number of watched tables */
+    mrp_domctl_connect_cb_t  connect_cb; /* connection state change callback */
+    mrp_domctl_watch_cb_t    watch_cb;   /* watched table change callback */
+    void                    *user_data;  /* opqaue user data for callbacks */
+    int                      busy;       /* non-zero if a callback is active */
+    int                      destroyed:1;/* non-zero if destroy pending */
+    uint32_t                 seqno;      /* request sequence number */
+    mrp_list_hook_t          pending;    /* queue of outstanding requests */
+    mrp_list_hook_t          methods;    /* registered proxied methods */
+};
+
+
+/*
+ * a table associated with or tracked by an enforcement point
+ */
+
+struct pep_table_s {
+    char               *name;            /* table name */
+    char               *mql_columns;     /* column definition clause */
+    char               *mql_index;       /* index column list */
+    mrp_list_hook_t     hook;            /* to list of tables */
+    mqi_handle_t        h;               /* table handle */
+    mqi_column_def_t   *columns;         /* column definitions */
+    mqi_column_desc_t  *coldesc;         /* column descriptors */
+    int                 ncolumn;         /* number of columns */
+    int                 idx_col;         /* column index of index column */
+    mrp_list_hook_t     watches;         /* watches for this table */
+    bool                changed;         /* whether has unsynced changes */
+};
+
+
+/*
+ * a table watch
+ */
+
+struct pep_watch_s {
+    pep_table_t     *table;              /* table being watched */
+    char            *mql_columns;        /* column list to select */
+    char            *mql_where;          /* where clause for select */
+    int              max_rows;           /* max number of rows to select */
+    pep_proxy_t     *proxy;              /* enforcement point */
+    int              id;                 /* table id within proxy */
+    mrp_list_hook_t  tbl_hook;           /* hook to table watch list */
+    mrp_list_hook_t  pep_hook;           /* hook to proxy watch list */
+    bool             notify;             /* whether to notify this watch */
+};
+
+
+/*
+ * a policy enforcement point (on the server side)
+ */
+
+typedef struct {
+    int  (*send_msg)(pep_proxy_t *proxy, msg_t *msg);
+    void (*unref)(void *data);
+    int  (*create_notify)(pep_proxy_t *proxy);
+    int  (*update_notify)(pep_proxy_t *proxy, int tblid, mql_result_t *r);
+    int  (*send_notify)(pep_proxy_t *proxy);
+    void (*free_notify)(pep_proxy_t *proxy);
+} proxy_ops_t;
+
+
+
+struct pep_proxy_s {
+    char              *name;             /* enforcement point name */
+    pdp_t             *pdp;              /* domain controller context */
+    mrp_transport_t   *t;                /* associated transport */
+    mrp_list_hook_t    hook;             /* to list of all enforcement points */
+    pep_table_t       *tables;           /* tables owned by this */
+    int                ntable;           /* number of tables */
+    mrp_list_hook_t    watches;          /* tables watched by this */
+    proxy_ops_t       *ops;              /* transport/messaging operations */
+    uint32_t           seqno;            /* request sequence number */
+    mrp_list_hook_t    pending;          /* pending method invocations */
+    void              *notify_msg;       /* notification being built */
+    int                notify_ntable;    /* number of changed tables */
+    int                notify_ncolumn;   /* total columns in notification */
+    int                notify_fail : 1;  /* notification failure */
+    int                notify : 1;       /* whether has pending notifications */
+};
+
+
+/*
+ * policy domain controller context
+ */
+
+struct pdp_s {
+    mrp_context_t   *ctx;                /* murphy context */
+    const char      *address;            /* external transport address */
+    mrp_transport_t *extt;               /* external transport */
+    mrp_transport_t *wrtt;               /* WRT transport */
+    mrp_transport_t *intt;               /* internal transport */
+    mrp_list_hook_t  proxies;            /* list of enforcement points */
+    mrp_list_hook_t  tables;             /* list of tables we track */
+    mrp_htbl_t      *watched;            /* tracked tables by name */
+    mrp_deferred_t  *notify;             /* deferred notification */
+    bool             notify_scheduled;   /* is notification scheduled? */
+    void            *reh;                /* resolver event handler */
+    int              ractive;            /* resolver active */
+    bool             rblocked;           /* resolver blocked update */
+};
+
+
+
+#endif /* __MURPHY_DOMAIN_CONTROL_TYPES_H__ */
diff --git a/src/plugins/domain-control/domain-control.c b/src/plugins/domain-control/domain-control.c
new file mode 100644 (file)
index 0000000..4a0f8fa
--- /dev/null
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/wsck-transport.h>
+
+#include <murphy/resolver/resolver.h>
+#include <murphy/core/domain.h>
+
+#include "proxy.h"
+#include "message.h"
+#include "table.h"
+#include "notify.h"
+#include "domain-control.h"
+
+static mrp_transport_t *create_transport(pdp_t *pdp, const char *address);
+static void destroy_transport(mrp_transport_t *t);
+
+static int invoke_handler(void *handler_data, const char *id,
+                          const char *method, int narg,
+                          mrp_domctl_arg_t *args,
+                          mrp_domain_return_cb_t return_cb,
+                          void *user_data);
+
+static uint32_t RESEVT_START, RESEVT_DONE, RESEVT_FAIL;
+
+
+static void resolver_event_cb(mrp_event_watch_t *w, uint32_t id, int format,
+                              void  *data, void *user_data)
+{
+    pdp_t *pdp = (pdp_t *)user_data;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(format);
+    MRP_UNUSED(data);
+
+    if (id == RESEVT_START)
+        pdp->ractive++;
+    else if (id == RESEVT_DONE || id == RESEVT_FAIL)
+        pdp->ractive--;
+
+    mrp_debug("resolver is %s active", pdp->ractive == 1 ? "now" :
+              pdp->ractive > 1 ? "still" : "no longer");
+
+    if (pdp->ractive == 0) {
+        schedule_notification(pdp);
+        pdp->rblocked = false;
+    }
+}
+
+
+static int add_resolver_trigger(pdp_t *pdp)
+{
+    mrp_context_t   *ctx = pdp->ctx;
+    mrp_mainloop_t  *ml  = ctx->ml;
+    mrp_event_bus_t *bus = mrp_event_bus_get(ml, MRP_RESOLVER_BUS);
+    mrp_event_mask_t mask;
+
+    if (bus == NULL)
+        return FALSE;
+
+    RESEVT_START = mrp_event_id(MRP_RESOLVER_EVENT_STARTED);
+    RESEVT_DONE  = mrp_event_id(MRP_RESOLVER_EVENT_DONE);
+    RESEVT_FAIL  = mrp_event_id(MRP_RESOLVER_EVENT_FAILED);
+
+    mrp_mask_init(&mask);
+    if (!mrp_mask_set(&mask, RESEVT_START) ||
+        !mrp_mask_set(&mask, RESEVT_DONE ) ||
+        !mrp_mask_set(&mask, RESEVT_FAIL ))
+        return FALSE;
+
+    pdp->reh = mrp_event_add_watch_mask(bus, &mask, resolver_event_cb, pdp);
+
+    return pdp->reh != NULL;
+}
+
+
+static void del_resolver_trigger(pdp_t *pdp)
+{
+    mrp_event_del_watch(pdp->reh);
+    pdp->reh = NULL;
+}
+
+
+pdp_t *create_domain_control(mrp_context_t *ctx,
+                             const char *extaddr, const char *intaddr,
+                             const char *wrtaddr, const char *httpdir)
+{
+    pdp_t *pdp;
+
+    pdp = mrp_allocz(sizeof(*pdp));
+
+    if (pdp != NULL) {
+        pdp->ctx     = ctx;
+        pdp->address = extaddr;
+
+        if (init_proxies(pdp) && init_tables(pdp)) {
+
+            if (!add_resolver_trigger(pdp))
+                goto fail;
+
+            if (extaddr && *extaddr)
+                pdp->extt = create_transport(pdp, extaddr);
+
+            if (intaddr && *intaddr)
+                pdp->intt = create_transport(pdp, intaddr);
+
+            if (wrtaddr && *wrtaddr) {
+                pdp->wrtt = create_transport(pdp, wrtaddr);
+
+                if (pdp->wrtt != NULL) {
+                    const char *sm_opt = MRP_WSCK_OPT_SENDMODE;
+                    const char *sm_val = MRP_WSCK_SENDMODE_TEXT;
+                    const char *hd_opt = MRP_WSCK_OPT_HTTPDIR;
+                    const char *hd_val = httpdir;
+
+                    mrp_transport_setopt(pdp->wrtt, sm_opt, sm_val);
+                    mrp_transport_setopt(pdp->wrtt, hd_opt, hd_val);
+                }
+            }
+
+
+            if ((!extaddr || !*extaddr || pdp->extt != NULL) &&
+                (!intaddr || !*intaddr || pdp->intt != NULL) &&
+                (!wrtaddr || !*wrtaddr || pdp->wrtt != NULL)) {
+                mrp_set_domain_invoke_handler(ctx, invoke_handler, pdp);
+                return pdp;
+            }
+        }
+
+    fail:
+        destroy_domain_control(pdp);
+    }
+
+    return NULL;
+}
+
+
+void destroy_domain_control(pdp_t *pdp)
+{
+    if (pdp != NULL) {
+        del_resolver_trigger(pdp);
+        destroy_proxies(pdp);
+        destroy_tables(pdp);
+        destroy_transport(pdp->extt);
+        destroy_transport(pdp->intt);
+        destroy_transport(pdp->wrtt);
+
+        mrp_free(pdp);
+    }
+}
+
+
+static void notify_cb(mrp_deferred_t *d, void *user_data)
+{
+    pdp_t *pdp = (pdp_t *)user_data;
+
+    mrp_disable_deferred(d);
+    pdp->notify_scheduled = false;
+    notify_table_changes(pdp);
+}
+
+
+void schedule_notification(pdp_t *pdp)
+{
+
+    if (pdp->notify == NULL)
+        pdp->notify = mrp_add_deferred(pdp->ctx->ml, notify_cb, pdp);
+
+    if (!pdp->notify_scheduled) {
+        mrp_debug("scheduling client notification");
+        mrp_enable_deferred(pdp->notify);
+        pdp->notify_scheduled = true;
+    }
+}
+
+
+static int msg_send_message(pep_proxy_t *proxy, msg_t *msg)
+{
+    mrp_msg_t *tmsg;
+
+    tmsg = msg_encode_message(msg);
+
+    if (tmsg != NULL) {
+        mrp_transport_send(proxy->t, tmsg);
+        mrp_msg_unref(tmsg);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int msg_send_ack(pep_proxy_t *proxy, uint32_t seq)
+{
+    ack_msg_t ack;
+
+    mrp_clear(&ack);
+    ack.type = MSG_TYPE_ACK;
+    ack.seq  = seq;
+
+    return proxy->ops->send_msg(proxy, (msg_t *)&ack);
+}
+
+
+static int msg_send_nak(pep_proxy_t *proxy, uint32_t seq,
+                        int32_t error, const char *msg)
+{
+    nak_msg_t nak;
+
+    mrp_clear(&nak);
+    nak.type   =  MSG_TYPE_NAK;
+    nak.seq    = seq;
+    nak.error  = error;
+    nak.msg    = msg;
+
+    return proxy->ops->send_msg(proxy, (msg_t *)&nak);
+}
+
+
+
+static void process_register(pep_proxy_t *proxy, register_msg_t *reg)
+{
+    int         error;
+    const char *errmsg;
+
+    if (register_proxy(proxy, reg->name, reg->tables, reg->ntable,
+                       reg->watches, reg->nwatch, &error, &errmsg)) {
+        msg_send_ack(proxy, reg->seq);
+        schedule_notification(proxy->pdp);
+    }
+    else
+        msg_send_nak(proxy, reg->seq, error, errmsg);
+}
+
+
+static void process_unregister(pep_proxy_t *proxy, unregister_msg_t *unreg)
+{
+    msg_send_ack(proxy, unreg->seq);
+}
+
+
+static void process_set(pep_proxy_t *proxy, set_msg_t *set)
+{
+    int         error;
+    const char *errmsg;
+
+    if (set_proxy_tables(proxy, set->tables, set->ntable, &error, &errmsg)) {
+        msg_send_ack(proxy, set->seq);
+    }
+    else
+        msg_send_nak(proxy, set->seq, error, errmsg);
+}
+
+
+static void process_invoke(pep_proxy_t *proxy, invoke_msg_t *invoke)
+{
+    mrp_context_t          *ctx = proxy->pdp->ctx;
+    mrp_domain_invoke_cb_t  cb;
+    void                   *user_data;
+    int                     max_out;
+    mrp_domctl_arg_t       *args;
+    int                     narg;
+    return_msg_t            ret;
+    mrp_msg_t              *msg;
+    int                     i;
+
+    mrp_clear(&ret);
+
+    ret.type = MSG_TYPE_RETURN;
+    ret.seq  = invoke->seq;
+    args     = NULL;
+    narg     = 0;
+
+    if (!mrp_lookup_domain_method(ctx, invoke->name, &cb, &max_out,&user_data)) {
+        ret.error = MRP_DOMCTL_NOTFOUND;
+    }
+    else {
+        ret.error = MRP_DOMCTL_OK;
+
+        narg = ret.narg = max_out;
+
+        if (narg > 0) {
+            args = ret.args = alloca(narg * sizeof(args[0]));
+            memset(args, 0, narg * sizeof(args[0]));
+        }
+
+        ret.retval = cb(invoke->narg, invoke->args,
+                        &ret.narg, ret.args, user_data);
+    }
+
+    msg = msg_encode_message((msg_t *)&ret);
+
+    if (msg != NULL) {
+        mrp_transport_send(proxy->t, msg);
+        mrp_msg_unref(msg);
+    }
+
+    narg = ret.narg;
+    for (i = 0; i < narg; i++) {
+        if (args[i].type == MRP_DOMCTL_STRING)
+            mrp_free((char *)args[i].str);
+        else if (MRP_DOMCTL_IS_ARRAY(args[i].type)) {
+            uint32_t j;
+
+            for (j = 0; j < args[i].size; j++)
+                if (MRP_DOMCTL_ARRAY_TYPE(args[i].type) == MRP_DOMCTL_STRING)
+                    mrp_free(((char **)args[i].arr)[j]);
+
+            mrp_free(args[i].arr);
+        }
+    }
+}
+
+
+static void process_return(pep_proxy_t *proxy, return_msg_t *ret)
+{
+    uint32_t                id = ret->seq;
+    mrp_domain_return_cb_t  cb;
+    void                   *user_data;
+
+    if (!proxy_dequeue_pending(proxy, id, &cb, &user_data))
+        return;
+
+    cb(ret->error, ret->retval, ret->narg, ret->args, user_data);
+}
+
+
+static void process_message(pep_proxy_t *proxy, msg_t *msg)
+{
+    char *name  = proxy->name ? proxy->name : "<unknown>";
+
+    switch (msg->any.type) {
+    case MSG_TYPE_REGISTER:
+        process_register(proxy, &msg->reg);
+        break;
+    case MSG_TYPE_UNREGISTER:
+        process_unregister(proxy, &msg->unreg);
+        break;
+    case MSG_TYPE_SET:
+        process_set(proxy, &msg->set);
+        break;
+    case MSG_TYPE_INVOKE:
+        process_invoke(proxy, &msg->invoke);
+        break;
+    case MSG_TYPE_RETURN:
+        process_return(proxy, &msg->ret);
+        break;
+    default:
+        mrp_log_error("Unexpected message 0x%x from client %s.",
+                      msg->any.type, name);
+        break;
+    }
+}
+
+
+static int invoke_handler(void *handler_data, const char *domain,
+                          const char *method, int narg,
+                          mrp_domctl_arg_t *args,
+                          mrp_domain_return_cb_t return_cb,
+                          void *user_data)
+{
+    pdp_t        *pdp   = (pdp_t *)handler_data;
+    pep_proxy_t  *proxy = find_proxy(pdp, domain);
+    uint32_t      id;
+    invoke_msg_t  invoke;
+
+    if (proxy == NULL)
+        return FALSE;
+
+    id = proxy_queue_pending(proxy, return_cb, user_data);
+
+    if (!id)
+        return FALSE;
+
+    mrp_clear(&invoke);
+
+    invoke.type  = MSG_TYPE_INVOKE;
+    invoke.seq   = id;
+    invoke.name  = method;
+    invoke.noret = (return_cb == NULL);
+    invoke.narg  = narg;
+    invoke.args  = args;
+
+    return msg_send_message(proxy, (msg_t *)&invoke);
+}
+
+
+static int msg_op_send_msg(pep_proxy_t *proxy, msg_t *msg)
+{
+    return msg_send_message(proxy, msg);
+}
+
+
+static void msg_op_unref_msg(void *msg)
+{
+    mrp_msg_unref((mrp_msg_t *)msg);
+}
+
+
+static int msg_op_create_notify(pep_proxy_t *proxy)
+{
+    if (proxy->notify_msg == NULL)
+        proxy->notify_msg = msg_create_notify();
+
+    if (proxy->notify_msg != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static int msg_op_update_notify(pep_proxy_t *proxy, int tblid, mql_result_t *r)
+{
+    int n;
+
+    n = msg_update_notify((mrp_msg_t *)proxy->notify_msg, tblid, r);
+
+    if (n >= 0) {
+        proxy->notify_ncolumn += n;
+        proxy->notify_ntable++;
+    }
+
+    return n;
+}
+
+
+static int msg_op_send_notify(pep_proxy_t *proxy)
+{
+    mrp_msg_t *msg     = proxy->notify_msg;
+    uint16_t   nchange = proxy->notify_ntable;
+    uint16_t   ntotal  = proxy->notify_ncolumn;
+
+    mrp_msg_set(msg, MSG_UINT16(NCHANGE, nchange));
+    mrp_msg_set(msg, MSG_UINT16(NTOTAL , ntotal));
+
+    return mrp_transport_send(proxy->t, msg);
+}
+
+
+static void msg_op_free_notify(pep_proxy_t *proxy)
+{
+    mrp_msg_unref((mrp_msg_t *)proxy->notify_msg);
+    proxy->notify_msg = NULL;
+}
+
+
+static void msg_connect_cb(mrp_transport_t *t, void *user_data)
+{
+    static proxy_ops_t ops = {
+        .send_msg      = msg_op_send_msg,
+        .unref         = msg_op_unref_msg,
+        .create_notify = msg_op_create_notify,
+        .update_notify = msg_op_update_notify,
+        .send_notify   = msg_op_send_notify,
+        .free_notify   = msg_op_free_notify,
+    };
+
+    pdp_t       *pdp = (pdp_t *)user_data;
+    pep_proxy_t *proxy;
+    int          flags;
+
+    proxy = create_proxy(pdp);
+
+    if (proxy != NULL) {
+        flags    = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+        proxy->t = mrp_transport_accept(t, proxy, flags);
+
+        if (proxy->t != NULL) {
+            proxy->ops = &ops;
+            mrp_log_info("Accepted new client connection.");
+        }
+        else {
+            mrp_log_error("Failed to accept new client connection.");
+            destroy_proxy(proxy);
+        }
+    }
+}
+
+
+static void msg_closed_cb(mrp_transport_t *t, int error, void *user_data)
+{
+    pep_proxy_t *proxy = (pep_proxy_t *)user_data;
+    char        *name  = proxy && proxy->name ? proxy->name : "<unknown>";
+
+    MRP_UNUSED(t);
+
+    if (error)
+        mrp_log_error("Transport to client %s closed (%d: %s).",
+                      name, error, strerror(error));
+    else
+        mrp_log_info("Transport to client %s closed.", name);
+
+    mrp_log_info("Destroying client %s.", name);
+    destroy_proxy(proxy);
+}
+
+
+static void msg_recv_cb(mrp_transport_t *t, mrp_msg_t *tmsg, void *user_data)
+{
+    pep_proxy_t *proxy = (pep_proxy_t *)user_data;
+    char        *name;
+    msg_t       *msg;
+    uint32_t     seqno;
+
+    MRP_UNUSED(t);
+
+    /*
+      mrp_log_info("Message from client %p:", proxy);
+      mrp_msg_dump(msg, stdout);
+    */
+
+    if (proxy != NULL) {
+        name = proxy->name ? proxy->name : "<unknown>";
+        msg  = msg_decode_message(tmsg);
+
+        if (msg != NULL) {
+            process_message(proxy, msg);
+            msg_free_message(msg);
+        }
+        else {
+            if (!mrp_msg_get(tmsg, MSG_UINT32(MSGSEQ, &seqno), MSG_END))
+                seqno = 0;
+            mrp_log_error("Failed to decode message from %s.", name);
+            msg_send_nak(proxy, seqno, 1, "failed to decode message");
+        }
+    }
+}
+
+
+static int wrt_send_message(pep_proxy_t *proxy, msg_t *msg)
+{
+    mrp_json_t *tmsg;
+
+    tmsg = json_encode_message(msg);
+
+    if (tmsg != NULL) {
+        mrp_transport_sendcustom(proxy->t, tmsg);
+        mrp_json_unref(tmsg);
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int wrt_op_send_msg(pep_proxy_t *proxy, msg_t *msg)
+{
+    return wrt_send_message(proxy, msg);
+}
+
+
+static void wrt_op_unref_msg(void *msg)
+{
+    mrp_json_unref((mrp_json_t *)msg);
+}
+
+
+static int wrt_op_create_notify(pep_proxy_t *proxy)
+{
+    if (proxy->notify_msg == NULL)
+        proxy->notify_msg = json_create_notify();
+
+    if (proxy->notify_msg != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+static int wrt_op_update_notify(pep_proxy_t *proxy, int tblid, mql_result_t *r)
+{
+    int n;
+
+    n = json_update_notify((mrp_json_t *)proxy->notify_msg, tblid, r);
+
+    if (n >= 0) {
+        proxy->notify_ncolumn += n;
+        proxy->notify_ntable++;
+    }
+
+    return n;
+}
+
+
+static int wrt_op_send_notify(pep_proxy_t *proxy)
+{
+    mrp_json_t *msg     = proxy->notify_msg;
+    int         nchange = proxy->notify_ntable;
+    int         ntotal  = proxy->notify_ncolumn;
+
+    if (mrp_json_add_integer(msg, "nchange", nchange) &&
+        mrp_json_add_integer(msg, "ntotal" , ntotal))
+        return mrp_transport_sendcustom(proxy->t, msg);
+    else
+        return FALSE;
+}
+
+
+static void wrt_op_free_notify(pep_proxy_t *proxy)
+{
+    mrp_json_unref((mrp_json_t *)proxy->notify_msg);
+    proxy->notify_msg = NULL;
+}
+
+
+static void wrt_connect_cb(mrp_transport_t *t, void *user_data)
+{
+    static proxy_ops_t ops = {
+        .send_msg      = wrt_op_send_msg,
+        .unref         = wrt_op_unref_msg,
+        .create_notify = wrt_op_create_notify,
+        .update_notify = wrt_op_update_notify,
+        .send_notify   = wrt_op_send_notify,
+        .free_notify   = wrt_op_free_notify,
+    };
+
+    pdp_t       *pdp = (pdp_t *)user_data;
+    pep_proxy_t *proxy;
+    int          flags;
+
+    proxy = create_proxy(pdp);
+
+    if (proxy != NULL) {
+        flags    = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+        proxy->t = mrp_transport_accept(t, proxy, flags);
+
+        if (proxy->t != NULL) {
+            proxy->ops = &ops;
+            mrp_log_info("Accepted new client connection.");
+        }
+        else {
+            mrp_log_error("Failed to accept new client connection.");
+            destroy_proxy(proxy);
+        }
+    }
+}
+
+
+static void wrt_closed_cb(mrp_transport_t *t, int error, void *user_data)
+{
+    pep_proxy_t *proxy = (pep_proxy_t *)user_data;
+    char        *name  = proxy && proxy->name ? proxy->name : "<unknown>";
+
+    MRP_UNUSED(t);
+
+    if (error)
+        mrp_log_error("Transport to client %s closed (%d: %s).",
+                      name, error, strerror(error));
+    else
+        mrp_log_info("Transport to client %s closed.", name);
+
+    mrp_log_info("Destroying client %s.", name);
+    destroy_proxy(proxy);
+}
+
+
+static void wrt_recv_cb(mrp_transport_t *t, void *data, void *user_data)
+{
+    pep_proxy_t *proxy = (pep_proxy_t *)user_data;
+    char        *name;
+    msg_t       *msg;
+    int          seqno;
+
+    MRP_UNUSED(t);
+
+    /*
+      mrp_log_info("Message from WRT client %p:", proxy);
+    */
+
+    if (proxy != NULL) {
+        name = proxy->name ? proxy->name : "<unknown>";
+        msg  = json_decode_message(data);
+
+        if (msg != NULL) {
+            process_message(proxy, msg);
+            msg_free_message(msg);
+        }
+        else {
+            if (!mrp_json_get_integer(data, "seq", &seqno))
+                seqno = 0;
+            mrp_log_error("Failed to decode message from %s.", name);
+            msg_send_nak(proxy, seqno, 1, "failed to decode message");
+        }
+    }
+}
+
+
+
+
+static mrp_transport_t *create_transport(pdp_t *pdp, const char *address)
+{
+    static mrp_transport_evt_t msg_evt, wrt_evt;
+
+    mrp_transport_evt_t *e;
+    mrp_transport_t     *t;
+    mrp_sockaddr_t       addr;
+    socklen_t            alen;
+    int                  flags;
+    const char          *type;
+
+    t    = NULL;
+    alen = mrp_transport_resolve(NULL, address, &addr, sizeof(addr), &type);
+
+    if (alen <= 0) {
+        mrp_log_error("Failed to resolve transport address '%s'.", address);
+        return NULL;
+    }
+
+    flags = MRP_TRANSPORT_REUSEADDR;
+
+    if (strncmp(address, "wsck", 4) != 0) {
+        e = &msg_evt;
+
+        e->connection  = msg_connect_cb;
+        e->closed      = msg_closed_cb;
+        e->recvmsg     = msg_recv_cb;
+        e->recvmsgfrom = NULL;
+    }
+    else {
+        e = &wrt_evt;
+
+        e->connection     = wrt_connect_cb;
+        e->closed         = wrt_closed_cb;
+        e->recvcustom     = wrt_recv_cb;
+        e->recvcustomfrom = NULL;
+
+        flags |= MRP_TRANSPORT_MODE_CUSTOM;
+    }
+
+    t = mrp_transport_create(pdp->ctx->ml, type, e, pdp, flags);
+
+    if (t != NULL) {
+        if (mrp_transport_bind(t, &addr, alen) && mrp_transport_listen(t, 4))
+            return t;
+        else {
+            mrp_log_error("Failed to bind to transport address '%s'.", address);
+            mrp_transport_destroy(t);
+        }
+    }
+    else
+        mrp_log_error("Failed to create transport '%s'.", address);
+
+    return NULL;
+}
+
+
+static void destroy_transport(mrp_transport_t *t)
+{
+    mrp_transport_destroy(t);
+}
diff --git a/src/plugins/domain-control/domain-control.h b/src/plugins/domain-control/domain-control.h
new file mode 100644 (file)
index 0000000..476e573
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_H__
+#define __MURPHY_DOMAIN_CONTROL_H__
+
+#include "domain-control-types.h"
+
+pdp_t *create_domain_control(mrp_context_t *ctx, const char *ext_addr,
+                             const char *int_addr, const char *wrt_addr,
+                             const char *httpdir);
+void destroy_domain_control(pdp_t *pdp);
+
+void schedule_notification(pdp_t *pdp);
+
+#endif /* __MURPHY_DOMAIN_CONTROL_H__ */
diff --git a/src/plugins/domain-control/message.c b/src/plugins/domain-control/message.c
new file mode 100644 (file)
index 0000000..ed7472e
--- /dev/null
@@ -0,0 +1,1671 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/mm.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/json.h>
+
+#include <murphy-db/mql.h>
+
+#include "message.h"
+
+
+static void unref_wire(msg_t *msg)
+{
+    if (msg->any.wire && msg->any.unref_wire) {
+        msg->any.unref_wire(msg->any.wire);
+        msg->any.wire = NULL;
+    }
+}
+
+
+static void msg_unref_wire(void *wire)
+{
+    mrp_msg_unref((mrp_msg_t *)wire);
+}
+
+
+static void msg_free_register(msg_t *msg)
+{
+    register_msg_t *reg = (register_msg_t *)msg;
+
+    if (reg != NULL) {
+        mrp_free(reg->tables);
+        mrp_free(reg->watches);
+        unref_wire(msg);
+
+        mrp_free(reg);
+    }
+}
+
+
+mrp_msg_t *msg_encode_register(register_msg_t *reg)
+{
+    mrp_msg_t          *msg;
+    mrp_domctl_table_t *t;
+    mrp_domctl_watch_t *w;
+    int                 i;
+
+    msg = mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_REGISTER),
+                         MSG_UINT32(MSGSEQ , 0),
+                         MSG_STRING(NAME   , reg->name),
+                         MSG_UINT16(NTABLE , reg->ntable),
+                         MSG_UINT16(NWATCH , reg->nwatch),
+                         MSG_END);
+
+    for (i = 0, t = reg->tables; i < reg->ntable; i++, t++) {
+        mrp_msg_append(msg, MSG_STRING(TBLNAME, t->table));
+        mrp_msg_append(msg, MSG_STRING(COLUMNS, t->mql_columns));
+        mrp_msg_append(msg, MSG_STRING(INDEX  , t->mql_index));
+    }
+
+    for (i = 0, w = reg->watches; i < reg->nwatch; i++, w++) {
+        mrp_msg_append(msg, MSG_STRING(TBLNAME, w->table));
+        mrp_msg_append(msg, MSG_STRING(COLUMNS, w->mql_columns));
+        mrp_msg_append(msg, MSG_STRING(WHERE  , w->mql_where));
+        mrp_msg_append(msg, MSG_UINT16(MAXROWS, w->max_rows));
+    }
+
+    return msg;
+}
+
+
+msg_t *msg_decode_register(mrp_msg_t *msg)
+{
+    register_msg_t     *reg;
+    void               *it;
+    mrp_domctl_table_t *t;
+    mrp_domctl_watch_t *w;
+    char               *name, *table, *columns, *index, *where;
+    uint16_t            ntable, nwatch, max_rows;
+    uint32_t            seqno;
+    int                 i;
+
+    it = NULL;
+
+    if (!mrp_msg_iterate_get(msg, &it,
+                             MSG_UINT32(MSGSEQ, &seqno),
+                             MSG_STRING(NAME  , &name),
+                             MSG_UINT16(NTABLE, &ntable),
+                             MSG_UINT16(NWATCH, &nwatch),
+                             MSG_END))
+        return NULL;
+
+    reg = mrp_allocz(sizeof(*reg));
+
+    if (reg == NULL)
+        return NULL;
+
+    reg->type    = MSG_TYPE_REGISTER;
+    reg->seq     = seqno;
+    reg->name    = name;
+    reg->tables  = mrp_allocz(sizeof(*reg->tables)  * ntable);
+    reg->watches = mrp_allocz(sizeof(*reg->watches) * nwatch);
+
+    if ((reg->tables == NULL && ntable) || (reg->watches == NULL && nwatch))
+        goto fail;
+
+    for (i = 0, t = reg->tables; i < ntable; i++, t++) {
+        if (mrp_msg_iterate_get(msg, &it,
+                                MSG_STRING(TBLNAME, &table),
+                                MSG_STRING(COLUMNS, &columns),
+                                MSG_STRING(INDEX  , &index),
+                                MSG_END)) {
+            t->table       = table;
+            t->mql_columns = columns;
+            t->mql_index   = index;
+        }
+        else
+            goto fail;
+    }
+
+    reg->ntable = ntable;
+
+    for (i = 0, w = reg->watches; i < nwatch; i++, w++) {
+        if (mrp_msg_iterate_get(msg, &it,
+                                MSG_STRING(TBLNAME, &table),
+                                MSG_STRING(COLUMNS, &columns),
+                                MSG_STRING(WHERE  , &where),
+                                MSG_UINT16(MAXROWS, &max_rows),
+                                MSG_END)) {
+            w->table       = table;
+            w->mql_columns = columns;
+            w->mql_where   = where;
+            w->max_rows    = max_rows;
+        }
+        else
+            goto fail;
+    }
+
+    reg->nwatch = nwatch;
+
+    reg->wire       = mrp_msg_ref(msg);
+    reg->unref_wire = msg_unref_wire;
+
+    return (msg_t *)reg;
+
+ fail:
+    msg_free_register((msg_t *)reg);
+
+    return NULL;
+}
+
+
+void msg_free_unregister(msg_t *msg)
+{
+    unregister_msg_t *ureg = (unregister_msg_t *)msg;
+
+    if (ureg != NULL) {
+        unref_wire(msg);
+        mrp_free(ureg);
+    }
+}
+
+
+mrp_msg_t *msg_encode_unregister(unregister_msg_t *ureg)
+{
+    return mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_UNREGISTER),
+                          MSG_UINT32(MSGSEQ , ureg->seq),
+                          MSG_END);
+}
+
+
+msg_t *msg_decode_unregister(mrp_msg_t *msg)
+{
+    unregister_msg_t *ureg;
+    void             *it;
+    uint32_t          seqno;
+
+    ureg = mrp_allocz(sizeof(*ureg));
+
+    if (ureg != NULL) {
+        it = NULL;
+
+        if (mrp_msg_iterate_get(msg, &it,
+                                MSG_UINT32(MSGSEQ, &seqno),
+                                MSG_END)) {
+            ureg->type = MSG_TYPE_UNREGISTER;
+            ureg->seq  = seqno;
+
+            return (msg_t *)ureg;
+        }
+
+        msg_free_unregister((msg_t *)ureg);
+    }
+
+    return NULL;
+}
+
+
+void msg_free_ack(msg_t *msg)
+{
+    ack_msg_t *ack = (ack_msg_t *)msg;
+
+    if (ack != NULL) {
+        unref_wire(msg);
+        mrp_free(ack);
+    }
+}
+
+
+mrp_msg_t *msg_encode_ack(ack_msg_t *ack)
+{
+    return mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_ACK),
+                          MSG_UINT32(MSGSEQ , ack->seq),
+                          MSG_END);
+}
+
+
+msg_t *msg_decode_ack(mrp_msg_t *msg)
+{
+    ack_msg_t *ack;
+    void      *it;
+    uint32_t   seqno;
+
+    ack = mrp_allocz(sizeof(*ack));
+
+    if (ack != NULL) {
+        it = NULL;
+
+        if (mrp_msg_iterate_get(msg, &it,
+                                MSG_UINT32(MSGSEQ, &seqno),
+                                MSG_END)) {
+            ack->type = MSG_TYPE_ACK;
+            ack->seq  = seqno;
+
+            return (msg_t *)ack;
+        }
+
+        msg_free_ack((msg_t *)ack);
+    }
+
+    return NULL;
+}
+
+
+void msg_free_nak(msg_t *msg)
+{
+    nak_msg_t *nak = (nak_msg_t *)msg;
+
+    if (nak != NULL) {
+        unref_wire(msg);
+        mrp_free(nak);
+    }
+}
+
+
+mrp_msg_t *msg_encode_nak(nak_msg_t *nak)
+{
+    return mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_NAK),
+                          MSG_UINT32(MSGSEQ , nak->seq),
+                          MSG_SINT32(ERRCODE, nak->error),
+                          MSG_STRING(ERRMSG, nak->msg),
+                          MSG_END);
+}
+
+
+msg_t *msg_decode_nak(mrp_msg_t *msg)
+{
+    nak_msg_t  *nak;
+    void       *it;
+    uint32_t    seqno;
+    int32_t     error;
+    const char *errmsg;
+
+    nak = mrp_allocz(sizeof(*nak));
+
+    if (nak != NULL) {
+        it = NULL;
+
+        if (mrp_msg_iterate_get(msg, &it,
+                                MSG_UINT32(MSGSEQ , &seqno),
+                                MSG_SINT32(ERRCODE, &error),
+                                MSG_STRING(ERRMSG , &errmsg),
+                                MSG_END)) {
+            nak->type  = MSG_TYPE_NAK;
+            nak->seq   = seqno;
+            nak->error = error;
+            nak->msg   = errmsg;
+
+            nak->wire       = mrp_msg_ref(msg);
+            nak->unref_wire = msg_unref_wire;
+
+            return (msg_t *)nak;
+        }
+
+        msg_free_nak((msg_t *)nak);
+    }
+
+    return NULL;
+}
+
+
+void msg_free_set(msg_t *msg)
+{
+    set_msg_t *set = (set_msg_t *)msg;
+    int        values_freed, i;
+
+    if (set != NULL) {
+        values_freed = FALSE;
+        for (i = 0; i < set->ntable; i++) {
+            if (set->tables != NULL && set->tables[i].rows != NULL) {
+                if (!values_freed && set->tables[i].rows[0] != NULL) {
+                    mrp_free(set->tables[i].rows[0]);
+                    values_freed = TRUE;
+                }
+                mrp_free(set->tables[i].rows);
+            }
+        }
+
+        mrp_free(set->tables);
+        mrp_free(set);
+    }
+}
+
+
+mrp_msg_t *msg_encode_set(set_msg_t *set)
+{
+    mrp_msg_t          *msg;
+    mrp_domctl_value_t *rows, *col;
+    uint16_t            utable, utotal, tid, ncol, nrow;
+    int                 i, r, c;
+
+    utable = set->ntable;
+    utotal = 0;
+
+    msg = mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_SET),
+                         MSG_UINT32(MSGSEQ , set->seq),
+                         MSG_UINT16(NCHANGE, utable),
+                         MSG_UINT16(NTOTAL , 0),
+                         MSG_END);
+
+    if (msg == NULL)
+        return NULL;
+
+    for (i = 0; i < set->ntable; i++) {
+        tid  = set->tables[i].id;
+        ncol = set->tables[i].ncolumn;
+        nrow = set->tables[i].nrow;
+
+        if (!mrp_msg_append(msg, MSG_UINT16(TBLID, tid))  ||
+            !mrp_msg_append(msg, MSG_UINT16(NROW , nrow)) ||
+            !mrp_msg_append(msg, MSG_UINT16(NCOL , ncol)))
+            goto fail;
+
+        for (r = 0; r < nrow; r++) {
+            rows = set->tables[i].rows[r];
+
+            for (c = 0; c < ncol; c++) {
+                col = rows + c;
+
+#define HANDLE_TYPE(pt, t, m)                                           \
+                case MRP_DOMCTL_##pt:                                   \
+                    if (!mrp_msg_append(msg, MSG_##t(DATA,col->m)))     \
+                        goto fail;                                      \
+                    break
+
+                switch (col->type) {
+                    HANDLE_TYPE(STRING  , STRING, str);
+                    HANDLE_TYPE(INTEGER , SINT32, s32);
+                    HANDLE_TYPE(UNSIGNED, UINT32, u32);
+                    HANDLE_TYPE(DOUBLE  , DOUBLE, dbl);
+                default:
+                    goto fail;
+                }
+#undef HANDLE_TYPE
+            }
+        }
+
+        utotal += nrow * ncol;
+    }
+
+    mrp_msg_set(msg, MSG_UINT16(NTOTAL, utotal));
+
+    return msg;
+
+ fail:
+    mrp_msg_unref(msg);
+    return NULL;
+}
+
+
+msg_t *msg_decode_set(mrp_msg_t *msg)
+{
+    set_msg_t          *set;
+    void               *it;
+    mrp_domctl_data_t  *d;
+    mrp_domctl_value_t *values, *v;
+    uint64_t            columns_so_far;
+    uint32_t            seqno;
+    uint16_t            ntable, ntotal, nrow, ncol, tblid, type;
+    int                 t, r, c;
+    mrp_msg_value_t     value;
+
+    it = NULL;
+    columns_so_far = 0;
+
+    if (!mrp_msg_iterate_get(msg, &it,
+                             MSG_UINT32(MSGSEQ , &seqno),
+                             MSG_UINT16(NCHANGE, &ntable),
+                             MSG_UINT16(NTOTAL , &ntotal),
+                             MSG_END))
+        return NULL;
+
+    set = mrp_allocz(sizeof(*set));
+
+    if (set == NULL)
+        return NULL;
+
+    values      = NULL;
+    set->type   = MSG_TYPE_SET;
+    set->seq    = seqno;
+    set->tables = mrp_allocz(sizeof(*set->tables) * ntable);
+
+    if (set->tables == NULL && ntable != 0)
+        goto fail;
+
+    values = mrp_allocz(sizeof(*values) * ntotal);
+
+    if (values == NULL && ntotal != 0)
+        goto fail;
+
+    d = set->tables;
+    v = values;
+
+    for (t = 0; t < ntable; t++) {
+        if (!mrp_msg_iterate_get(msg, &it,
+                                 MSG_UINT16(TBLID, &tblid),
+                                 MSG_UINT16(NROW , &nrow ),
+                                 MSG_UINT16(NCOL , &ncol ),
+                                 MSG_END))
+            goto fail;
+
+        d->id      = tblid;
+        d->ncolumn = ncol;
+        d->nrow    = nrow;
+        d->rows    = mrp_allocz(sizeof(*d->rows) * nrow);
+
+        if (d->rows == NULL && nrow)
+            goto fail;
+
+        /* Check if we go over the possible total */
+        if (columns_so_far + (nrow * ncol) > ntotal)
+            goto fail;
+
+        /* If we are not overflowing, add ncol to count */
+        columns_so_far += nrow * ncol;
+
+        for (r = 0; r < nrow; r++) {
+            d->rows[r] = v;
+
+            for (c = 0; c < ncol; c++) {
+                if (!mrp_msg_iterate_get(msg, &it,
+                                         MSG_ANY(DATA, &type, &value),
+                                         MSG_END))
+                    goto fail;
+
+                switch (type) {
+                case MRP_MSG_FIELD_STRING:
+                    v->type = MRP_DOMCTL_STRING;
+                    v->str  = value.str;
+                    break;
+                case MRP_MSG_FIELD_SINT32:
+                    v->type = MRP_DOMCTL_INTEGER;
+                    v->s32  = value.s32;
+                    break;
+                case MRP_MSG_FIELD_UINT32:
+                    v->type = MRP_DOMCTL_UNSIGNED;
+                    v->u32  = value.u32;
+                    break;
+                case MRP_MSG_FIELD_DOUBLE:
+                    v->type = MRP_DOMCTL_DOUBLE;
+                    v->dbl  = value.dbl;
+                    break;
+                default:
+                    goto fail;
+                }
+
+                v++;
+            }
+        }
+
+        d++;
+    }
+
+    set->ntable = ntable;
+
+    set->wire       = mrp_msg_ref(msg);
+    set->unref_wire = msg_unref_wire;
+
+    return (msg_t *)set;
+
+ fail:
+    msg_free_set((msg_t *)set);
+    mrp_free(values);
+
+    return NULL;
+}
+
+
+void msg_free_notify(msg_t *msg)
+{
+    notify_msg_t *notify = (notify_msg_t *)msg;
+    int           values_freed, i;
+
+    if (notify != NULL) {
+        values_freed = FALSE;
+        for (i = 0; i < notify->ntable; i++) {
+            if (notify->tables[i].rows != NULL) {
+                if (!values_freed && notify->tables[i].rows[0] != NULL) {
+                    mrp_free(notify->tables[i].rows[0]);
+                    values_freed = TRUE;
+                }
+                mrp_free(notify->tables[i].rows);
+            }
+        }
+
+        mrp_free(notify->tables);
+        unref_wire((msg_t *)notify);
+        mrp_free(notify);
+    }
+}
+
+
+mrp_msg_t *msg_encode_notify(notify_msg_t *msg)
+{
+    MRP_UNUSED(msg);
+
+    return NULL;
+}
+
+
+mrp_msg_t *msg_create_notify(void)
+{
+    return mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_NOTIFY),
+                          MSG_UINT32(MSGSEQ , 0),
+                          MSG_UINT16(NCHANGE, 0),
+                          MSG_UINT16(NTOTAL , 0),
+                          MSG_END);
+}
+
+
+int msg_update_notify(mrp_msg_t *msg, int tblid, mql_result_t *r)
+{
+    uint16_t    tid, nrow, ncol;
+    int         types[MQI_COLUMN_MAX];
+    const char *str;
+    uint32_t    u32;
+    int32_t     s32;
+    double      dbl;
+    int         i, j;
+
+    if (r != NULL) {
+        nrow = mql_result_rows_get_row_count(r);
+        ncol = mql_result_rows_get_row_column_count(r);
+    }
+    else
+        nrow = ncol = 0;
+
+    tid = tblid;
+    if (!mrp_msg_append(msg, MSG_UINT16(TBLID, tid))  ||
+        !mrp_msg_append(msg, MSG_UINT16(NROW , nrow)) ||
+        !mrp_msg_append(msg, MSG_UINT16(NCOL , ncol)))
+        goto fail;
+
+    for (i = 0; i < ncol; i++)
+        types[i] = mql_result_rows_get_row_column_type(r, i);
+
+    for (i = 0; i < nrow; i++) {
+        for (j = 0; j < ncol; j++) {
+            switch (types[j]) {
+            case mqi_string:
+                str = mql_result_rows_get_string(r, j, i, NULL, 0);
+                if (!mrp_msg_append(msg, MSG_STRING(DATA, str)))
+                    goto fail;
+                break;
+            case mqi_integer:
+                s32 = mql_result_rows_get_integer(r, j, i);
+                if (!mrp_msg_append(msg, MSG_SINT32(DATA, s32)))
+                    goto fail;
+                break;
+            case mqi_unsignd:
+                u32 = mql_result_rows_get_unsigned(r, j, i);
+                if (!mrp_msg_append(msg, MSG_UINT32(DATA, u32)))
+                    goto fail;
+                break;
+
+            case mqi_floating:
+                dbl = mql_result_rows_get_floating(r, j, i);
+                if (!mrp_msg_append(msg, MSG_DOUBLE(DATA, dbl)))
+                    goto fail;
+                break;
+
+            default:
+                goto fail;
+            }
+        }
+
+        mrp_debug_code({
+                char buf[4096], *p;
+                int  n, l;
+
+                p = buf;
+                l = sizeof(buf) - 1;
+
+                n  = snprintf(p, l, "{");
+                p += n;
+                l -= n;
+
+                for (j = 0; j < ncol; j++) {
+                    switch (types[j]) {
+                    case mqi_string:
+                        str = mql_result_rows_get_string(r, j, i, NULL, 0);
+                        n   = snprintf(p, l, "%s'%s'", j ? ", " : " ", str);
+                        break;
+                    case mqi_integer:
+                        s32 = mql_result_rows_get_integer(r, j, i);
+                        n   = snprintf(p, l, "%s%d", j ? ", " : " ", s32);
+                        break;
+                    case mqi_unsignd:
+                        u32 = mql_result_rows_get_unsigned(r, j, i);
+                        n   = snprintf(p, l, "%s%u", j ? ", " : " ", u32);
+                        break;
+                    case mqi_floating:
+                        dbl = mql_result_rows_get_floating(r, j, i);
+                        n   = snprintf(p, l, "%s%f", j ? ", " : " ", dbl);
+                        break;
+                    default:
+                        continue;
+                    }
+
+                    p += n;
+                    l -= n;
+
+                    if (l <= 0)
+                        break;
+                }
+
+                if (l > 2) {
+                    *p++ = ' ', *p++ = '}';
+                    *p = '\0';
+
+                    mrp_debug("%s", buf);
+                }
+            });
+    }
+
+    return nrow * ncol;
+
+ fail:
+    return -1;
+}
+
+
+msg_t *msg_decode_notify(mrp_msg_t *msg)
+{
+    notify_msg_t       *notify;
+    mrp_domctl_data_t  *d;
+    mrp_domctl_value_t *values, *v;
+    void               *it;
+    uint64_t            columns_so_far;
+    uint32_t            seqno;
+    uint16_t            ntable, ntotal, nrow, ncol;
+    uint16_t            tblid;
+    int                 t, r, c;
+    uint16_t            type;
+    mrp_msg_value_t     value;
+
+    it = NULL;
+    columns_so_far = 0;
+
+    if (!mrp_msg_iterate_get(msg, &it,
+                             MSG_UINT32(MSGSEQ, &seqno),
+                             MSG_UINT16(NCHANGE, &ntable),
+                             MSG_UINT16(NTOTAL , &ntotal),
+                             MSG_END))
+        return NULL;
+
+    notify = mrp_allocz(sizeof(*notify));
+
+    if (notify == NULL)
+        return NULL;
+
+    values         = NULL;
+    notify->type   = MSG_TYPE_NOTIFY;
+    notify->seq    = seqno;
+    notify->tables = mrp_allocz(sizeof(*notify->tables) * ntable);
+
+    if (notify->tables == NULL && ntable != 0)
+        goto fail;
+
+    values = ntotal ? mrp_allocz(sizeof(*values) * ntotal) : NULL;
+
+    if (values == NULL && ntotal != 0)
+        goto fail;
+
+    d = notify->tables;
+    v = values;
+
+    for (t = 0; t < ntable; t++) {
+        if (!mrp_msg_iterate_get(msg, &it,
+                                 MSG_UINT16(TBLID, &tblid),
+                                 MSG_UINT16(NROW , &nrow ),
+                                 MSG_UINT16(NCOL , &ncol ),
+                                 MSG_END))
+            goto fail;
+
+        d->id      = tblid;
+        d->ncolumn = ncol;
+        d->nrow    = nrow;
+        d->rows    = nrow ? mrp_allocz(sizeof(*d->rows) * nrow) : NULL;
+
+        if (d->rows == NULL && nrow != 0)
+            goto fail;
+
+        /* Check if we go over the possible total */
+        if (columns_so_far + (nrow * ncol) > ntotal)
+            goto fail;
+
+        /* If we are not overflowing, add ncol to count */
+        columns_so_far += nrow * ncol;
+
+        for (r = 0; r < nrow; r++) {
+            d->rows[r] = v;
+
+            for (c = 0; c < ncol; c++) {
+                if (!mrp_msg_iterate_get(msg, &it,
+                                         MSG_ANY(DATA, &type, &value),
+                                         MSG_END))
+                    goto fail;
+
+                switch (type) {
+                case MRP_MSG_FIELD_STRING:
+                    v->type = MRP_DOMCTL_STRING;
+                    v->str  = value.str;
+                    break;
+                case MRP_MSG_FIELD_SINT32:
+                    v->type = MRP_DOMCTL_INTEGER;
+                    v->s32  = value.s32;
+                    break;
+                case MRP_MSG_FIELD_UINT32:
+                    v->type = MRP_DOMCTL_UNSIGNED;
+                    v->u32  = value.u32;
+                    break;
+                case MRP_MSG_FIELD_DOUBLE:
+                    v->type = MRP_DOMCTL_DOUBLE;
+                    v->dbl  = value.dbl;
+                    break;
+                default:
+                    goto fail;
+                }
+
+                v++;
+            }
+        }
+
+        d++;
+    }
+
+    notify->ntable = ntable;
+
+    notify->wire       = mrp_msg_ref(msg);
+    notify->unref_wire = msg_unref_wire;
+
+    return (msg_t *)notify;
+
+ fail:
+    msg_free_notify((msg_t *)notify);
+    mrp_free(values);
+
+    return NULL;
+}
+
+
+void msg_free_invoke(msg_t *msg)
+{
+    if (msg != NULL) {
+        mrp_free(msg->invoke.args);
+        mrp_free(msg);
+    }
+}
+
+
+mrp_msg_t *msg_encode_invoke(invoke_msg_t *invoke)
+{
+    mrp_msg_t        *msg;
+    mrp_domctl_arg_t *arg;
+    uint32_t          i;
+
+    msg = mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_INVOKE),
+                         MSG_UINT32(MSGSEQ , invoke->seq),
+                         MSG_STRING(METHOD , invoke->name),
+                         MSG_BOOL  (NORET  , invoke->noret),
+                         MSG_UINT32(NARG   , invoke->narg),
+                         MSG_END);
+
+    for (i = 0, arg = invoke->args; i < invoke->narg; i++, arg++) {
+        switch (arg->type) {
+        case MRP_DOMCTL_STRING:
+            if (!mrp_msg_append(msg, MSG_STRING(ARG, arg->str)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_DOUBLE:
+            if (!mrp_msg_append(msg, MSG_DOUBLE(ARG, arg->dbl)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_BOOL:
+            if (!mrp_msg_append(msg, MSG_BOOL(ARG, arg->bln)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT8:
+            if (!mrp_msg_append(msg, MSG_UINT8(ARG, arg->s8)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT8:
+            if (!mrp_msg_append(msg, MSG_SINT8(ARG, arg->u8)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT16:
+            if (!mrp_msg_append(msg, MSG_UINT16(ARG, arg->s16)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT16:
+            if (!mrp_msg_append(msg, MSG_SINT16(ARG, arg->u16)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT32:
+            if (!mrp_msg_append(msg, MSG_UINT32(ARG, arg->s32)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT32:
+            if (!mrp_msg_append(msg, MSG_SINT32(ARG, arg->u32)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT64:
+            if (!mrp_msg_append(msg, MSG_UINT64(ARG, arg->s64)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT64:
+            if (!mrp_msg_append(msg, MSG_SINT64(ARG, arg->u64)))
+                goto fail;
+            break;
+        default:
+            if (MRP_DOMCTL_IS_ARRAY(arg->type)) {
+                if (!mrp_msg_append(msg, MSG_ARRAY(ARG, arg->type,
+                                                   arg->size, arg->arr)))
+                    goto fail;
+            }
+            else
+                goto fail;
+            break;
+        }
+    }
+
+    return msg;
+
+ fail:
+    mrp_msg_unref(msg);
+    return NULL;
+}
+
+
+msg_t *msg_decode_invoke(mrp_msg_t *msg)
+{
+    invoke_msg_t     *invoke;
+    void             *it;
+    uint16_t          tag, type;
+    mrp_msg_value_t   val;
+    mrp_domctl_arg_t *arg;
+    uint32_t          i;
+    size_t            size;
+
+    mrp_debug_code({
+            mrp_debug("got domain invoke request:");
+            mrp_msg_dump(msg, stdout); });
+
+    it     = NULL;
+    invoke = mrp_allocz(sizeof(*invoke));
+
+    if (invoke == NULL)
+        goto fail;
+
+    invoke->type = MSG_TYPE_INVOKE;
+
+    if (!mrp_msg_iterate_get(msg, &it,
+                             MSG_UINT32(MSGSEQ, &invoke->seq),
+                             MSG_STRING(METHOD, &invoke->name),
+                             MSG_BOOL  (NORET , &invoke->noret),
+                             MSG_UINT32(NARG  , &invoke->narg),
+                             MSG_END))
+        goto fail;
+
+
+
+    if (invoke->narg > 0)
+        invoke->args = mrp_allocz(invoke->narg * sizeof(invoke->args[0]));
+
+    if (invoke->args == NULL && invoke->narg > 0)
+        goto fail;
+
+    for (i = 0, arg = invoke->args; i < invoke->narg; i++, arg++) {
+        if (!mrp_msg_iterate(msg, &it, &tag, &type, &val, &size))
+            goto fail;
+
+        arg->type = type;
+
+        switch (type) {
+        case MRP_DOMCTL_STRING:  arg->str = val.str; break;
+        case MRP_DOMCTL_BOOL:    arg->bln = val.bln; break;
+        case MRP_DOMCTL_UINT8:   arg->u8  = val.u8;  break;
+        case MRP_DOMCTL_INT8:    arg->s8  = val.s8;  break;
+        case MRP_DOMCTL_UINT16:  arg->u16 = val.u16; break;
+        case MRP_DOMCTL_INT16:   arg->s16 = val.s16; break;
+        case MRP_DOMCTL_UINT32:  arg->u32 = val.u32; break;
+        case MRP_DOMCTL_INT32:   arg->s32 = val.s32; break;
+        case MRP_DOMCTL_UINT64:  arg->u64 = val.u64; break;
+        case MRP_DOMCTL_INT64:   arg->s64 = val.s64; break;
+        case MRP_DOMCTL_DOUBLE:  arg->dbl = val.dbl; break;
+        default:
+            if (MRP_DOMCTL_IS_ARRAY(type)) {
+                arg->arr  = val.aany;
+                arg->size = size;
+            }
+            else
+                goto fail;
+        }
+    }
+
+    invoke->wire       = mrp_msg_ref(msg);
+    invoke->unref_wire = msg_unref_wire;
+
+    return (msg_t *)invoke;
+
+ fail:
+    msg_free_invoke((msg_t *)invoke);
+    return NULL;
+}
+
+
+void msg_free_return(msg_t *msg)
+{
+    if (msg != NULL) {
+        mrp_free(msg->ret.args);
+        mrp_free(msg);
+    }
+}
+
+
+mrp_msg_t *msg_encode_return(return_msg_t *ret)
+{
+    mrp_msg_t        *msg;
+    mrp_domctl_arg_t *arg;
+    uint32_t          i;
+
+    msg = mrp_msg_create(MSG_UINT16(MSGTYPE, MSG_TYPE_RETURN),
+                         MSG_UINT32(MSGSEQ , ret->seq),
+                         MSG_UINT32(ERROR  , ret->error),
+                         MSG_SINT32(RETVAL , ret->retval),
+                         MSG_UINT32(NARG   , ret->narg),
+                         MSG_END);
+
+    for (i = 0, arg = ret->args; i < ret->narg; i++, arg++) {
+        switch (arg->type) {
+        case MRP_DOMCTL_STRING:
+            if (!mrp_msg_append(msg, MSG_STRING(ARG, arg->str)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_DOUBLE:
+            if (!mrp_msg_append(msg, MSG_DOUBLE(ARG, arg->dbl)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_BOOL:
+            if (!mrp_msg_append(msg, MSG_BOOL(ARG, arg->bln)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT8:
+            if (!mrp_msg_append(msg, MSG_UINT8(ARG, arg->s8)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT8:
+            if (!mrp_msg_append(msg, MSG_SINT8(ARG, arg->u8)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT16:
+            if (!mrp_msg_append(msg, MSG_UINT16(ARG, arg->s16)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT16:
+            if (!mrp_msg_append(msg, MSG_SINT16(ARG, arg->u16)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT32:
+            if (!mrp_msg_append(msg, MSG_UINT32(ARG, arg->s32)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT32:
+            if (!mrp_msg_append(msg, MSG_SINT32(ARG, arg->u32)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_UINT64:
+            if (!mrp_msg_append(msg, MSG_UINT64(ARG, arg->s64)))
+                goto fail;
+            break;
+        case MRP_DOMCTL_INT64:
+            if (!mrp_msg_append(msg, MSG_SINT64(ARG, arg->u64)))
+                goto fail;
+            break;
+        default:
+            if (MRP_DOMCTL_IS_ARRAY(arg->type)) {
+                if (!mrp_msg_append(msg, MSG_ARRAY(ARG, arg->type,
+                                                   arg->size, arg->arr)))
+                    goto fail;
+            }
+            else
+                goto fail;
+            break;
+        }
+    }
+
+    return msg;
+
+ fail:
+    mrp_msg_unref(msg);
+    return NULL;
+}
+
+
+msg_t *msg_decode_return(mrp_msg_t *msg)
+{
+    return_msg_t     *ret;
+    void             *it;
+    uint16_t          tag, type;
+    mrp_msg_value_t   val;
+    mrp_domctl_arg_t *arg;
+    uint32_t          i;
+    size_t            size;
+
+    mrp_debug_code({
+            mrp_debug("got domain return (invoke reply):");
+            mrp_msg_dump(msg, stdout); });
+
+    it  = NULL;
+    ret = mrp_allocz(sizeof(*ret));
+
+    if (ret == NULL)
+        goto fail;
+
+    ret->type = MSG_TYPE_RETURN;
+
+    if (!mrp_msg_iterate_get(msg, &it,
+                             MSG_UINT32(MSGSEQ, &ret->seq),
+                             MSG_UINT32(ERROR , &ret->error),
+                             MSG_SINT32(RETVAL, &ret->retval),
+                             MSG_UINT32(NARG  , &ret->narg),
+                             MSG_END))
+        goto fail;
+
+    if (ret->narg > 0)
+        ret->args = mrp_allocz(ret->narg * sizeof(ret->args[0]));
+
+    if (ret->args == NULL && ret->narg > 0)
+        goto fail;
+
+    for (i = 0, arg = ret->args; i < ret->narg; i++, arg++) {
+        if (!mrp_msg_iterate(msg, &it, &tag, &type, &val, &size))
+            goto fail;
+
+        arg->type = type;
+
+        switch (type) {
+        case MRP_DOMCTL_STRING:  arg->str = val.str; break;
+        case MRP_DOMCTL_BOOL:    arg->bln = val.bln; break;
+        case MRP_DOMCTL_UINT8:   arg->u8  = val.u8;  break;
+        case MRP_DOMCTL_INT8:    arg->s8  = val.s8;  break;
+        case MRP_DOMCTL_UINT16:  arg->u16 = val.u16; break;
+        case MRP_DOMCTL_INT16:   arg->s16 = val.s16; break;
+        case MRP_DOMCTL_UINT32:  arg->u32 = val.u32; break;
+        case MRP_DOMCTL_INT32:   arg->s32 = val.s32; break;
+        case MRP_DOMCTL_UINT64:  arg->u64 = val.u64; break;
+        case MRP_DOMCTL_INT64:   arg->s64 = val.s64; break;
+        case MRP_DOMCTL_DOUBLE:  arg->dbl = val.dbl; break;
+        default:
+            if (MRP_DOMCTL_IS_ARRAY(type)) {
+                arg->arr  = val.aany;
+                arg->size = size;
+            }
+            else
+                goto fail;
+        }
+    }
+
+    ret->wire       = mrp_msg_ref(msg);
+    ret->unref_wire = msg_unref_wire;
+
+    return (msg_t *)ret;
+
+ fail:
+    msg_free_return((msg_t *)ret);
+    return NULL;
+}
+
+
+msg_t *msg_decode_message(mrp_msg_t *msg)
+{
+    uint16_t type;
+
+    if (mrp_msg_get(msg, MSG_UINT16(MSGTYPE, &type), MSG_END)) {
+        switch (type) {
+        case MSG_TYPE_REGISTER:   return msg_decode_register(msg);
+        case MSG_TYPE_UNREGISTER: return msg_decode_unregister(msg);
+        case MSG_TYPE_SET:        return msg_decode_set(msg);
+        case MSG_TYPE_NOTIFY:     return msg_decode_notify(msg);
+        case MSG_TYPE_ACK:        return msg_decode_ack(msg);
+        case MSG_TYPE_NAK:        return msg_decode_nak(msg);
+        case MSG_TYPE_INVOKE:     return msg_decode_invoke(msg);
+        case MSG_TYPE_RETURN:     return msg_decode_return(msg);
+        default:                  break;
+        }
+    }
+
+    return NULL;
+}
+
+
+mrp_msg_t *msg_encode_message(msg_t *msg)
+{
+    switch (msg->any.type) {
+    case MSG_TYPE_REGISTER:   return msg_encode_register(&msg->reg);
+    case MSG_TYPE_UNREGISTER: return msg_encode_unregister(&msg->unreg);
+    case MSG_TYPE_SET:        return msg_encode_set(&msg->set);
+    case MSG_TYPE_NOTIFY:     return msg_encode_notify(&msg->notify);
+    case MSG_TYPE_ACK:        return msg_encode_ack(&msg->ack);
+    case MSG_TYPE_NAK:        return msg_encode_nak(&msg->nak);
+    case MSG_TYPE_INVOKE:     return msg_encode_invoke(&msg->invoke);
+    case MSG_TYPE_RETURN:     return msg_encode_return(&msg->ret);
+    default:                  return NULL;
+    }
+}
+
+
+void msg_free_message(msg_t *msg)
+{
+    if (msg != NULL) {
+        switch (msg->any.type) {
+        case MSG_TYPE_REGISTER:   msg_free_register(msg);   break;
+        case MSG_TYPE_UNREGISTER: msg_free_unregister(msg); break;
+        case MSG_TYPE_SET:        msg_free_set(msg);        break;
+        case MSG_TYPE_NOTIFY:     msg_free_notify(msg);     break;
+        case MSG_TYPE_ACK:        msg_free_ack(msg);        break;
+        case MSG_TYPE_NAK:        msg_free_nak(msg);        break;
+        case MSG_TYPE_INVOKE:     msg_free_invoke(msg);     break;
+        case MSG_TYPE_RETURN:     msg_free_return(msg);     break;
+        default:                                            break;
+        }
+    }
+}
+
+
+static void json_unref_wire(void *wire)
+{
+    mrp_json_unref((mrp_json_t *)wire);
+}
+
+
+mrp_json_t *json_encode_register(register_msg_t *reg)
+{
+    MRP_UNUSED(reg);
+
+    return NULL;
+}
+
+
+msg_t *json_decode_register(mrp_json_t *msg)
+{
+
+    register_msg_t     *reg;
+    mrp_domctl_table_t *t;
+    mrp_domctl_watch_t *w;
+    int                 seqno;
+    char               *name, *table, *columns, *index, *where;
+    int                 ntable, nwatch, max_rows;
+    mrp_json_t         *arr, *tbl, *wch;
+    int                 i;
+
+    if (!mrp_json_get_integer(msg, "seq"   , &seqno)  ||
+        !mrp_json_get_string (msg, "name"  , &name)   ||
+        !mrp_json_get_integer(msg, "ntable", &ntable) ||
+        !mrp_json_get_integer(msg, "nwatch", &nwatch))
+        return NULL;
+
+    reg = mrp_allocz(sizeof(*reg));
+
+    if (reg == NULL)
+        return NULL;
+
+    reg->type    = MSG_TYPE_REGISTER;
+    reg->seq     = seqno;
+    reg->name    = name;
+    reg->tables  = mrp_allocz(sizeof(*reg->tables)  * ntable);
+    reg->watches = mrp_allocz(sizeof(*reg->watches) * nwatch);
+
+    if ((reg->tables == NULL && ntable) || (reg->watches == NULL && nwatch))
+        goto fail;
+
+    if (!mrp_json_get_array(msg, "tables", &arr))
+        goto fail;
+
+    if (mrp_json_array_length(arr) != ntable)
+        goto fail;
+
+    for (i = 0, t = reg->tables; i < ntable; i++, t++) {
+        if (!mrp_json_array_get_object(arr, i, &tbl))
+            goto fail;
+
+        if (mrp_json_get_string(tbl, "table"  , &table)   &&
+            mrp_json_get_string(tbl, "columns", &columns) &&
+            mrp_json_get_string(tbl, "index"  , &index)) {
+            t->table       = table;
+            t->mql_columns = columns;
+            t->mql_index   = index;
+        }
+        else
+            goto fail;
+    }
+
+    reg->ntable = ntable;
+
+    if (!mrp_json_get_array(msg, "watches", &arr))
+        goto fail;
+
+    if (mrp_json_array_length(arr) != nwatch)
+        goto fail;
+
+    for (i = 0, w = reg->watches; i < nwatch; i++, w++) {
+        if (!mrp_json_array_get_object(arr, i, &wch))
+            goto fail;
+
+        if (mrp_json_get_string (wch, "table"  , &table)   &&
+            mrp_json_get_string (wch, "columns", &columns) &&
+            mrp_json_get_string (wch, "where"  , &where)   &&
+            mrp_json_get_integer(wch, "maxrows", &max_rows)) {
+            w->table       = table;
+            w->mql_columns = columns;
+            w->mql_where   = where;
+            w->max_rows    = max_rows;
+        }
+        else
+            goto fail;
+    }
+
+    reg->nwatch = nwatch;
+
+    reg->wire       = mrp_json_ref(msg);
+    reg->unref_wire = json_unref_wire;
+
+    return (msg_t *)reg;
+
+ fail:
+    msg_free_register((msg_t *)reg);
+
+    return NULL;
+}
+
+
+msg_t *json_decode_unregister(mrp_json_t *msg)
+{
+    unregister_msg_t *ureg;
+    int               seqno;
+
+    ureg = mrp_allocz(sizeof(*ureg));
+
+    if (ureg != NULL) {
+        if (mrp_json_get_integer(msg, "seq", &seqno)) {
+            ureg->type = MSG_TYPE_UNREGISTER;
+            ureg->seq  = seqno;
+
+            return (msg_t *)ureg;
+        }
+
+        msg_free_unregister((msg_t *)ureg);
+    }
+
+    return NULL;
+}
+
+
+mrp_json_t *json_encode_ack(ack_msg_t *ack)
+{
+    mrp_json_t *msg;
+    int         seqno;
+
+    msg = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (msg != NULL) {
+        seqno = ack->seq;
+
+        if (mrp_json_add_string (msg, "type", "ack") &&
+            mrp_json_add_integer(msg, "seq" , seqno))
+            return msg;
+        else
+            mrp_json_unref(msg);
+    }
+
+    return NULL;
+}
+
+
+msg_t *json_decode_ack(mrp_json_t *msg)
+{
+    ack_msg_t *ack;
+    int        seqno;
+
+    ack = mrp_allocz(sizeof(*ack));
+
+    if (ack != NULL) {
+        if (mrp_json_get_integer(msg, "seq", &seqno)) {
+            ack->type = MSG_TYPE_ACK;
+            ack->seq  = seqno;
+
+            return (msg_t *)ack;
+        }
+
+        msg_free_ack((msg_t *)ack);
+    }
+
+    return NULL;
+}
+
+
+mrp_json_t *json_encode_nak(nak_msg_t *nak)
+{
+    mrp_json_t *msg;
+    int         seqno, error;
+    const char *errmsg;
+
+    msg = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (msg != NULL) {
+        seqno  = nak->seq;
+        error  = nak->error;
+        errmsg = nak->msg;
+
+        if (mrp_json_add_string (msg, "type"  , "nak") &&
+            mrp_json_add_integer(msg, "seq"   , seqno) &&
+            mrp_json_add_integer(msg, "error" , error) &&
+            mrp_json_add_string (msg, "errmsg", errmsg))
+            return msg;
+        else
+            mrp_json_unref(msg);
+    }
+
+    return NULL;
+}
+
+
+msg_t *json_decode_nak(mrp_json_t *msg)
+{
+    nak_msg_t  *nak;
+    int         seqno, error;
+    const char *errmsg;
+
+    nak = mrp_allocz(sizeof(*nak));
+
+    if (nak != NULL) {
+        if (mrp_json_get_integer(msg, "seqno" , &seqno) &&
+            mrp_json_get_integer(msg, "error" , &error) &&
+            mrp_json_get_string (msg, "errmsg", &errmsg)) {
+            nak->type  = MSG_TYPE_NAK;
+            nak->seq   = seqno;
+            nak->error = error;
+            nak->msg   = errmsg;
+
+            nak->wire       = mrp_json_ref(msg);
+            nak->unref_wire = json_unref_wire;
+
+            return (msg_t *)nak;
+        }
+
+        msg_free_nak((msg_t *)nak);
+    }
+
+    return NULL;
+}
+
+
+msg_t *json_decode_set(mrp_json_t *msg)
+{
+    set_msg_t          *set;
+    mrp_domctl_data_t  *d;
+    mrp_domctl_value_t *values, *v;
+    mrp_json_t         *tables, *tbl, *rows, *row, *col;
+    int                 seqno, ntable, ntotal, nrow, ncol, tblid;
+    int                 t, r, c;
+
+    if (!mrp_json_get_integer(msg, "seq"    , &seqno)  ||
+        !mrp_json_get_integer(msg, "nchange", &ntable) ||
+        !mrp_json_get_integer(msg, "ntotal" , &ntotal))
+        return NULL;
+
+    set = mrp_allocz(sizeof(*set));
+
+    if (set == NULL)
+        return NULL;
+
+    values      = NULL;
+    set->type   = MSG_TYPE_SET;
+    set->seq    = seqno;
+    set->tables = mrp_allocz(sizeof(*set->tables) * ntable);
+
+    if (set->tables == NULL)
+        goto fail;
+
+    values = mrp_allocz(sizeof(*values) * ntotal);
+
+    if (values == NULL)
+        goto fail;
+
+    d = set->tables;
+    v = values;
+
+    if (!mrp_json_get_array(msg, "tables", &tables))
+        goto fail;
+
+    for (t = 0; t < ntable; t++) {
+        if (!mrp_json_array_get_object(tables, t, &tbl))
+            goto fail;
+
+        if (!mrp_json_get_integer(tbl, "id"  , &tblid) ||
+            !mrp_json_get_integer(tbl, "nrow", &nrow)  ||
+            !mrp_json_get_integer(tbl, "ncol", &ncol))
+            goto fail;
+
+        d->id      = tblid;
+        d->ncolumn = ncol;
+        d->nrow    = nrow;
+        d->rows    = mrp_allocz(sizeof(*d->rows) * nrow);
+
+        if (d->rows == NULL && nrow)
+            goto fail;
+
+        if (!mrp_json_get_array(tbl, "rows", &rows))
+            goto fail;
+
+        for (r = 0; r < nrow; r++) {
+            if (!mrp_json_array_get_array(rows, t, &row))
+                goto fail;
+
+            d->rows[r] = v;
+            values     = NULL;
+
+            for (c = 0; c < ncol; c++) {
+                col = mrp_json_array_get(row, c);
+
+                if (col == NULL)
+                    goto fail;
+
+                switch (mrp_json_get_type(col)) {
+                case MRP_JSON_STRING:
+                    v->type = MRP_DOMCTL_STRING;
+                    v->str  = mrp_json_string_value(col);
+                    break;
+
+                case MRP_JSON_INTEGER:
+                    v->type = MRP_DOMCTL_INTEGER;
+                    v->s32  = mrp_json_integer_value(col);
+                    break;
+
+                case MRP_JSON_BOOLEAN:
+                    v->type = MRP_DOMCTL_INTEGER;
+                    v->s32  = !!mrp_json_boolean_value(col);
+                    break;
+
+                case MRP_JSON_DOUBLE:
+                    v->type = MRP_DOMCTL_DOUBLE;
+                    v->dbl  = mrp_json_double_value(col);
+                    break;
+
+                default:
+                    goto fail;
+                }
+
+                v++;
+            }
+        }
+
+        d++;
+    }
+
+    set->ntable = ntable;
+
+    set->wire       = mrp_json_ref(msg);
+    set->unref_wire = json_unref_wire;
+
+    return (msg_t *)set;
+
+ fail:
+    msg_free_set((msg_t *)set);
+    mrp_free(values);
+
+    return NULL;
+}
+
+
+mrp_json_t *json_create_notify(void)
+{
+    mrp_json_t *msg;
+
+    msg = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (msg != NULL) {
+        if (mrp_json_add_string (msg, "type"   , "notify") &&
+            mrp_json_add_integer(msg, "seq"    , 0))
+            return msg;
+        else
+            mrp_json_unref(msg);
+    }
+
+    return NULL;
+}
+
+
+int json_update_notify(mrp_json_t *msg, int tblid, mql_result_t *r)
+{
+    int         nrow, ncol;
+    int         types[MQI_COLUMN_MAX];
+    const char *str;
+    uint32_t    u32;
+    int32_t     s32;
+    double      dbl;
+    mrp_json_t *tables, *tbl, *rows, *row;
+    int         i, j;
+
+    if (r != NULL) {
+        nrow = mql_result_rows_get_row_count(r);
+        ncol = mql_result_rows_get_row_column_count(r);
+    }
+    else
+        nrow = ncol = 0;
+
+    if (!mrp_json_get_array(msg, "tables", &tables)) {
+        tables = mrp_json_create(MRP_JSON_ARRAY);
+
+        if (tables == NULL)
+            goto fail;
+
+        mrp_json_add(msg, "tables", tables);
+    }
+
+    tbl = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (tbl == NULL || !mrp_json_array_append(tables, tbl)) {
+        mrp_json_unref(tbl);
+        goto fail;
+    }
+
+    if (!mrp_json_add_integer(tbl, "id"  , tblid) ||
+        !mrp_json_add_integer(tbl, "nrow", nrow)  ||
+        !mrp_json_add_integer(tbl, "ncol", ncol))
+        goto fail;
+
+    rows = mrp_json_create(MRP_JSON_ARRAY);
+
+    if (rows == NULL)
+        goto fail;
+
+    mrp_json_add(tbl, "rows", rows);
+
+    for (i = 0; i < ncol; i++)
+        types[i] = mql_result_rows_get_row_column_type(r, i);
+
+    for (i = 0; i < nrow; i++) {
+        row = mrp_json_create(MRP_JSON_ARRAY);
+
+        if (row == NULL || !mrp_json_array_append(rows, row)) {
+            mrp_json_unref(row);
+            goto fail;
+        }
+
+        for (j = 0; j < ncol; j++) {
+            switch (types[j]) {
+            case mqi_string:
+                str = mql_result_rows_get_string(r, j, i, NULL, 0);
+                if (!mrp_json_array_append_string(row, str))
+                    goto fail;
+                break;
+            case mqi_integer:
+                s32 = mql_result_rows_get_integer(r, j, i);
+                if (!mrp_json_array_append_integer(row, s32))
+                    goto fail;
+                break;
+            case mqi_unsignd:
+                u32 = mql_result_rows_get_unsigned(r, j, i);
+                /* XXX TODO: check for overflow */
+                if (!mrp_json_array_append_integer(row, u32))
+                    goto fail;
+                break;
+            case mqi_floating:
+                dbl = mql_result_rows_get_floating(r, j, i);
+                if (!mrp_json_array_append_double(row, dbl))
+                    goto fail;
+                break;
+            default:
+                goto fail;
+            }
+        }
+    }
+
+    return nrow * ncol;
+
+ fail:
+    return -1;
+}
+
+
+msg_t *json_decode_message(mrp_json_t *msg)
+{
+    const char *type;
+
+    if (mrp_json_get_string(msg, "type", &type)) {
+        if (!strcmp(type, "register"  )) return json_decode_register(msg);
+        if (!strcmp(type, "unregister")) return json_decode_unregister(msg);
+        if (!strcmp(type, "set"       )) return json_decode_set(msg);
+    }
+
+    return NULL;
+}
+
+
+mrp_json_t *json_encode_message(msg_t *msg)
+{
+    switch (msg->any.type) {
+    case MSG_TYPE_ACK: return json_encode_ack(&msg->ack);
+    case MSG_TYPE_NAK: return json_encode_nak(&msg->nak);
+    default:           return NULL;
+    }
+}
diff --git a/src/plugins/domain-control/message.h b/src/plugins/domain-control/message.h
new file mode 100644 (file)
index 0000000..4f48eba
--- /dev/null
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_MESSAGE_H__
+#define __MURPHY_DOMAIN_CONTROL_MESSAGE_H__
+
+#include <murphy/common/msg.h>
+#include <murphy/common/json.h>
+#include <murphy-db/mql.h>
+
+#include "domain-control-types.h"
+
+typedef enum {
+    MSG_TYPE_UNKNOWN = 0,
+    MSG_TYPE_REGISTER,
+    MSG_TYPE_UNREGISTER,
+    MSG_TYPE_SET,
+    MSG_TYPE_NOTIFY,
+    MSG_TYPE_ACK,
+    MSG_TYPE_NAK,
+    MSG_TYPE_INVOKE,
+    MSG_TYPE_RETURN,
+} msg_type_t;
+
+typedef enum {
+    /* fixed common tags */
+    MSGTAG_MSGTYPE = 0x1,            /* message type */
+    MSGTAG_MSGSEQ  = 0x2,            /* sequence number */
+
+    /* fixed tags in registration messages */
+    MSGTAG_NAME     = 0x3,           /* enforcement point name */
+    MSGTAG_NTABLE   = 0x4,           /* number of owned tables */
+    MSGTAG_NWATCH   = 0x5,           /* number of watched tables */
+    MSGTAG_TBLNAME  = 0x6,           /* table name */
+    MSGTAG_COLUMNS  = 0x8,           /* column definitions/list */
+    MSGTAG_INDEX    = 0x9,           /* index definition */
+    MSGTAG_WHERE    = 0xa,           /* where clause for select */
+    MSGTAG_MAXROWS  = 0xb,           /* max number of rows to select */
+
+    /* fixed tags in NAKs */
+    MSGTAG_ERRCODE  = 0x3,           /* error code */
+    MSGTAG_ERRMSG   = 0x4,           /* error message */
+
+    /* fixed tags in data notification messages */
+    MSGTAG_NCHANGE = 0x3,            /* number of tables in notification */
+    MSGTAG_NTOTAL  = 0x4,            /* total columns in notification */
+    MSGTAG_TBLID   = 0x5,            /* table id */
+    MSGTAG_NROW    = 0x6,            /* number of table rows */
+    MSGTAG_NCOL    = 0x7,            /* number of columns in a row */
+    MSGTAG_DATA    = 0x8,            /* a data column */
+
+    /* fixed tags in invoke and return messages */
+    MSGTAG_METHOD  = 0x3,            /* method name */
+    MSGTAG_NORET   = 0x4,            /* whether return values ignored */
+    MSGTAG_NARG    = 0x5,            /* number of arguments */
+    MSGTAG_ARG     = 0x6,            /* argument */
+    MSGTAG_ERROR   = 0x7,            /* invocation error */
+    MSGTAG_RETVAL  = 0x8,            /* invocation return value */
+} msgtag_t;
+
+
+#define MSG_UINT8(tag, val) MRP_MSG_TAG_UINT8(MSGTAG_##tag, val)
+#define MSG_SINT8(tag, val) MRP_MSG_TAG_SINT8(MSGTAG_##tag, val)
+#define MSG_UINT16(tag, val) MRP_MSG_TAG_UINT16(MSGTAG_##tag, val)
+#define MSG_SINT16(tag, val) MRP_MSG_TAG_SINT16(MSGTAG_##tag, val)
+#define MSG_UINT32(tag, val) MRP_MSG_TAG_UINT32(MSGTAG_##tag, val)
+#define MSG_SINT32(tag, val) MRP_MSG_TAG_SINT32(MSGTAG_##tag, val)
+#define MSG_UINT64(tag, val) MRP_MSG_TAG_UINT64(MSGTAG_##tag, val)
+#define MSG_SINT64(tag, val) MRP_MSG_TAG_SINT64(MSGTAG_##tag, val)
+#define MSG_DOUBLE(tag, val) MRP_MSG_TAG_DOUBLE(MSGTAG_##tag, val)
+#define MSG_STRING(tag, val) MRP_MSG_TAG_STRING(MSGTAG_##tag, val)
+#define MSG_BOOL(tag, val) MRP_MSG_TAG_BOOL(MSGTAG_##tag, val)
+#define MSG_ANY(tag, typep, valp) MRP_MSG_TAG_ANY(MSGTAG_##tag, typep, valp)
+#define MSG_ARRAY(tag, type, size, arr) \
+    MRP_MSG_TAGGED(MSGTAG_##tag, type, size, arr)
+
+#define MSG_END MRP_MSG_END
+
+#define COMMON_MSG_FIELDS                /* common message fields */      \
+    msg_type_t  type;                    /* message type */               \
+    uint32_t    seq;                     /* message sequence number */    \
+    void       *wire;                    /* associated on-wire message */ \
+    void      (*unref_wire)(void *)      /* function to unref message */
+
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+    char               *name;            /* domain controller name */
+    mrp_domctl_table_t *tables;          /* owned tables */
+    int                 ntable;          /* number of tables */
+    mrp_domctl_watch_t *watches;         /* watched tables */
+    int                 nwatch;          /* number of watches */
+} register_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+} unregister_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+    mrp_domctl_data_t  *tables;          /* data for tables to set */
+    int                 ntable;          /* number of tables */
+} set_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+    mrp_domctl_data_t *tables;           /* data in changed tables */
+    int                ntable;           /* number of changed tables */
+} notify_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+} ack_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+    int32_t     error;
+    const char *msg;
+} nak_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+    const char       *name;
+    int               noret;
+    uint32_t          narg;
+    mrp_domctl_arg_t *args;
+} invoke_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+    uint32_t          error;
+    int32_t           retval;
+    uint32_t          narg;
+    mrp_domctl_arg_t *args;
+} return_msg_t;
+
+
+typedef struct {
+    COMMON_MSG_FIELDS;
+} any_msg_t;
+
+
+union msg_u {
+    any_msg_t        any;
+    register_msg_t   reg;
+    unregister_msg_t unreg;
+    set_msg_t        set;
+    notify_msg_t     notify;
+    ack_msg_t        ack;
+    nak_msg_t        nak;
+    invoke_msg_t     invoke;
+    return_msg_t     ret;
+};
+
+
+mrp_msg_t *msg_encode_message(msg_t *msg);
+msg_t *msg_decode_message(mrp_msg_t *msg);
+mrp_json_t *json_encode_message(msg_t *msg);
+msg_t *json_decode_message(mrp_json_t *msg);
+void msg_free_message(msg_t *msg);
+
+mrp_msg_t *msg_create_notify(void);
+int msg_update_notify(mrp_msg_t *msg, int tblid, mql_result_t *r);
+
+mrp_json_t *json_create_notify(void);
+int json_update_notify(mrp_json_t *msg, int tblid, mql_result_t *r);
+
+#endif /* __MURPHY_DOMAIN_CONTROL_MESSAGE_H__ */
diff --git a/src/plugins/domain-control/murphy-domain-controller.pc.in b/src/plugins/domain-control/murphy-domain-controller.pc.in
new file mode 100644 (file)
index 0000000..83f8b0f
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-domain-controller
+Description: Murphy policy framework, domain controller library.
+Requires: murphy-common = @PACKAGE_VERSION@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-domain-controller
+Cflags: -I${includedir}
diff --git a/src/plugins/domain-control/notify.c b/src/plugins/domain-control/notify.c
new file mode 100644 (file)
index 0000000..e48fabd
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include <murphy-db/mql-result.h>
+
+#include "domain-control-types.h"
+#include "message.h"
+#include "table.h"
+#include "notify.h"
+
+
+static void prepare_proxy_notification(pep_proxy_t *proxy)
+{
+    proxy->notify_ntable  = 0;
+    proxy->notify_ncolumn = 0;
+    proxy->notify_fail    = false;
+}
+
+
+static int collect_watch_notification(pep_watch_t *w)
+{
+    pep_proxy_t  *proxy = w->proxy;
+    mql_result_t *r     = NULL;
+    int           n;
+
+    mrp_debug("updating %s watch for %s", w->table->name, proxy->name);
+
+    if (proxy->notify_msg == NULL) {
+        if (!proxy->ops->create_notify(proxy))
+            goto fail;
+    }
+
+    if (w->table->h != MQI_HANDLE_INVALID) {
+        if (!exec_mql(mql_result_rows, &r, "select %s from %s%s%s",
+                      w->mql_columns, w->table->name,
+                      w->mql_where[0] ? " where " : "", w->mql_where)) {
+            mrp_debug("select from table %s failed", w->table->name);
+            goto fail;
+        }
+    }
+
+    n = proxy->ops->update_notify(proxy, w->id, r);
+
+    if (r != NULL)
+        mql_result_free(r);
+
+    if (n >= 0)
+        return TRUE;
+    else {
+    fail:
+        proxy->ops->free_notify(proxy);
+        proxy->notify_fail = true;
+
+        return FALSE;
+    }
+}
+
+
+static int send_proxy_notification(pep_proxy_t *proxy)
+{
+    if (proxy->notify_msg == NULL)
+        return TRUE;
+
+    if (!proxy->notify_fail) {
+        mrp_debug("notifying client %s", proxy->name);
+
+        proxy->ops->send_notify(proxy);
+        proxy->ops->free_notify(proxy);
+    }
+    else
+        mrp_log_error("Failed to generate/send notification to %s.",
+                      proxy->name);
+
+    proxy->notify_msg     = NULL;
+    proxy->notify_ntable  = 0;
+    proxy->notify_ncolumn = 0;
+    proxy->notify_fail    = false;
+
+    return TRUE;
+}
+
+
+void notify_table_changes(pdp_t *pdp)
+{
+    mrp_list_hook_t *p, *n, *wp, *wn;
+    pep_proxy_t     *proxy;
+    pep_table_t     *t;
+    pep_watch_t     *w;
+
+    mrp_debug("notifying clients about table changes");
+
+    mrp_list_foreach(&pdp->proxies, p, n) {
+        proxy = mrp_list_entry(p, typeof(*proxy), hook);
+        prepare_proxy_notification(proxy);
+    }
+
+    mrp_list_foreach(&pdp->tables, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+
+        mrp_debug("table '%s' has %s changes", t->name,
+                  t->changed ? "unsynced" : "no");
+
+        if (!t->changed)
+            continue;
+
+        mrp_list_foreach(&t->watches, wp, wn) {
+            w = mrp_list_entry(wp, typeof(*w), tbl_hook);
+            w->proxy->notify = true;
+        }
+    }
+
+    mrp_list_foreach(&pdp->proxies, p, n) {
+        proxy = mrp_list_entry(p, typeof(*proxy), hook);
+
+        mrp_debug("proxy %s needs %supdate", proxy->name,
+                  proxy->notify ? "" : "no ");
+
+        if (proxy->notify) {
+            mrp_list_foreach(&proxy->watches, wp, wn) {
+                w = mrp_list_entry(wp, typeof(*w), pep_hook);
+                if (!collect_watch_notification(w))
+                    break;
+            }
+
+            send_proxy_notification(proxy);
+
+            proxy->notify = false;
+        }
+    }
+
+    mrp_list_foreach(&pdp->tables, p, n) {
+        t = mrp_list_entry(p, typeof(*t), hook);
+        t->changed = false;
+    }
+}
diff --git a/src/plugins/domain-control/notify.h b/src/plugins/domain-control/notify.h
new file mode 100644 (file)
index 0000000..e53cc46
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_NOTIFY_H__
+#define __MURPHY_DOMAIN_CONTROL_NOTIFY_H__
+
+#include "domain-control-types.h"
+
+void notify_table_changes(pdp_t *pdp);
+
+#endif /* __MURPHY_DOMAIN_CONTROL_NOTIFY_H__ */
diff --git a/src/plugins/domain-control/plugin-domain-control.c b/src/plugins/domain-control/plugin-domain-control.c
new file mode 100644 (file)
index 0000000..287844e
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/macros.h>
+
+#include <murphy/core/plugin.h>
+#include <murphy/core/console.h>
+
+#include "domain-control-types.h"
+#include "domain-control.h"
+#include "client.h"
+
+#define DEFAULT_EXTADDR MRP_DEFAULT_DOMCTL_ADDRESS
+#define NO_ADDR         NULL
+
+#ifdef MURPHY_DATADIR
+#    define DEFAULT_HTTPDIR MURPHY_DATADIR"/domain-control"
+#else
+#    define DEFAULT_HTTPDIR "/usr/share/murphy/domain-control"
+#endif
+
+enum {
+    ARG_EXTADDR,                         /* external transport address */
+    ARG_INTADDR,                         /* internal transport address */
+    ARG_WRTADDR,                         /* WRT transport address */
+    ARG_HTTPDIR                          /* content directory for HTTP */
+};
+
+
+static int plugin_init(mrp_plugin_t *plugin)
+{
+    const char *extaddr = plugin->args[ARG_EXTADDR].str;
+    const char *intaddr = plugin->args[ARG_INTADDR].str;
+    const char *wrtaddr = plugin->args[ARG_WRTADDR].str;
+    const char *httpdir = plugin->args[ARG_HTTPDIR].str;
+
+    plugin->data = create_domain_control(plugin->ctx,
+                                         extaddr && *extaddr ? extaddr : NULL,
+                                         intaddr && *intaddr ? intaddr : NULL,
+                                         wrtaddr && *wrtaddr ? wrtaddr : NULL,
+                                         httpdir);
+
+    return (plugin->data != NULL);
+}
+
+
+static void plugin_exit(mrp_plugin_t *plugin)
+{
+    pdp_t *pdp = (pdp_t *)plugin->data;
+
+    destroy_domain_control(pdp);
+}
+
+
+static void cmd_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    printf("domctl:%s() called...\n", __FUNCTION__);
+}
+
+
+#define DOMCTL_DESCRIPTION "Murphy domain-control plugin."
+#define DOMCTL_HELP                                                         \
+    "The domain-control plugin provides a control interface for Murphy\n"    \
+    "domain controllers. A domain controller is an entity capable of\n"      \
+    "enforcing domain-specific policies in a certain resource domain, eg.\n" \
+    "audio, video, CPU-scheduling, etc. The domain-control plugin allows\n"  \
+    "such entities to export and import domain-specific data to and from\n"  \
+    "Murphy. Domain controllers typically import either ready decisions\n"   \
+    "for their domain or data necessary for local decision making in\n"      \
+    "the controller itself. The controllers typically export also some\n"    \
+    "domain-specific data to Murphy which can then be used for decision\n"   \
+    "making in other domains other domains.\n"
+
+#define DOMCTL_VERSION MRP_VERSION_INT(0, 0, 2)
+#define DOMCTL_AUTHORS "Krisztian Litkey <krisztian.litkey@intel.com>"
+
+MRP_CONSOLE_GROUP(domctl_commands, "domain-control", NULL, NULL, {
+        MRP_TOKENIZED_CMD("cmd", cmd_cb, TRUE,
+                          "cmd [args]", "a command", "A command..."),
+});
+
+static mrp_plugin_arg_t domctl_args[] = {
+    MRP_PLUGIN_ARGIDX(ARG_EXTADDR, STRING, "external_address", DEFAULT_EXTADDR),
+    MRP_PLUGIN_ARGIDX(ARG_INTADDR, STRING, "internal_address", NO_ADDR        ),
+    MRP_PLUGIN_ARGIDX(ARG_WRTADDR, STRING, "wrt_address"     , NO_ADDR        ),
+    MRP_PLUGIN_ARGIDX(ARG_HTTPDIR, STRING, "httpdir", DEFAULT_HTTPDIR)
+};
+
+MURPHY_REGISTER_PLUGIN("domain-control",
+                       DOMCTL_VERSION, DOMCTL_DESCRIPTION,
+                       DOMCTL_AUTHORS, DOMCTL_HELP, MRP_MULTIPLE,
+                       plugin_init, plugin_exit,
+                       domctl_args, MRP_ARRAY_SIZE(domctl_args),
+                       NULL, 0, NULL, 0, &domctl_commands);
diff --git a/src/plugins/domain-control/proxy.c b/src/plugins/domain-control/proxy.c
new file mode 100644 (file)
index 0000000..4840717
--- /dev/null
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+
+#include "domain-control-types.h"
+#include "table.h"
+#include "proxy.h"
+
+
+/*
+ * a pending proxied invocation
+ */
+
+typedef struct {
+    mrp_list_hook_t         hook;        /* to pending list */
+    uint32_t                id;          /* request id */
+    mrp_domain_return_cb_t  cb;          /* return callback */
+    void                   *user_data;   /* opaque callback data */
+} pending_t;
+
+static void purge_pending(pep_proxy_t *proxy);
+
+
+int init_proxies(pdp_t *pdp)
+{
+    mrp_list_init(&pdp->proxies);
+
+    return TRUE;
+}
+
+
+void destroy_proxies(pdp_t *pdp)
+{
+    MRP_UNUSED(pdp);
+
+    return;
+}
+
+
+pep_proxy_t *create_proxy(pdp_t *pdp)
+{
+    pep_proxy_t *proxy;
+
+    proxy = mrp_allocz(sizeof(*proxy));
+
+    if (proxy != NULL) {
+        mrp_list_init(&proxy->hook);
+        mrp_list_init(&proxy->watches);
+        mrp_list_init(&proxy->pending);
+
+        proxy->pdp   = pdp;
+        proxy->seqno = 1;
+
+        mrp_list_append(&pdp->proxies, &proxy->hook);
+    }
+
+    return proxy;
+}
+
+
+void destroy_proxy(pep_proxy_t *proxy)
+{
+    int i;
+
+    if (proxy != NULL) {
+        mrp_list_delete(&proxy->hook);
+
+        for (i = 0; i < proxy->ntable; i++)
+            destroy_proxy_table(proxy->tables + i);
+
+        destroy_proxy_watches(proxy);
+
+        purge_pending(proxy);
+
+        mrp_free(proxy);
+    }
+}
+
+
+int register_proxy(pep_proxy_t *proxy, char *name,
+                   mrp_domctl_table_t *tables, int ntable,
+                   mrp_domctl_watch_t *watches, int nwatch,
+                   int *error, const char **errmsg)
+{
+    pep_table_t        *t;
+    mrp_domctl_watch_t *w;
+    int                 i;
+
+    proxy->name   = mrp_strdup(name);
+    proxy->tables = mrp_allocz_array(typeof(*proxy->tables) , ntable);
+    proxy->ntable = ntable;
+    proxy->notify = true;
+
+    if (proxy->name == NULL || (ntable && proxy->tables == NULL)) {
+        *error  = ENOMEM;
+        *errmsg = "failed to allocate proxy table";
+
+        return FALSE;
+    }
+
+    for (i = 0, t = proxy->tables; i < ntable; i++, t++) {
+        t->h           = MQI_HANDLE_INVALID;
+        t->name        = mrp_strdup(tables[i].table);
+        t->mql_columns = mrp_strdup(tables[i].mql_columns);
+        t->mql_index   = mrp_strdup(tables[i].mql_index);
+
+        if (t->name == NULL || t->mql_columns == NULL || t->mql_index == NULL) {
+            mrp_log_error("Failed to allocate proxy table %s for %s.",
+                          tables[i].table, name);
+            *error  = ENOMEM;
+            *errmsg = "failed to allocate proxy table";
+
+            return FALSE;
+        }
+
+        if (create_proxy_table(t, error, errmsg))
+            mrp_log_info("Client %s created table %s.", proxy->name,
+                         tables[i].table);
+        else {
+            mrp_log_error("Client %s failed to create table %s (%d: %s).",
+                          proxy->name, tables[i].table, *error, *errmsg);
+            return FALSE;
+        }
+    }
+
+    for (i = 0, w = watches; i < nwatch; i++, w++) {
+        if (create_proxy_watch(proxy, i, w->table, w->mql_columns,
+                               w->mql_where, w->max_rows, error, errmsg))
+            mrp_log_info("Client %s subscribed for table %s.", proxy->name,
+                         w->table);
+        else
+            mrp_log_error("Client %s failed to subscribe for table %s.",
+                          proxy->name, w->table);
+    }
+
+    return TRUE;
+}
+
+
+int unregister_proxy(pep_proxy_t *proxy)
+{
+    destroy_proxy(proxy);
+
+    return TRUE;
+}
+
+
+pep_proxy_t *find_proxy(pdp_t *pdp, const char *name)
+{
+    mrp_list_hook_t *p, *n;
+    pep_proxy_t     *proxy;
+
+    mrp_list_foreach(&pdp->proxies, p, n) {
+        proxy = mrp_list_entry(p, typeof(*proxy), hook);
+
+        if (!strcmp(proxy->name, name))
+            return proxy;
+    }
+
+    return NULL;
+}
+
+
+uint32_t proxy_queue_pending(pep_proxy_t *proxy,
+                             mrp_domain_return_cb_t return_cb, void *user_data)
+{
+    pending_t *pending;
+
+    if (return_cb == NULL)
+        return proxy->seqno++;
+
+    pending = mrp_allocz(sizeof(*pending));
+
+    if (pending == NULL)
+        return 0;
+
+    mrp_list_init(&pending->hook);
+
+    pending->id        = proxy->seqno++;
+    pending->cb        = return_cb;
+    pending->user_data = user_data;
+
+    mrp_list_append(&proxy->pending, &pending->hook);
+
+    return pending->id;
+}
+
+
+int proxy_dequeue_pending(pep_proxy_t *proxy, uint32_t id,
+                          mrp_domain_return_cb_t *cbp, void **user_datap)
+{
+    mrp_list_hook_t *p, *n;
+    pending_t       *pending;
+
+    mrp_list_foreach(&proxy->pending, p, n) {
+        pending = mrp_list_entry(p, typeof(*pending), hook);
+
+        if (pending->id == id) {
+            mrp_list_delete(&pending->hook);
+            *cbp        = pending->cb;
+            *user_datap = pending->user_data;
+
+            mrp_free(pending);
+
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void purge_pending(pep_proxy_t *proxy)
+{
+    mrp_list_hook_t *p, *n;
+    pending_t       *pending;
+
+    mrp_list_foreach(&proxy->pending, p, n) {
+        pending = mrp_list_entry(p, typeof(*pending), hook);
+
+        mrp_list_delete(&pending->hook);
+        mrp_free(pending);
+    }
+}
diff --git a/src/plugins/domain-control/proxy.h b/src/plugins/domain-control/proxy.h
new file mode 100644 (file)
index 0000000..42e03e8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_PROXY_H__
+#define __MURPHY_DOMAIN_CONTROL_PROXY_H__
+
+#include <murphy/core/domain.h>
+
+#include "domain-control-types.h"
+
+int init_proxies(pdp_t *pdp);
+void destroy_proxies(pdp_t *pdp);
+
+pep_proxy_t *create_proxy(pdp_t *pdp);
+void destroy_proxy(pep_proxy_t *proxy);
+
+int register_proxy(pep_proxy_t *proxy, char *name,
+                   mrp_domctl_table_t *tables, int ntable,
+                   mrp_domctl_watch_t *watches, int nwatch,
+                   int *error, const char **errmsg);
+int unregister_proxy(pep_proxy_t *proxy);
+
+pep_proxy_t *find_proxy(pdp_t *pdp, const char *name);
+
+uint32_t proxy_queue_pending(pep_proxy_t *proxy,
+                             mrp_domain_return_cb_t return_cb, void *user_data);
+int proxy_dequeue_pending(pep_proxy_t *proxy, uint32_t id,
+                          mrp_domain_return_cb_t *cb, void **user_datap);
+
+#endif /* __MURPHY_DOMAIN_CONTROL_PROXY_H__ */
diff --git a/src/plugins/domain-control/table.c b/src/plugins/domain-control/table.c
new file mode 100644 (file)
index 0000000..6e7615a
--- /dev/null
@@ -0,0 +1,645 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+
+#include <murphy/common/debug.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+
+#include <murphy-db/mqi.h>
+#include <murphy-db/mql.h>
+#include <murphy-db/mdb.h>
+
+#include "domain-control.h"
+#include "table.h"
+
+#define FAIL(ec, msg) do {                      \
+        *errcode = ec;                          \
+        *errmsg = msg;                          \
+        goto fail;                              \
+    } while (0)
+
+static pep_table_t *lookup_watch_table(pdp_t *pdp, const char *name);
+
+/*
+ * proxied and tracked tables
+ */
+
+
+static void table_change_cb(mqi_event_t *e, void *tptr)
+{
+    static const char *events[] = {
+        "unknown (?)",
+        "column change",
+        "row insert",
+        "row delete",
+        "table create",
+        "table drop",
+        "transaction start (?)",
+        "transaction end (?)",
+    };
+    pep_table_t *t = (pep_table_t *)tptr;
+
+    if (!t->changed) {
+        t->changed = true;
+        mrp_debug("table '%s' changed by %s event", t->name, events[e->event]);
+    }
+}
+
+
+static int add_table_triggers(pep_table_t *t)
+{
+    mdb_table_t      *tbl;
+    mqi_column_def_t  cols[256];
+    int               ncol, i;
+
+    if (t->h == MQI_HANDLE_INVALID) {
+        errno = EAGAIN;
+        return -1;
+    }
+
+    if ((tbl = mdb_table_find(t->name)) == NULL) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if ((ncol = mdb_table_describe(tbl, &cols[0], sizeof(cols))) <= 0) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    if (mdb_trigger_add_row_callback(tbl, table_change_cb, t, NULL)) {
+        errno = EINVAL;
+        return -1;
+    }
+
+    for (i = 0; i < ncol; i++) {
+        if (mdb_trigger_add_column_callback(tbl, i, table_change_cb,
+                                            t, NULL) < 0) {
+            mdb_trigger_delete_row_callback(tbl, table_change_cb, t);
+            errno = EINVAL;
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+static void del_table_triggers(pep_table_t *t)
+{
+    mdb_table_t      *tbl;
+    mqi_column_def_t  cols[256];
+    int               ncol, i;
+
+    if (t->h == MQI_HANDLE_INVALID)
+        return;
+
+    if ((tbl = mdb_table_find(t->name)) == NULL)
+        return;
+
+    ncol = mdb_table_describe(tbl, &cols[0], sizeof(cols));
+
+    mdb_trigger_delete_row_callback(tbl, table_change_cb, t);
+
+    for (i = 0; i < ncol; i++)
+        mdb_trigger_delete_column_callback(tbl, i, table_change_cb, t);
+}
+
+
+static void table_event_cb(mqi_event_t *e, void *user_data)
+{
+    pdp_t        *pdp  = (pdp_t *)user_data;
+    const char   *name = e->table.table.name;
+    mqi_handle_t  h    = e->table.table.handle;
+    pep_table_t  *t;
+
+    switch (e->event) {
+    case mqi_table_created:
+        mrp_debug("table %s (0x%x) created", name, h);
+        break;
+
+    case mqi_table_dropped:
+        mrp_debug("table %s (0x%x) dropped", name, h);
+        break;
+
+    default:
+        return;
+    }
+
+    t = lookup_watch_table(pdp, name);
+
+    if (t != NULL) {
+        t->changed = true;
+
+        if (e->event == mqi_table_created) {
+            t->h = h;
+            add_table_triggers(t);
+        }
+        else {
+            t->h = MQI_HANDLE_INVALID;
+            del_table_triggers(t);
+        }
+    }
+
+    schedule_notification(pdp);
+}
+
+
+static void transaction_event_cb(mqi_event_t *e, void *user_data)
+{
+    pdp_t *pdp   = (pdp_t *)user_data;
+    int    depth = e->transact.depth;
+
+    switch (e->event) {
+    case mqi_transaction_end:
+        if (depth == 1) {
+            mrp_debug("outermost transaction ended");
+
+            if (pdp->ractive) {
+                mrp_debug("resolver active, delaying client notifications");
+                pdp->rblocked = true;
+            }
+            else
+                schedule_notification(pdp);
+        }
+        else
+            mrp_debug("nested transaction (#%d) ended", depth);
+        break;
+
+    case mqi_transaction_start:
+        if (depth == 1)
+            mrp_debug("outermost transaction started");
+        else
+            mrp_debug("nested transaction (#%d) started", depth);
+        break;
+
+    default:
+        break;
+    }
+}
+
+
+static int open_db(pdp_t *pdp)
+{
+    static bool done = false;
+
+    if (done)
+        return TRUE;
+
+    if (mqi_open() == 0) {
+        if (mqi_create_transaction_trigger(transaction_event_cb, pdp) == 0 &&
+            mqi_create_table_trigger(table_event_cb, pdp) == 0) {
+            done = true;
+            return TRUE;
+        }
+
+        mqi_drop_transaction_trigger(transaction_event_cb, pdp);
+    }
+
+    return FALSE;
+}
+
+
+static void close_db(pdp_t *pdp)
+{
+    mqi_drop_table_trigger(table_event_cb, pdp);
+    mqi_drop_transaction_trigger(transaction_event_cb, pdp);
+}
+
+
+static void purge_watch_table_cb(void *key, void *entry);
+
+
+
+int init_tables(pdp_t *pdp)
+{
+    mrp_htbl_config_t hcfg;
+
+    if (open_db(pdp)) {
+        mrp_list_init(&pdp->tables);
+
+        mrp_clear(&hcfg);
+        hcfg.comp = mrp_string_comp;
+        hcfg.hash = mrp_string_hash;
+        hcfg.free = purge_watch_table_cb;
+
+        pdp->watched = mrp_htbl_create(&hcfg);
+    }
+
+    return (pdp->watched != NULL);
+}
+
+
+void destroy_tables(pdp_t *pdp)
+{
+    close_db(pdp);
+    mrp_htbl_destroy(pdp->watched, TRUE);
+
+    pdp->watched = NULL;
+}
+
+
+int exec_mql(mql_result_type_t type, mql_result_t **resultp,
+             const char *format, ...)
+{
+    mql_result_t *r;
+    char          buf[4096];
+    va_list       ap;
+    int           success, n;
+
+    va_start(ap, format);
+    n = vsnprintf(buf, sizeof(buf), format, ap);
+    va_end(ap);
+
+    if (n < (int)sizeof(buf)) {
+        r       = mql_exec_string(type, buf);
+        success = (r == NULL || mql_result_is_success(r));
+
+        if (resultp != NULL) {
+            *resultp = r;
+            return success;
+        }
+        else {
+            mql_result_free(r);
+            return success;
+        }
+    }
+    else {
+        errno = EOVERFLOW;
+        if (resultp != NULL)
+            *resultp = NULL;
+
+        return FALSE;
+    }
+}
+
+
+static int get_table_description(pep_table_t *t)
+{
+    mqi_column_def_t    columns[MQI_COLUMN_MAX];
+    mrp_domctl_value_t *values = NULL;
+    int                 ncolumn, i;
+
+    if (t->h == MQI_HANDLE_INVALID)
+        t->h = mqi_get_table_handle((char *)t->name);
+
+    if (t->h != MQI_HANDLE_INVALID) {
+        ncolumn = mqi_describe(t->h, columns, MRP_ARRAY_SIZE(columns));
+
+        if (ncolumn > 0) {
+            t->columns = mrp_allocz_array(typeof(*t->columns), ncolumn);
+            t->coldesc = mrp_allocz_array(typeof(*t->coldesc), ncolumn + 1);
+
+            if (t->columns != NULL && t->coldesc != NULL) {
+                memcpy(t->columns, columns, ncolumn * sizeof(*t->columns));
+                t->ncolumn = ncolumn;
+
+                for (i = 0; i < t->ncolumn; i++) {
+                    t->coldesc[i].cindex = i;
+                    t->coldesc[i].offset = (int)(ptrdiff_t)&values[i].str;
+                }
+
+                t->coldesc[i].cindex = -1;
+                t->coldesc[i].offset = 0;
+
+                return TRUE;
+            }
+        }
+    }
+
+    return FALSE;
+}
+
+
+int create_proxy_table(pep_table_t *t, int *errcode, const char **errmsg)
+{
+    mrp_list_init(&t->hook);
+    mrp_list_init(&t->watches);
+
+    if (mqi_get_table_handle((char *)t->name) != MQI_HANDLE_INVALID)
+        FAIL(EEXIST, "DB error: table already exists");
+
+    if (exec_mql(mql_result_dontcare, NULL,
+                 "create temporary table %s (%s)", t->name, t->mql_columns)) {
+        if (t->mql_index && t->mql_index[0]) {
+            if (!exec_mql(mql_result_dontcare, NULL,
+                          "create index on %s (%s)", t->name, t->mql_index))
+                FAIL(EINVAL, "DB error: failed to create table index");
+        }
+
+        if (!get_table_description(t))
+            FAIL(EINVAL, "DB error: failed to get table description");
+
+        return TRUE;
+    }
+    else
+        FAIL(ENOMEM, "DB error: failed to create table");
+
+ fail:
+    return FALSE;
+}
+
+
+void destroy_proxy_table(pep_table_t *t)
+{
+    mrp_debug("destroying table %s", t->name ? t->name : "<unknown>");
+
+    if (t->h != MQI_HANDLE_INVALID)
+        mqi_drop_table(t->h);
+
+    mrp_free(t->mql_columns);
+    mrp_free(t->mql_index);
+
+    mrp_free(t->columns);
+    mrp_free(t->coldesc);
+    mrp_free(t->name);
+
+    t->name    = NULL;
+    t->h       = MQI_HANDLE_INVALID;
+    t->columns = NULL;
+    t->ncolumn = 0;
+}
+
+
+void destroy_proxy_tables(pep_proxy_t *proxy)
+{
+    mqi_handle_t tx;
+    int          i;
+
+    mrp_debug("destroying tables of client %s", proxy->name);
+
+    tx = mqi_begin_transaction();
+    for (i = 0; i < proxy->ntable; i++)
+        destroy_proxy_table(proxy->tables + i);
+    mqi_commit_transaction(tx);
+
+    proxy->tables = NULL;
+    proxy->ntable = 0;
+}
+
+
+pep_table_t *create_watch_table(pdp_t *pdp, const char *name)
+{
+    pep_table_t *t;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+        mrp_list_init(&t->hook);
+        mrp_list_init(&t->watches);
+
+        t->h    = MQI_HANDLE_INVALID;
+        t->name = mrp_strdup(name);
+
+        if (t->name == NULL)
+            goto fail;
+
+        get_table_description(t);
+
+        if (t->h != MQI_HANDLE_INVALID)
+            add_table_triggers(t);
+
+        if (!mrp_htbl_insert(pdp->watched, t->name, t))
+            goto fail;
+
+        mrp_list_append(&pdp->tables, &t->hook);
+    }
+
+    return t;
+
+ fail:
+    destroy_watch_table(pdp, t);
+
+    return FALSE;
+}
+
+
+static void destroy_table_watches(pep_table_t *t)
+{
+    pep_watch_t     *w;
+    mrp_list_hook_t *p, *n;
+
+    if (t != NULL) {
+        del_table_triggers(t);
+
+        mrp_list_foreach(&t->watches, p, n) {
+            w = mrp_list_entry(p, typeof(*w), tbl_hook);
+
+            mrp_list_delete(&w->tbl_hook);
+            mrp_list_delete(&w->pep_hook);
+
+            mrp_free(w->mql_columns);
+            mrp_free(w->mql_where);
+            mrp_free(w);
+        }
+    }
+}
+
+
+void destroy_watch_table(pdp_t *pdp, pep_table_t *t)
+{
+    mrp_list_delete(&t->hook);
+    t->h = MQI_HANDLE_INVALID;
+
+    if (pdp != NULL)
+        mrp_htbl_remove(pdp->watched, t->name, FALSE);
+
+    destroy_table_watches(t);
+}
+
+
+static pep_table_t *lookup_watch_table(pdp_t *pdp, const char *name)
+{
+    return mrp_htbl_lookup(pdp->watched, (void *)name);
+}
+
+
+static void purge_watch_table_cb(void *key, void *entry)
+{
+    pep_table_t *t = (pep_table_t *)entry;
+
+    MRP_UNUSED(key);
+
+    destroy_watch_table(NULL, t);
+}
+
+
+int create_proxy_watch(pep_proxy_t *proxy, int id,
+                       const char *table, const char *mql_columns,
+                       const char *mql_where, int max_rows,
+                       int *error, const char **errmsg)
+{
+    pdp_t       *pdp = proxy->pdp;
+    pep_table_t *t;
+    pep_watch_t *w;
+
+    t = lookup_watch_table(pdp, table);
+
+    if (t == NULL) {
+        t = create_watch_table(pdp, table);
+
+        if (t == NULL) {
+            *error  = EINVAL;
+            *errmsg = "failed to watch table";
+        }
+    }
+
+    w = mrp_allocz(sizeof(*w));
+
+    if (w != NULL) {
+        mrp_list_init(&w->tbl_hook);
+        mrp_list_init(&w->pep_hook);
+
+        w->table        = t;
+        w->mql_columns  = mrp_strdup(mql_columns);
+        w->mql_where    = mrp_strdup(mql_where ? mql_where : "");
+        w->max_rows     = max_rows;
+        w->proxy        = proxy;
+        w->id           = id;
+        w->notify       = true;
+
+        if (w->mql_columns == NULL || w->mql_where == NULL)
+            goto fail;
+
+        mrp_list_append(&t->watches, &w->tbl_hook);
+        mrp_list_append(&proxy->watches, &w->pep_hook);
+
+        return TRUE;
+    }
+    else {
+        *error  = ENOMEM;
+        *errmsg = "failed to allocate table watch";
+    }
+
+ fail:
+    if (w != NULL) {
+        mrp_free(w->mql_columns);
+        mrp_free(w->mql_where);
+        mrp_free(w);
+    }
+
+    return FALSE;
+}
+
+
+void destroy_proxy_watches(pep_proxy_t *proxy)
+{
+    pep_watch_t     *w;
+    mrp_list_hook_t *p, *n;
+
+    if (proxy != NULL) {
+        mrp_list_foreach(&proxy->watches, p, n) {
+            w = mrp_list_entry(p, typeof(*w), pep_hook);
+
+            mrp_list_delete(&w->tbl_hook);
+            mrp_list_delete(&w->pep_hook);
+
+            mrp_free(w);
+        }
+    }
+}
+
+
+static void reset_proxy_tables(pep_proxy_t *proxy)
+{
+    int i;
+
+    for (i = 0; i < proxy->ntable; i++)
+        mqi_delete_from(proxy->tables[i].h, NULL);
+}
+
+
+static int insert_into_table(pep_table_t *t,
+                             mrp_domctl_value_t **rows, int nrow)
+{
+    void *data[2];
+    int   i;
+
+    data[1] = NULL;
+
+    for (i = 0; i < nrow; i++) {
+        data[0] = rows[i];
+        if (mqi_insert_into(t->h, 0, t->coldesc, data) != 1)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+int set_proxy_tables(pep_proxy_t *proxy, mrp_domctl_data_t *tables, int ntable,
+                     int *error, const char **errmsg)
+{
+    mqi_handle_t    tx;
+    pep_table_t    *t;
+    int             i, id;
+
+    tx = mqi_begin_transaction();
+
+    if (tx != MQI_HANDLE_INVALID) {
+        reset_proxy_tables(proxy);
+
+        for (i = 0; i < ntable; i++) {
+            id = tables[i].id;
+
+            if (id < 0 || id >= proxy->ntable)
+                goto fail;
+
+            t = proxy->tables + id;
+
+            if (tables[i].ncolumn != t->ncolumn)
+                goto fail;
+
+#if 0
+            if (!delete_from_table(t, tables[i].rows, tables[i].nrow))
+                goto fail;
+#endif
+
+            if (!insert_into_table(t, tables[i].rows, tables[i].nrow))
+                goto fail;
+
+
+        }
+
+        mqi_commit_transaction(tx);
+
+        return TRUE;
+
+    fail:
+        *error  = EINVAL;
+        *errmsg = "failed to set tables";
+        mqi_rollback_transaction(tx);
+    }
+
+    return FALSE;
+}
diff --git a/src/plugins/domain-control/table.h b/src/plugins/domain-control/table.h
new file mode 100644 (file)
index 0000000..243cc22
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DOMAIN_CONTROL_TABLE_H__
+#define __MURPHY_DOMAIN_CONTROL_TABLE_H__
+
+#include <murphy-db/mql-result.h>
+
+#include "client.h"
+#include "domain-control-types.h"
+
+int init_tables(pdp_t *pdp);
+void destroy_tables(pdp_t *pdp);
+
+int create_proxy_table(pep_table_t *t, int *errcode, const char **errmsg);
+
+int create_proxy_watch(pep_proxy_t *proxy, int id,
+                       const char *table, const char *mql_columns,
+                       const char *mql_where, int max_rows,
+                       int *error, const char **errmsg);
+
+void destroy_watch_table(pdp_t *pdp, pep_table_t *t);
+
+void destroy_proxy_table(pep_table_t *t);
+void destroy_proxy_tables(pep_proxy_t *proxy);
+
+void destroy_proxy_watches(pep_proxy_t *proxy);
+
+int set_proxy_tables(pep_proxy_t *proxy, mrp_domctl_data_t *tables, int ntable,
+                     int *error, const char **errmsg);
+
+int exec_mql(mql_result_type_t type, mql_result_t **resultp,
+             const char *format, ...);
+
+
+#endif /* __MURPHY_DOMAIN_CONTROL_TABLE_H__ */
diff --git a/src/plugins/domain-control/test-client.c b/src/plugins/domain-control/test-client.c
new file mode 100644 (file)
index 0000000..f801470
--- /dev/null
@@ -0,0 +1,1381 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <netdb.h>
+#include <signal.h>
+#include <alloca.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+#include <breedline/breedline-murphy.h>
+
+#include "client.h"
+
+#define DEFAULT_PROMPT "test-controller"
+
+
+/*
+ * client context
+ */
+
+typedef struct {
+    const char      *addrstr;            /* server address */
+    int              zone;               /* run in zone control mode */
+    int              verbose;            /* verbose mode */
+    int              audio;              /* subscribe for audio_playback_* */
+    mrp_mainloop_t  *ml;                 /* murphy mainloop */
+    void            *dc;                 /* domain controller */
+    brl_t           *brl;                /* breedline for terminal input */
+} client_t;
+
+
+#define NVALUE 512
+
+
+/*
+ * device and stream definitions
+ */
+
+#define NDEVICE        (MRP_ARRAY_SIZE(devices) - 1)
+#define DEVICE_NCOLUMN 4
+
+typedef struct {
+    const char *name;
+    const char *type;
+    int         public;
+    int         available;
+} device_t;
+
+static device_t devices[] = {
+    { "builtin-speaker" , "speaker"  , TRUE , TRUE  },
+    { "builtin-earpiece", "speaker"  , FALSE, TRUE  },
+    { "usb-speaker"     , "speaker"  , TRUE , FALSE },
+    { "a2dp-speaker"    , "speaker"  , TRUE , FALSE },
+    { "wired-headset"   , "headset"  , FALSE, FALSE },
+    { "usb-headphone"   , "headphone", FALSE, FALSE },
+    { "a2dp-headphone"  , "headphone", FALSE, FALSE },
+    { "sco-headset"     , "headset"  , FALSE, FALSE },
+    { NULL              , NULL       , FALSE, FALSE }
+};
+
+#define NSTREAM        (MRP_ARRAY_SIZE(streams) - 1)
+#define STREAM_NCOLUMN 4
+
+typedef struct {
+    const char *name;
+    const char *role;
+    pid_t       owner;
+    int         playing;
+} stream_t;
+
+static stream_t streams[] = {
+    { "player1", "player"   , 1234, FALSE },
+    { "player2", "player"   , 4321, FALSE },
+    { "navit"  , "navigator", 5432, FALSE },
+    { "phone"  , "call"     , 6666, FALSE },
+    { NULL     , NULL       , 0   , FALSE }
+};
+
+
+/*
+ * device and stream descriptors
+ */
+
+#define DEVICE_COLUMNS          \
+    "name      varchar(32), "   \
+    "type      varchar(32), "   \
+    "public    integer    , "   \
+    "available integer"
+
+#define DEVICE_INDEX "name"
+
+#define DEVICE_SELECT "*"
+
+#define DEVICE_WHERE  NULL
+
+#define STREAM_COLUMNS          \
+    "name      varchar(32),"    \
+    "role      varchar(32),"    \
+    "owner     unsigned   ,"    \
+    "playing   integer"
+
+#define STREAM_INDEX "name"
+
+#define STREAM_SELECT "*"
+#define STREAM_WHERE  NULL
+
+#define SELECT_ALL "*"
+#define ANY_WHERE  NULL
+
+mrp_domctl_table_t media_tables[] = {
+    MRP_DOMCTL_TABLE("test-devices", DEVICE_COLUMNS, DEVICE_INDEX),
+    MRP_DOMCTL_TABLE("test-streams", STREAM_COLUMNS, STREAM_INDEX),
+};
+
+mrp_domctl_watch_t media_watches[] = {
+    MRP_DOMCTL_WATCH("test-devices", DEVICE_SELECT, DEVICE_WHERE, 0),
+    MRP_DOMCTL_WATCH("test-streams", STREAM_SELECT, STREAM_WHERE, 0),
+    MRP_DOMCTL_WATCH("audio_playback_owner", SELECT_ALL, ANY_WHERE, 0),
+    MRP_DOMCTL_WATCH("audio_playback_users", SELECT_ALL, ANY_WHERE, 0),
+};
+
+
+/*
+ * zone and call definitions
+ */
+
+#define NZONE        (MRP_ARRAY_SIZE(zones) - 1)
+#define ZONE_NCOLUMN 3
+
+typedef struct {
+    const char *name;
+    int         occupied;
+    int         active;
+} zone_t;
+
+static zone_t zones[] = {
+    { "driver"     , TRUE , FALSE },
+    { "fearer"     , FALSE, TRUE  },
+    { "back-left"  , TRUE , FALSE },
+    { "back-center", FALSE, FALSE },
+    { "back-right" , TRUE , TRUE  },
+    { NULL         , FALSE, FALSE }
+};
+
+
+#define NCALL        (MRP_ARRAY_SIZE(calls) - 1)
+#define CALL_NCOLUMN 3
+
+typedef struct {
+    int         id;
+    const char *state;
+    const char *modem;
+} call_t;
+
+static call_t calls[] = {
+    { 1, "active"  , "modem1" },
+    { 2, "ringing" , "modem1" },
+    { 3, "held"    , "modem2" },
+    { 4, "alerting", "modem2" },
+    { 0, NULL      , NULL     }
+};
+
+
+/*
+ * zone and call descriptors
+ */
+
+#define ZONE_COLUMNS          \
+    "name      varchar(32), " \
+    "occupied  integer    , " \
+    "active    integer"
+
+#define ZONE_INDEX "name"
+
+#define ZONE_SELECT "*"
+
+#define ZONE_WHERE  NULL
+
+#define CALL_COLUMNS          \
+    "id        integer    , " \
+    "state     varchar(32), " \
+    "modem     varchar(32)"
+
+#define CALL_INDEX "id"
+
+#define CALL_SELECT "*"
+
+#define CALL_WHERE  NULL
+
+mrp_domctl_table_t zone_tables[] = {
+    MRP_DOMCTL_TABLE("test-zones", ZONE_COLUMNS, ZONE_INDEX),
+    MRP_DOMCTL_TABLE("test-calls", CALL_COLUMNS, CALL_INDEX),
+};
+
+mrp_domctl_watch_t zone_watches[] = {
+    MRP_DOMCTL_WATCH("test-zones", ZONE_SELECT, ZONE_WHERE, 0),
+    MRP_DOMCTL_WATCH("test-calls", CALL_SELECT, CALL_WHERE, 0),
+    MRP_DOMCTL_WATCH("audio_playback_owner", SELECT_ALL, ANY_WHERE, 0),
+    MRP_DOMCTL_WATCH("audio_playback_users", SELECT_ALL, ANY_WHERE, 0),
+};
+
+mrp_domctl_table_t *exports;
+int                 nexport;
+mrp_domctl_watch_t *imports;
+int                 nimport;
+
+
+static client_t *client;
+
+
+static void fatal_msg(int error, const char *format, ...);
+static void error_msg(const char *format, ...);
+static void info_msg(const char *format, ...);
+
+static void export_data(client_t *c);
+
+
+static void plug_device(client_t *c, const char *name, int plug)
+{
+    device_t *d;
+    int       changed;
+
+    if (c->zone) {
+        error_msg("cannot plug/unplug, client is in zone mode");
+        return;
+    }
+
+    changed = FALSE;
+
+    for (d = devices; d->name != NULL; d++) {
+        if (!strcmp(d->name, name)) {
+            changed = plug ^ d->available;
+            d->available = plug;
+            break;
+        }
+    }
+
+    if (changed) {
+        info_msg("device '%s' is now %splugged", d->name, plug ? "" : "un");
+        export_data(c);
+    }
+}
+
+
+static void list_devices(void)
+{
+    device_t *d;
+    int       n;
+
+    for (d = devices, n = 0; d->name != NULL; d++, n++) {
+        info_msg("device '%s': (%s, %s), %s",
+                 d->name, d->type, d->public ? "public" : "private",
+                 d->available ? "available" : "currently unplugged");
+    }
+
+    if (n == 0)
+        info_msg("devices: none");
+}
+
+
+static void play_stream(client_t *c, const char *name, int play)
+{
+    stream_t *s;
+    int       changed;
+
+    if (c->zone) {
+        error_msg("cannot control streams, client is in zone mode");
+        return;
+    }
+
+    changed = FALSE;
+
+    for (s = streams; s->name != NULL; s++) {
+        if (!strcmp(s->name, name)) {
+            changed = play ^ s->playing;
+            s->playing = play;
+            break;
+        }
+    }
+
+    if (changed) {
+        info_msg("stream '%s' is now %s", s->name, play ? "playing":"stopped");
+        export_data(c);
+    }
+}
+
+
+static void list_streams(void)
+{
+    stream_t *s;
+    int       n;
+
+    for (s = streams, n = 0; s->name != NULL; s++, n++) {
+        info_msg("stream '%s': role %s, owner %u, currently %splaying",
+                 s->name, s->role, s->owner, s->playing ? "" : "not ");
+    }
+
+    if (n == 0)
+        info_msg("streams: none");
+}
+
+
+static void set_zone_state(client_t *c, const char *config)
+{
+    zone_t *z;
+    int     occupied, active, changed, len;
+    char    name[256], *end;
+
+    if (!c->zone) {
+        error_msg("cannot control zones, client is not in zone mode");
+        return;
+    }
+
+    while (*config == ' ' || *config == '\t')
+        config++;
+
+    end = strchr(config, ' ');
+    if (end == NULL)
+        return;
+
+    len = end - config;
+    strncpy(name, config, len);
+    name[len] = '\0';
+
+    config = end + 1;
+    while (*config == ' ' || *config == '\t')
+        config++;
+
+    occupied = FALSE;
+    active   = FALSE;
+    changed  = FALSE;
+
+    if (strstr(config, "occupied"))
+        occupied = TRUE;
+    if (strstr(config, "active"))
+        active = TRUE;
+
+    for (z = zones; z->name != NULL; z++) {
+        if (!strcmp(z->name, name)) {
+            changed     = (active ^ z->active) | (occupied ^ z->occupied);
+            z->active   = active;
+            z->occupied = occupied;
+            break;
+        }
+    }
+
+    if (changed) {
+        info_msg("zone '%s' is now %s and %s", z->name,
+                 z->occupied ? "occupied" : "free",
+                 z->active   ? "active"   : "idle");
+        export_data(c);
+    }
+}
+
+
+static void list_zones(void)
+{
+    zone_t *z;
+    int     n;
+
+    for (z = zones, n = 0; z->name != NULL; z++, n++) {
+        info_msg("zone '%s' is now %s and %s", z->name,
+                 z->occupied ? "occupied" : "free",
+                 z->active   ? "active"   : "idle");
+    }
+
+    if (n == 0)
+        info_msg("zones: none");
+}
+
+
+static void set_call_state(client_t *c, const char *config)
+{
+    call_t *call;
+    char   idstr[64], *state, *end;
+    int     id, changed, len;
+
+    if (!c->zone) {
+        error_msg("cannot control calls, client is not in zone mode");
+        return;
+    }
+
+    while (*config == ' ' || *config == '\t')
+        config++;
+
+    end = strchr(config, ' ');
+    if (end == NULL)
+        return;
+
+    len = end - config;
+    strncpy(idstr, config, len);
+    idstr[len] = '\0';
+
+    config = end + 1;
+    while (*config == ' ' || *config == '\t')
+        config++;
+    state = (char *)config;
+
+    id = strtoul(idstr, &end, 10);
+
+    if (end && *end) {
+        error_msg("invalid call id '%s'", idstr);
+        return;
+    }
+
+    changed = FALSE;
+    for (call = calls; call->id > 0; call++) {
+        if (call->id == id) {
+            if (strcmp(call->state, state)) {
+                mrp_free((char *)call->state);
+                call->state = mrp_strdup(state);
+                changed     = TRUE;
+                break;
+            }
+        }
+    }
+
+    if (changed) {
+        info_msg("call #%d is now %s", call->id, call->state);
+        export_data(c);
+    }
+}
+
+
+static void list_calls(void)
+{
+    call_t *c;
+    int     n;
+
+    for (c = calls, n = 0; c->id > 0; c++, n++) {
+        info_msg("call #%d: %s (on modem %s)", c->id, c->state, c->modem);
+    }
+
+    if (n == 0)
+        info_msg("calls: none");
+}
+
+
+static void init_devices(void)
+{
+    mrp_clear(&devices);
+}
+
+
+static void reset_devices(void)
+{
+    int i;
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(devices); i++) {
+        mrp_free((char *)devices[i].name);
+        mrp_free((char *)devices[i].type);
+    }
+
+    mrp_clear(&devices);
+}
+
+
+void update_devices(mrp_domctl_data_t *data)
+{
+    device_t           *d;
+    mrp_domctl_value_t *v;
+    int                 i;
+
+    if (data->nrow != 0 && data->ncolumn != DEVICE_NCOLUMN) {
+        error_msg("incorrect number of columns in device update (%d != %d)",
+                  data->ncolumn, DEVICE_NCOLUMN);
+        return;
+    }
+
+    if (data->nrow > (int)NDEVICE) {
+        error_msg("too many rows (%d) in device update", data->nrow);
+        return;
+    }
+
+    if (data->nrow == 0)
+        reset_devices();
+    else {
+        d = devices;
+
+        for (i = 0; i < data->nrow; i++) {
+            mrp_free((char *)d->name);
+            mrp_free((char *)d->type);
+
+            v            = data->rows[i];
+            d->name      = mrp_strdup(v[0].str);
+            d->type      = mrp_strdup(v[1].str);
+            d->public    = v[2].s32;
+            d->available = v[3].s32;
+
+            d += 1;
+        }
+    }
+
+    list_devices();
+}
+
+
+static void init_streams(void)
+{
+    mrp_clear(&streams);
+}
+
+
+static void reset_streams(void)
+{
+    int i;
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(streams); i++) {
+        mrp_free((char *)streams[i].name);
+        mrp_free((char *)streams[i].role);
+    }
+
+    mrp_clear(&streams);
+}
+
+
+void update_streams(mrp_domctl_data_t *data)
+{
+    stream_t           *s;
+    mrp_domctl_value_t *v;
+    int                 i;
+
+    if (data->nrow != 0 && data->ncolumn != STREAM_NCOLUMN) {
+        error_msg("incorrect number of columns in stream update (%d != %d)",
+                  data->ncolumn, STREAM_NCOLUMN);
+        return;
+    }
+
+    if (data->nrow > (int)NSTREAM) {
+        error_msg("too many rows (%d) in stream update", data->nrow);
+        return;
+    }
+
+    if (data->nrow == 0)
+        reset_streams();
+    else {
+        s = streams;
+
+        for (i = 0; i < data->nrow; i++) {
+            mrp_free((char *)s->name);
+            mrp_free((char *)s->role);
+
+            v          = data->rows[i];
+            s->name    = mrp_strdup(v[0].str);
+            s->role    = mrp_strdup(v[1].str);
+            s->owner   = v[2].u32;
+            s->playing = v[3].s32;
+
+            s += 1;
+        }
+    }
+
+    list_streams();
+}
+
+
+static void init_zones(void)
+{
+    mrp_clear(&zones);
+}
+
+
+static void reset_zones(void)
+{
+    int i;
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(zones); i++)
+        mrp_free((char *)zones[i].name);
+
+    mrp_clear(&zones);
+}
+
+
+void update_zones(mrp_domctl_data_t *data)
+{
+    zone_t             *z;
+    mrp_domctl_value_t *v;
+    int                 i;
+
+    if (data->nrow != 0 && data->ncolumn != ZONE_NCOLUMN) {
+        error_msg("incorrect number of columns in zone update (%d != %d)",
+                  data->ncolumn, ZONE_NCOLUMN);
+        return;
+    }
+
+    if (data->nrow > (int)NZONE) {
+        error_msg("too many rows (%d) in zone update", data->nrow);
+        return;
+    }
+
+    if (data->nrow == 0)
+        reset_zones();
+    else {
+        z = zones;
+
+        for (i = 0; i < data->nrow; i++) {
+            mrp_free((char *)z->name);
+
+            v           = data->rows[i];
+            z->name     = mrp_strdup(v[0].str);
+            z->occupied = v[1].s32;
+            z->active   = v[2].s32;
+
+            z += 1;
+        }
+    }
+
+    list_zones();
+}
+
+
+static void init_calls(void)
+{
+    mrp_clear(&calls);
+}
+
+
+static void reset_calls(void)
+{
+    int i;
+
+    for (i = 0; i < (int)MRP_ARRAY_SIZE(calls); i++) {
+        mrp_free((char *)calls[i].state);
+        mrp_free((char *)calls[i].modem);
+    }
+
+    mrp_clear(&calls);
+}
+
+
+void update_calls(mrp_domctl_data_t *data)
+{
+    call_t             *c;
+    mrp_domctl_value_t *v;
+    int                 i;
+
+    if (data->nrow != 0 && data->ncolumn != CALL_NCOLUMN) {
+        error_msg("incorrect number of columns in call update (%d != %d)",
+                  data->ncolumn, CALL_NCOLUMN);
+        return;
+    }
+
+    if (data->nrow > (int)NCALL) {
+        error_msg("too many rows (%d) in call update", data->nrow);
+        return;
+    }
+
+    if (data->nrow == 0)
+        reset_calls();
+    else {
+        c = calls;
+
+        for (i = 0; i < data->nrow; i++) {
+            mrp_free((char *)c->state);
+            mrp_free((char *)c->modem);
+
+            v        = data->rows[i];
+            c->id    = v[0].s32;
+            c->state = mrp_strdup(v[1].str);
+            c->modem = mrp_strdup(v[2].str);
+
+            c += 1;
+        }
+    }
+
+    list_calls();
+}
+
+
+void update_imports(client_t *c, mrp_domctl_data_t *data, int ntable)
+{
+    int i;
+
+    MRP_UNUSED(ntable);
+
+    for (i = 0; i < 2; i++) {
+        if (c->zone) {
+            if (data[i].id == 0)
+                update_devices(data + i);
+            else
+                update_streams(data + i);
+        }
+        else {
+            if (data[i].id == 0)
+                update_zones(data + i);
+            else
+                update_calls(data + i);
+        }
+    }
+}
+
+
+static int ping_cb(mrp_domctl_t *dc, uint32_t narg, mrp_domctl_arg_t *args,
+                   uint32_t *nout, mrp_domctl_arg_t *outs, void *user_data)
+{
+    client_t *c = (client_t *)user_data;
+    int       i;
+
+    MRP_UNUSED(dc);
+    MRP_UNUSED(c);
+
+    info_msg("pinged with %d arguments", narg);
+
+    for (i = 0; i < (int)narg; i++) {
+        switch (args[i].type) {
+        case MRP_DOMCTL_STRING:
+            info_msg("    #%d: %s", i, args[i].str);
+            break;
+        case MRP_DOMCTL_UINT32:
+            info_msg("    #%d: %u", i, args[i].u32);
+            break;
+        default:
+            if (MRP_DOMCTL_IS_ARRAY(args[i].type)) {
+                uint32_t j;
+
+                info_msg("    #%d: array of %u items:", i, args[i].size);
+                for (j = 0; j < args[i].size; j++) {
+                    switch (MRP_DOMCTL_ARRAY_TYPE(args[i].type)) {
+                    case MRP_DOMCTL_STRING:
+                        info_msg("        #%d: '%s'", j,
+                                 ((char **)args[i].arr)[j]);
+                        break;
+                    case MRP_DOMCTL_UINT32:
+                        info_msg("        #%d: %u", j,
+                                 ((uint32_t *)args[i].arr)[j]);
+                        break;
+                    default:
+                        info_msg("        #%d: <type 0x%x", j,
+                                 MRP_DOMCTL_ARRAY_TYPE(args[i].type));
+                        break;
+                    }
+                }
+            }
+            else
+                info_msg("    <type 0x%x>", args[i].type);
+        }
+    }
+
+
+    for (i = 0; i < (int)*nout; i++) {
+        if (i < (int)narg) {
+            if (MRP_DOMCTL_IS_ARRAY(args[i].type)) {
+                int j;
+
+                if (i & 0x1) {
+                    outs[i].type = MRP_DOMCTL_ARRAY(STRING);
+                    outs[i].arr  = mrp_allocz(sizeof(char *) * 5);
+                    for (j = 0; j < 5; j++) {
+                        char entry[32];
+                        snprintf(entry, sizeof(entry), "xyzzy #%d.%d", i, j);
+                        ((char **)outs[i].arr)[j] = mrp_strdup(entry);
+                    }
+                    outs[i].size = 5;
+                }
+                else {
+                    outs[i].type = MRP_DOMCTL_ARRAY(UINT32);
+                    outs[i].arr  = mrp_allocz(sizeof(uint32_t) * 5);
+                    for (j = 0; j < 5; j++)
+                        ((uint32_t*)outs[i].arr)[j] = 3141 + i * j;
+                    outs[i].size = 5;
+                }
+            }
+            else {
+                outs[i] = args[i];
+
+                if (outs[i].type == MRP_DOMCTL_STRING)
+                    outs[i].str = mrp_strdup(outs[i].str);
+            }
+        }
+        else {
+            outs[i].type = MRP_DOMCTL_UINT32;
+            outs[i].u32  = i;
+        }
+    }
+
+    return 0;
+}
+
+
+void init_methods(client_t *c)
+{
+    mrp_domctl_method_def_t methods[] = {
+        { "ping", 32, ping_cb, c },
+    };
+    int nmethod = MRP_ARRAY_SIZE(methods);
+
+    mrp_domctl_register_methods(c->dc, methods, nmethod);
+}
+
+
+static void show_help(void)
+{
+#define P info_msg
+
+    P("Available commands:");
+    P("  help                                  show this help");
+    P("  list                                  list all data");
+    P("  list {devices|streams|zones|calls}    list the requested data");
+    P("  plug <device>                         update <device> as plugged");
+    P("  unplug <device>                       update <device> as unplugged");
+    P("  play <stream>                         update <stream> as playing");
+    P("  stop <stream>                         update <stream> as stopped");
+    P("  call <call> <state>                   update state of <call>");
+    P("  zone <zone> [occupied,[active]]       update state of <zone>");
+
+#undef P
+}
+
+
+static void input_cb(brl_t *brl, const char *input, void *user_data)
+{
+    int len;
+
+    MRP_UNUSED(user_data);
+
+    brl_add_history(brl, input);
+
+    if (input == NULL || !strcmp(input, "exit")) {
+        brl_destroy(brl);
+        exit(0);
+    }
+    else if (!strcmp(input, "help")) {
+        show_help();
+    }
+    else if (!strcmp(input, "list")) {
+        list_devices();
+        list_streams();
+        list_zones();
+        list_calls();
+    }
+    else if (!strcmp(input, "list devices"))
+        list_devices();
+    else if (!strcmp(input, "list streams"))
+        list_streams();
+    else if (!strcmp(input, "list zones"))
+        list_zones();
+    else if (!strcmp(input, "list calls"))
+        list_calls();
+    else if (!strncmp(input, "plug "  , len=sizeof("plug ")   - 1) ||
+             !strncmp(input, "unplug ", len=sizeof("unplug ") - 1)) {
+        plug_device(client, input + len, *input == 'p');
+    }
+    else if (!strncmp(input, "play "  , len=sizeof("play ")   - 1) ||
+             !strncmp(input, "stop ", len=sizeof("stop ") - 1)) {
+        play_stream(client, input + len, *input == 'p');
+    }
+    else if (!strncmp(input, "call "  , len=sizeof("call ")   - 1)) {
+        set_call_state(client, input + len);
+    }
+    else if (!strncmp(input, "zone "  , len=sizeof("zone ")   - 1)) {
+        set_zone_state(client, input + len);
+    }
+}
+
+
+static void terminal_setup(client_t *c)
+{
+    int         fd;
+    const char *prompt;
+
+    fd     = fileno(stdin);
+    prompt = DEFAULT_PROMPT;
+    c->brl = brl_create_with_murphy(fd, prompt, c->ml, input_cb, c);
+
+    if (c->brl != NULL) {
+        brl_show_prompt(c->brl);
+    }
+    else {
+        mrp_log_error("Failed to breedline for console input.");
+        exit(1);
+    }
+}
+
+
+static void terminal_cleanup(client_t *c)
+{
+    if (c->brl != NULL) {
+        brl_destroy(c->brl);
+        c->brl = NULL;
+    }
+}
+
+
+static void fatal_msg(int error, const char *format, ...)
+{
+    va_list ap;
+
+    if (client && client->brl)
+        brl_hide_prompt(client->brl);
+
+    fprintf(stderr, "fatal error: ");
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+    fprintf(stderr, "\n");
+    fflush(stderr);
+
+    exit(error);
+}
+
+
+static void error_msg(const char *format, ...)
+{
+    va_list ap;
+
+    if (client && client->brl)
+        brl_hide_prompt(client->brl);
+
+    fprintf(stderr, "error: ");
+    va_start(ap, format);
+    vfprintf(stderr, format, ap);
+    va_end(ap);
+    fprintf(stderr, "\n");
+    fflush(stderr);
+
+    if (client && client->brl)
+        brl_show_prompt(client->brl);
+}
+
+
+static void info_msg(const char *format, ...)
+{
+    va_list ap;
+
+    if (client && client->brl)
+        brl_hide_prompt(client->brl);
+
+    va_start(ap, format);
+    vfprintf(stdout, format, ap);
+    va_end(ap);
+    fprintf(stdout, "\n");
+    fflush(stdout);
+
+    if (client && client->brl)
+        brl_show_prompt(client->brl);
+}
+
+
+static void signal_handler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml = mrp_get_sighandler_mainloop(h);
+
+    MRP_UNUSED(user_data);
+
+    switch (signum) {
+    case SIGINT:
+        info_msg("Got SIGINT, stopping...");
+        if (ml != NULL)
+            mrp_mainloop_quit(ml, 0);
+        else
+            exit(0);
+        break;
+    }
+}
+
+
+static void connect_notify(mrp_domctl_t *dc, int connected, int errcode,
+                           const char *errmsg, void *user_data)
+{
+    MRP_UNUSED(dc);
+    MRP_UNUSED(user_data);
+
+    if (connected) {
+        info_msg("Successfully registered to server.");
+        export_data(client);
+    }
+    else
+        error_msg("No connection to server (%d: %s).", errcode, errmsg);
+}
+
+
+static void dump_data(mrp_domctl_data_t *table)
+{
+    mrp_domctl_value_t *row;
+    int                 i, j;
+    char                buf[1024], *p;
+    const char         *t;
+    int                 n, l;
+
+    info_msg("Table #%d: %d rows x %d columns", table->id,
+             table->nrow, table->ncolumn);
+
+    for (i = 0; i < table->nrow; i++) {
+        row = table->rows[i];
+        p   = buf;
+        n   = sizeof(buf);
+
+        for (j = 0, t = ""; j < table->ncolumn; j++, t = ", ") {
+            switch (row[j].type) {
+            case MRP_DOMCTL_STRING:
+                l  = snprintf(p, n, "%s'%s'", t, row[j].str);
+                p += l;
+                n -= l;
+                break;
+            case MRP_DOMCTL_INTEGER:
+                l  = snprintf(p, n, "%s%d", t, row[j].s32);
+                p += l;
+                n -= l;
+                break;
+            case MRP_DOMCTL_UNSIGNED:
+                l  = snprintf(p, n, "%s%u", t, row[j].u32);
+                p += l;
+                n -= l;
+                break;
+            case MRP_DOMCTL_DOUBLE:
+                l  = snprintf(p, n, "%s%f", t, row[j].dbl);
+                p += l;
+                n -= l;
+                break;
+            default:
+                l  = snprintf(p, n, "%s<invalid column 0x%x>",
+                              t, row[j].type);
+                p += l;
+                n -= l;
+            }
+        }
+
+        info_msg("row #%d: { %s }", i, buf);
+    }
+}
+
+
+static void data_notify(mrp_domctl_t *dc, mrp_domctl_data_t *tables,
+                        int ntable, void *user_data)
+{
+    client_t *client = (client_t *)user_data;
+
+    MRP_UNUSED(dc);
+
+    if (client->verbose) {
+        int i;
+
+        for (i = 0; i < ntable; i++) {
+            dump_data(tables + i);
+        }
+    }
+
+    update_imports(client, tables, ntable);
+}
+
+
+static void export_notify(mrp_domctl_t *dc, int errcode, const char *errmsg,
+                          void *user_data)
+{
+    MRP_UNUSED(dc);
+    MRP_UNUSED(user_data);
+
+    if (errcode != 0) {
+        error_msg("Data set request failed (%d: %s).", errcode, errmsg);
+    }
+    else
+        info_msg("Sucessfully set data.");
+}
+
+
+static void export_data(client_t *c)
+{
+    mrp_domctl_data_t  *tables;
+    int                 ntable = 2;
+    mrp_domctl_value_t *values, *v;
+    int                 i, id;
+
+    tables = alloca(sizeof(*tables) * ntable);
+    values = alloca(sizeof(*values) * NVALUE);
+    v      = values;
+
+    if (!c->zone) {
+        id = 0;
+
+        tables[id].id      = id;
+        tables[id].ncolumn = 4;
+        tables[id].nrow    = NDEVICE;
+        tables[id].rows    = alloca(sizeof(*tables[id].rows) * tables[id].nrow);
+
+        for (i = 0; i < (int)NDEVICE; i++) {
+            tables[id].rows[i] = v;
+            v[0].type = MRP_DOMCTL_STRING ; v[0].str = devices[i].name;
+            v[1].type = MRP_DOMCTL_STRING ; v[1].str = devices[i].type;
+            v[2].type = MRP_DOMCTL_INTEGER; v[2].s32 = devices[i].public;
+            v[3].type = MRP_DOMCTL_INTEGER; v[3].s32 = devices[i].available;
+            v += 4;
+        }
+
+        id++;
+
+        tables[id].id      = id;
+        tables[id].ncolumn = 4;
+        tables[id].nrow    = NSTREAM;
+        tables[id].rows    = alloca(sizeof(*tables[id].rows) * tables[id].nrow);
+
+        for (i = 0; i < (int)NSTREAM; i++) {
+            tables[id].rows[i] = v;
+            v[0].type = MRP_DOMCTL_STRING  ; v[0].str = streams[i].name;
+            v[1].type = MRP_DOMCTL_STRING  ; v[1].str = streams[i].role;
+            v[2].type = MRP_DOMCTL_UNSIGNED; v[2].s32 = streams[i].owner;
+            v[3].type = MRP_DOMCTL_INTEGER ; v[3].u32 = streams[i].playing;
+            v += 4;
+        }
+    }
+    else {
+        id = 0;
+
+        tables[id].id      = id;
+        tables[id].ncolumn = 3;
+        tables[id].nrow    = NZONE;
+        tables[id].rows    = alloca(sizeof(*tables[id].rows) * tables[id].nrow);
+
+        for (i = 0; i < (int)NZONE; i++) {
+            tables[id].rows[i] = v;
+            v[0].type = MRP_DOMCTL_STRING ; v[0].str = zones[i].name;
+            v[1].type = MRP_DOMCTL_INTEGER; v[1].s32 = zones[i].occupied;
+            v[2].type = MRP_DOMCTL_INTEGER; v[2].s32 = zones[i].active;
+            v += 3;
+        }
+
+        id++;
+
+        tables[id].id      = id;
+        tables[id].ncolumn = 3;
+        tables[id].nrow    = NCALL;
+        tables[id].rows    = alloca(sizeof(*tables[0].rows) * tables[id].nrow);
+
+        for (i = 0; i < (int)NCALL; i++) {
+            tables[id].rows[i] = v;
+            v[0].type = MRP_DOMCTL_INTEGER; v[0].s32 = calls[i].id;
+            v[1].type = MRP_DOMCTL_STRING ; v[1].str = calls[i].state;
+            v[2].type = MRP_DOMCTL_STRING ; v[2].str = calls[i].modem;
+            v += 3;
+        }
+    }
+
+    if (!mrp_domctl_set_data(c->dc, tables, ntable, export_notify, c))
+        error_msg("Failed to send data set request to server.");
+}
+
+
+static void client_setup(client_t *c)
+{
+    mrp_mainloop_t *ml;
+    mrp_domctl_t   *dc;
+
+    ml = mrp_mainloop_create();
+
+    if (ml != NULL) {
+        if (!c->zone) {
+            exports = media_tables;
+            nexport = MRP_ARRAY_SIZE(media_tables);
+            imports = zone_watches;
+            nimport = MRP_ARRAY_SIZE(zone_watches) - (c->audio ? 0 : 2);
+        }
+        else {
+            exports = zone_tables;
+            nexport = MRP_ARRAY_SIZE(zone_tables);
+            imports = media_watches;
+            nimport = MRP_ARRAY_SIZE(media_watches) - (c->audio ? 0 : 2);
+        }
+
+        if (c->audio)
+            info_msg("Will subscribe for audio_playback_* tables.");
+
+        dc = mrp_domctl_create(c->zone ? "zone-ctrl" : "media-ctrl", ml,
+                               exports, nexport, imports, nimport,
+                               connect_notify, data_notify, c);
+
+        if (dc != NULL) {
+            c->ml = ml;
+            c->dc = dc;
+
+            mrp_add_sighandler(ml, SIGINT, signal_handler, c);
+
+            if (c->zone) {
+                zone_t *z;
+                call_t *call;
+
+                for (z = zones; z->name != NULL; z++) {
+                    z->name = mrp_strdup(z->name);
+                }
+
+                for (call = calls; call->id > 0; call++) {
+                    call->state = mrp_strdup(call->state);
+                    call->modem = mrp_strdup(call->modem);
+                }
+
+                init_devices();
+                init_streams();
+            }
+            else {
+                device_t *d;
+                stream_t *s;
+
+                for (d = devices; d->name != NULL; d++) {
+                    d->name = mrp_strdup(d->name);
+                    d->type = mrp_strdup(d->type);
+                }
+
+                for (s = streams; s->name != NULL; s++) {
+                    s->name = mrp_strdup(s->name);
+                    s->role = mrp_strdup(s->role);
+                }
+
+                init_zones();
+                init_calls();
+            }
+        }
+        else
+            fatal_msg(1, "Failed to create enforcement point.");
+    }
+    else
+        fatal_msg(1, "Failed to create mainloop.");
+
+    init_methods(c);
+}
+
+
+static void client_cleanup(client_t *c)
+{
+    if (c->zone) {
+        reset_devices();
+        reset_streams();
+    }
+    else {
+        reset_zones();
+        reset_calls();
+    }
+
+    mrp_mainloop_destroy(c->ml);
+    mrp_domctl_destroy(c->dc);
+
+    c->ml = NULL;
+    c->dc = NULL;
+}
+
+
+static void client_run(client_t *c)
+{
+    if (mrp_domctl_connect(c->dc, c->addrstr, 0))
+        info_msg("Trying to connect to server at %s...", c->addrstr);
+    else
+        error_msg("Failed to connect to server at %s.", c->addrstr);
+
+    mrp_mainloop_run(c->ml);
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -s, --server <address>     connect to murphy at given address\n"
+           "  -z, --zone                 run as zone controller\n"
+           "  -A, --audio                subscribe for audio_playback*\n"
+           "  -v, --verbose              run in verbose mode\n"
+           "  -h, --help                 show this help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void client_set_defaults(client_t *c)
+{
+    mrp_clear(c);
+    c->addrstr = MRP_DEFAULT_DOMCTL_ADDRESS;
+    c->zone    = FALSE;
+    c->verbose = FALSE;
+    c->audio   = FALSE;
+}
+
+
+int parse_cmdline(client_t *c, int argc, char **argv)
+{
+#   define OPTIONS "vAzhs:"
+    struct option options[] = {
+        { "zone"      , no_argument      , NULL, 'z' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "audio"     , no_argument      , NULL, 'A' },
+        { "server"    , required_argument, NULL, 's' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    client_set_defaults(c);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'z':
+            c->zone = TRUE;
+            break;
+
+        case 'A':
+            c->audio = TRUE;
+            c->verbose = TRUE;
+            break;
+
+        case 'v':
+            c->verbose = TRUE;
+            break;
+
+        case 's':
+            c->addrstr = optarg;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    client_t  c;
+
+    client_set_defaults(&c);
+    parse_cmdline(&c, argc, argv);
+
+    client_setup(&c);
+    terminal_setup(&c);
+
+    client = &c;
+    client_run(&c);
+
+    terminal_cleanup(&c);
+    client_cleanup(&c);
+
+    return 0;
+}
diff --git a/src/plugins/plugin-dbus.c b/src/plugins/plugin-dbus.c
new file mode 100644 (file)
index 0000000..deaf69a
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <dbus/dbus.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/plugin.h>
+
+typedef struct dbus_glue_s dbus_glue_t;
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_io_watch_t  *mw;
+    DBusWatch       *dw;
+    mrp_list_hook_t  hook;
+} watch_t;
+
+
+typedef struct {
+    dbus_glue_t     *glue;
+    mrp_timer_t     *mt;
+    DBusTimeout     *dt;
+    mrp_list_hook_t  hook;
+} timeout_t;
+
+
+struct dbus_glue_s {
+    DBusConnection  *conn;
+    mrp_mainloop_t  *ml;
+    mrp_list_hook_t  watches;
+    mrp_list_hook_t  timers;
+    mrp_deferred_t  *pump;
+};
+
+
+static dbus_int32_t data_slot = -1;
+
+static void dispatch_watch(mrp_io_watch_t *mw, int fd, mrp_io_event_t events,
+                           void *user_data)
+{
+    watch_t        *watch = (watch_t *)user_data;
+    DBusConnection *conn  = watch->glue->conn;
+    unsigned int    mask  = 0;
+
+    MRP_UNUSED(mw);
+    MRP_UNUSED(fd);
+
+    if (events & MRP_IO_EVENT_IN)
+        mask |= DBUS_WATCH_READABLE;
+    if (events & MRP_IO_EVENT_OUT)
+        mask |= DBUS_WATCH_WRITABLE;
+    if (events & MRP_IO_EVENT_HUP)
+        mask |= DBUS_WATCH_HANGUP;
+    if (events & MRP_IO_EVENT_ERR)
+        mask |= DBUS_WATCH_ERROR;
+
+    dbus_connection_ref(conn);
+    dbus_watch_handle(watch->dw, mask);
+    dbus_connection_unref(conn);
+}
+
+
+static void watch_freed_cb(void *data)
+{
+    watch_t *watch = (watch_t *)data;
+
+    if (watch != NULL) {
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+        mrp_free(watch);
+    }
+}
+
+
+static dbus_bool_t add_watch(DBusWatch *dw, void *data)
+{
+    dbus_glue_t    *glue = (dbus_glue_t *)data;
+    watch_t        *watch;
+    mrp_io_watch_t *mw;
+    mrp_io_event_t  mask;
+    int             fd;
+    unsigned int    flags;
+
+    if (!dbus_watch_get_enabled(dw))
+        return TRUE;
+
+    fd    = dbus_watch_get_unix_fd(dw);
+    flags = dbus_watch_get_flags(dw);
+    mask  = MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR;
+
+    if (flags & DBUS_WATCH_READABLE)
+        mask |= MRP_IO_EVENT_IN;
+    if (flags & DBUS_WATCH_WRITABLE)
+        mask |= MRP_IO_EVENT_OUT;
+
+    if ((watch = mrp_allocz(sizeof(*watch))) != NULL) {
+        mrp_list_init(&watch->hook);
+        mw = mrp_add_io_watch(glue->ml, fd, mask, dispatch_watch, watch);
+
+        if (mw != NULL) {
+            watch->glue = glue;
+            watch->mw   = mw;
+            watch->dw   = dw;
+            dbus_watch_set_data(dw, watch, watch_freed_cb);
+            mrp_list_append(&glue->watches, &watch->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(watch);
+    }
+
+    return FALSE;
+}
+
+
+static void del_watch(DBusWatch *dw, void *data)
+{
+    watch_t *watch = (watch_t *)dbus_watch_get_data(dw);
+
+    MRP_UNUSED(data);
+
+    if (watch != NULL) {
+        mrp_del_io_watch(watch->mw);
+        watch->mw = NULL;
+    }
+}
+
+
+static void toggle_watch(DBusWatch *dw, void *data)
+{
+    if (dbus_watch_get_enabled(dw))
+        add_watch(dw, data);
+    else
+        del_watch(dw, data);
+}
+
+
+static void dispatch_timeout(mrp_timer_t *mt, void *user_data)
+{
+    timeout_t *timer = (timeout_t *)user_data;
+
+    MRP_UNUSED(mt);
+
+    dbus_timeout_handle(timer->dt);
+}
+
+
+static void timeout_freed_cb(void *data)
+{
+    timeout_t *timer = (timeout_t *)data;
+
+    if (timer != NULL) {
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+}
+
+
+static dbus_bool_t add_timeout(DBusTimeout *dt, void *data)
+{
+    dbus_glue_t  *glue = (dbus_glue_t *)data;
+    timeout_t    *timer;
+    mrp_timer_t  *mt;
+    unsigned int  msecs;
+
+    if ((timer = mrp_allocz(sizeof(*timer))) != NULL) {
+        mrp_list_init(&timer->hook);
+        msecs = dbus_timeout_get_interval(dt);
+        mt    = mrp_add_timer(glue->ml, msecs, dispatch_timeout, timer);
+
+        if (mt != NULL) {
+            timer->glue = glue;
+            timer->mt   = mt;
+            timer->dt   = dt;
+            dbus_timeout_set_data(dt, timer, timeout_freed_cb);
+            mrp_list_append(&glue->timers, &timer->hook);
+
+            return TRUE;
+        }
+        else
+            mrp_free(timer);
+    }
+
+    return FALSE;
+}
+
+
+static void del_timeout(DBusTimeout *dt, void *data)
+{
+    timeout_t *timer = (timeout_t *)dbus_timeout_get_data(dt);
+
+    MRP_UNUSED(data);
+
+    if (timer != NULL) {
+        mrp_del_timer(timer->mt);
+        timer->mt = NULL;
+    }
+}
+
+
+static void toggle_timeout(DBusTimeout *dt, void *data)
+{
+    if (dbus_timeout_get_enabled(dt))
+        add_timeout(dt, data);
+    else
+        del_timeout(dt, data);
+}
+
+
+static void wakeup_mainloop(void *data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)data;
+
+    mrp_enable_deferred(glue->pump);
+}
+
+
+static void glue_free_cb(void *data)
+{
+    dbus_glue_t     *glue = (dbus_glue_t *)data;
+    mrp_list_hook_t *p, *n;
+    watch_t         *watch;
+    timeout_t       *timer;
+
+    mrp_list_foreach(&glue->watches, p, n) {
+        watch = mrp_list_entry(p, typeof(*watch), hook);
+
+        mrp_list_delete(&watch->hook);
+        mrp_del_io_watch(watch->mw);
+
+        mrp_free(watch);
+    }
+
+    mrp_list_foreach(&glue->timers, p, n) {
+        timer = mrp_list_entry(p, typeof(*timer), hook);
+
+        mrp_list_delete(&timer->hook);
+        mrp_del_timer(timer->mt);
+
+        mrp_free(timer);
+    }
+
+    mrp_free(glue);
+}
+
+
+static void pump_cb(mrp_deferred_t *d, void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    if (dbus_connection_dispatch(glue->conn) == DBUS_DISPATCH_COMPLETE)
+        mrp_disable_deferred(d);
+}
+
+
+static void dispatch_status_cb(DBusConnection *conn, DBusDispatchStatus status,
+                               void *user_data)
+{
+    dbus_glue_t *glue = (dbus_glue_t *)user_data;
+
+    MRP_UNUSED(conn);
+
+    switch (status) {
+    case DBUS_DISPATCH_COMPLETE:
+        mrp_disable_deferred(glue->pump);
+        break;
+
+    case DBUS_DISPATCH_DATA_REMAINS:
+    case DBUS_DISPATCH_NEED_MEMORY:
+    default:
+        mrp_enable_deferred(glue->pump);
+        break;
+    }
+}
+
+
+int mrp_setup_dbus_connection(mrp_mainloop_t *ml, DBusConnection *conn)
+{
+    dbus_glue_t *glue;
+
+    if (!dbus_connection_allocate_data_slot(&data_slot))
+        return FALSE;
+
+    if (dbus_connection_get_data(conn, data_slot) != NULL)
+        return FALSE;
+
+    if ((glue = mrp_allocz(sizeof(*glue))) != NULL) {
+        mrp_list_init(&glue->watches);
+        mrp_list_init(&glue->timers);
+        glue->pump = mrp_add_deferred(ml, pump_cb, glue);
+
+        if (glue->pump == NULL) {
+            mrp_free(glue);
+            return FALSE;
+        }
+
+        glue->ml   = ml;
+        glue->conn = conn;
+    }
+    else
+        return FALSE;
+
+    if (!dbus_connection_set_data(conn, data_slot, glue, glue_free_cb))
+        return FALSE;
+
+    dbus_connection_set_dispatch_status_function(conn, dispatch_status_cb,
+                                                 glue, NULL);
+
+    dbus_connection_set_wakeup_main_function(conn, wakeup_mainloop,
+                                             glue, NULL);
+
+    return
+        dbus_connection_set_watch_functions(conn, add_watch, del_watch,
+                                            toggle_watch, glue, NULL) &&
+            dbus_connection_set_timeout_functions(conn, add_timeout, del_timeout,
+                                              toggle_timeout, glue, NULL);
+}
+
+
+
+static int dbus_init(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    mrp_log_info("%s() called...", __FUNCTION__);
+
+    return TRUE;
+}
+
+
+static void dbus_exit(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    mrp_log_info("%s() called...", __FUNCTION__);
+}
+
+
+#define DBPLG_DESCRIPTION "A plugin to pump DBusConnections."
+#define DBPLG_HELP "DBUS pump plugin (DBUS-mainloop integration)."
+#define DBPLG_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define DBPLG_AUTHORS     "Krisztian Litkey <krisztian.litkey@intel.com>"
+
+MURPHY_REGISTER_CORE_PLUGIN("dbus",
+                            DBPLG_VERSION, DBPLG_DESCRIPTION, DBPLG_AUTHORS,
+                            DBPLG_HELP, MRP_SINGLETON,
+                            dbus_init, dbus_exit,
+                            NULL, 0, NULL, 0, NULL, 0, NULL);
diff --git a/src/plugins/plugin-glib.c b/src/plugins/plugin-glib.c
new file mode 100644 (file)
index 0000000..d87d6cf
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <glib.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/plugin.h>
+
+
+/*
+ * A simple glue layer to pump GMainLoop from mrp_mainloop_t. This
+ * will pretty much be turned into a murphy plugin as such...
+ */
+
+
+typedef struct {
+    GMainLoop     *ml;
+    GMainContext  *mc;
+    gint           maxprio;
+    mrp_subloop_t *sl;
+} glib_glue_t;
+
+static glib_glue_t *glib_glue;
+
+
+static int glib_prepare(void *user_data)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    return g_main_context_prepare(glue->mc, &glue->maxprio);
+}
+
+
+static int glib_query(void *user_data, struct pollfd *fds, int nfd,
+                      int *timeout)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    return g_main_context_query(glue->mc, glue->maxprio, timeout,
+                                (GPollFD *)fds, nfd);
+}
+
+
+static int glib_check(void *user_data, struct pollfd *fds, int nfd)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    return g_main_context_check(glue->mc, glue->maxprio, (GPollFD *)fds, nfd);
+
+}
+
+
+static void glib_dispatch(void *user_data)
+{
+    glib_glue_t *glue = (glib_glue_t *)user_data;
+
+    g_main_context_dispatch(glue->mc);
+
+}
+
+
+static int glib_pump_setup(mrp_mainloop_t *ml)
+{
+    static mrp_subloop_ops_t glib_ops = {
+        .prepare  = glib_prepare,
+        .query    = glib_query,
+        .check    = glib_check,
+        .dispatch = glib_dispatch
+    };
+
+    GMainContext *main_context;
+    GMainLoop    *main_loop;
+
+    if (sizeof(GPollFD) != sizeof(struct pollfd)) {
+        mrp_log_error("sizeof(GPollFD:%zd) != sizeof(struct pollfd:%zd)\n",
+                      sizeof(GPollFD), sizeof(struct pollfd));
+        return FALSE;
+    }
+
+    main_context = NULL;
+    main_loop    = NULL;
+    glib_glue    = NULL;
+
+    if ((main_context = g_main_context_default())             != NULL &&
+        (main_loop    = g_main_loop_new(main_context, FALSE)) != NULL &&
+        (glib_glue    = mrp_allocz(sizeof(*glib_glue)))       != NULL) {
+
+        glib_glue->mc = main_context;
+        glib_glue->ml = main_loop;
+        glib_glue->sl = mrp_add_subloop(ml, &glib_ops, glib_glue);
+
+        if (glib_glue->sl != NULL)
+            return TRUE;
+        else
+            mrp_log_error("glib-pump failed to register subloop.");
+    }
+
+    /* all of these handle a NULL argument gracefully... */
+    g_main_loop_unref(main_loop);
+    g_main_context_unref(main_context);
+
+    mrp_free(glib_glue);
+    glib_glue = NULL;
+
+    return FALSE;
+}
+
+
+static void glib_pump_cleanup(void)
+{
+    if (glib_glue != NULL) {
+        mrp_del_subloop(glib_glue->sl);
+
+        g_main_loop_unref(glib_glue->ml);
+        g_main_context_unref(glib_glue->mc);
+
+        mrp_free(glib_glue);
+        glib_glue = NULL;
+    }
+}
+
+
+
+static int plugin_init(mrp_plugin_t *plugin)
+{
+    mrp_log_info("%s() called...", __FUNCTION__);
+
+    return glib_pump_setup(plugin->ctx->ml);
+}
+
+
+static void plugin_exit(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    mrp_log_info("%s() called...", __FUNCTION__);
+
+    glib_pump_cleanup();
+}
+
+
+#define GLIB_DESCRIPTION "Glib mainloop pump plugin."
+#define GLIB_HELP        "Glib pump plugin (GMainLoop integration)."
+#define GLIB_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define GLIB_AUTHORS     "Krisztian Litkey <krisztian.litkey@intel.com>"
+
+MURPHY_REGISTER_PLUGIN("glib", GLIB_VERSION, GLIB_DESCRIPTION, GLIB_AUTHORS,
+                       GLIB_HELP, MRP_SINGLETON,
+                       plugin_init, plugin_exit,
+                       NULL, 0, NULL, 0, NULL, 0, NULL);
+
diff --git a/src/plugins/plugin-lua.c b/src/plugins/plugin-lua.c
new file mode 100644 (file)
index 0000000..9674498
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#define LUAR_INTERPRETER_NAME "lua"
+
+
+enum {
+    ARG_CONFIG,                          /* configuration file */
+    ARG_RESOLVER,                        /* enable resolver lua support */
+};
+
+
+static int load_config(lua_State *L, const char *path)
+{
+    int success;
+
+    if (!luaL_loadfile(L, path) && !lua_pcall(L, 0, 0, 0))
+        success = TRUE;
+    else {
+        mrp_log_error("plugin-lua: failed to load config file %s.", path);
+        mrp_log_error("%s", lua_tostring(L, -1));
+        lua_settop(L, 0);
+
+        success = FALSE;
+    }
+
+    return success;
+}
+
+
+static int luaR_compile(mrp_scriptlet_t *script)
+{
+    mrp_interpreter_t *i    = script->interpreter;
+    lua_State         *L    = i->data;
+    const char        *code = script->source;
+    int                len  = strlen(code);
+    int                status;
+
+    if (!luaL_loadbuffer(L, code, len, "<resolver Lua scriptlet>")) {
+        script->data = (void *)(ptrdiff_t)luaL_ref(L, LUA_REGISTRYINDEX);
+        status = 0;
+    }
+    else {
+        mrp_log_error("plugin-lua: failed to compile scriptlet.");
+        mrp_log_error("%s", lua_tostring(L, -1));
+        status = -EINVAL;
+    }
+
+    lua_settop(L, 0);
+
+    return status;
+}
+
+
+static int luaR_prepare(mrp_scriptlet_t *script)
+{
+    MRP_UNUSED(script);
+
+    return 0;
+}
+
+
+static int luaR_execute(mrp_scriptlet_t *script, mrp_context_tbl_t *ctbl)
+{
+    mrp_interpreter_t *i   = script->interpreter;
+    lua_State         *L   = i->data;
+    int                ref = (ptrdiff_t)script->data;
+    int                top, success;
+
+    MRP_UNUSED(ctbl);
+
+    success = FALSE;
+    top = lua_gettop(L);
+
+    lua_rawgeti(L, LUA_REGISTRYINDEX, ref);
+
+    if (lua_isfunction(L, -1)) {
+        if (!lua_pcall(L, 0, 0, 0))
+            success = TRUE;
+    }
+    else {
+        mrp_log_error("plugin-lua: failed to execute scriptlet.");
+        mrp_log_error("error: %s", lua_tostring(L, -1));
+    }
+
+    lua_settop(L, top);
+
+    return success;
+}
+
+
+static void luaR_cleanup(mrp_scriptlet_t *script)
+{
+    mrp_interpreter_t *i   = script->interpreter;
+    lua_State         *L   = i->data;
+    int                ref = (ptrdiff_t)script->data;
+
+    luaL_unref(L, LUA_REGISTRYINDEX, ref);
+}
+
+static int plugin_init(mrp_plugin_t *plugin)
+{
+    static mrp_interpreter_t interpreter = {
+        .name    = LUAR_INTERPRETER_NAME,
+        .compile = luaR_compile,
+        .prepare = luaR_prepare,
+        .execute = luaR_execute,
+        .cleanup = luaR_cleanup,
+        .data    = NULL
+    };
+    mrp_plugin_arg_t *args = plugin->args;
+    const char       *cfg  = args[ARG_CONFIG].str;
+    int               res  = args[ARG_RESOLVER].bln;
+    lua_State        *L;
+
+    L = mrp_lua_set_murphy_context(plugin->ctx);
+
+    if (L != NULL) {
+        if (res) {
+            interpreter.data = L;
+
+            if (!mrp_register_interpreter(&interpreter)) {
+                mrp_log_error("plugin-lua: failed to register interpreter.");
+
+                return FALSE;
+            }
+        }
+        else
+            mrp_log_info("plugin-lua: resolver Lua support disabled.");
+
+        mrp_lua_set_murphy_lua_config_file(cfg);
+
+        if (load_config(L, cfg))
+            return TRUE;
+        else
+            if (res)
+                mrp_unregister_interpreter(LUAR_INTERPRETER_NAME);
+    }
+
+    return FALSE;
+}
+
+
+static void plugin_exit(mrp_plugin_t *plugin)
+{
+    mrp_plugin_arg_t *args = plugin->args;
+
+    if (args[ARG_RESOLVER].bln)
+        mrp_unregister_interpreter(LUAR_INTERPRETER_NAME);
+}
+
+
+#define PLUGIN_DESCRIPTION "Lua bindings for Murphy."
+#define PLUGIN_HELP        "Enable Lua bindings for Murphy."
+#define PLUGIN_AUTHORS     "Krisztian Litkey <kli@iki.fi>"
+#define PLUGIN_VERSION     MRP_VERSION_INT(0, 0, 1)
+
+#define DEFAULT_CONFIG  "/etc/murphy/murphy.lua"
+
+static mrp_plugin_arg_t plugin_args[] = {
+    MRP_PLUGIN_ARGIDX(ARG_CONFIG  , STRING,  "config",  DEFAULT_CONFIG),
+    MRP_PLUGIN_ARGIDX(ARG_RESOLVER, BOOL  , "resolver",TRUE),
+};
+
+MURPHY_REGISTER_PLUGIN("lua",
+                       PLUGIN_VERSION, PLUGIN_DESCRIPTION, PLUGIN_AUTHORS,
+                       PLUGIN_HELP, MRP_SINGLETON, plugin_init, plugin_exit,
+                       plugin_args, MRP_ARRAY_SIZE(plugin_args),
+                       NULL, 0,
+                       NULL, 0,
+                       NULL);
diff --git a/src/plugins/plugin-resource-dbus.c b/src/plugins/plugin-resource-dbus.c
new file mode 100644 (file)
index 0000000..2f23141
--- /dev/null
@@ -0,0 +1,2345 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <signal.h>
+
+#include <murphy/common.h>
+#include <murphy/core.h>
+#include <murphy/common/dbus-libdbus.h>
+
+#include <murphy/resource/client-api.h>
+
+
+#define MURPHY_PATH_BASE "/org/murphy/resource"
+
+#define MANAGER_IFACE "org.murphy.manager"
+#define RSET_IFACE "org.murphy.resourceset"
+#define RESOURCE_IFACE "org.murphy.resource"
+
+#define MAX_PATH_LENGTH 64
+#define MAX_DBUS_SIG_LENGTH 8
+
+
+#define MANAGER_CREATE_RESOURCE_SET "createResourceSet"
+#define MANAGER_GET_PROPERTIES      "getProperties"
+
+#define RSET_SET_PROPERTY           "setProperty"
+#define RSET_GET_PROPERTIES         "getProperties"
+#define RSET_ADD_RESOURCE           "addResource"
+#define RSET_REQUEST                "request"
+#define RSET_RELEASE                "release"
+#define RSET_DELETE                 "delete"
+
+#define RESOURCE_SET_PROPERTY       "setProperty"
+#define RESOURCE_GET_PROPERTIES     "getProperties"
+#define RESOURCE_DELETE             "delete"
+
+#define PROP_RESOURCE_SETS          "resourceSets"
+#define PROP_AVAILABLE_CLASSES      "availableClasses"
+#define PROP_AVAILABLE_RESOURCES    "availableResources"
+#define PROP_NAME                   "name"
+#define PROP_SHARED                 "shared"
+#define PROP_MANDATORY              "mandatory"
+#define PROP_CLASS                  "class"
+#define PROP_RESOURCES              "resources"
+#define PROP_STATUS                 "status"
+#define PROP_ATTRIBUTES             "attributes"
+#define PROP_ATTRIBUTES_CONF        "attributes_conf"
+
+#define SIG_PROPERTYCHANGED         "propertyChanged"
+
+enum {
+    ARG_DR_BUS,
+    ARG_DR_SERVICE,
+    ARG_DR_TRACK_CLIENTS,
+    ARG_DR_DEFAULT_ZONE,
+    ARG_DR_DEFAULT_CLASS,
+};
+
+typedef struct manager_o_s manager_o_t;
+
+typedef struct {
+    /* configuration */
+    mrp_dbus_t *dbus;
+    const char *addr;
+    const char *bus;
+    const char *default_zone;
+    const char *default_class;
+
+    bool tracking;
+
+    int has_classes;
+
+    /* resource management */
+    manager_o_t *mgr;
+
+    /* murphy integration */
+    mrp_mainloop_t *ml;
+} dbus_data_t;
+
+typedef struct property_o_s {
+    /* dbus properties */
+    char *path;
+    char *interface;
+    char *dbus_sig;
+
+    /* data */
+    char *name;
+    void *value;
+    bool writable; /* used later when we allow more access to properties */
+
+    dbus_data_t *ctx;
+
+    /* function to free the value */
+    void (*free_data)(void *data);
+
+    /* may be needed in the future? maybe not in this form */
+    int (*compare)(struct property_o_s *a, struct property_o_s *b);
+} property_o_t;
+
+struct manager_o_s {
+    uint32_t next_id; /* next resource set id */
+
+    dbus_data_t *ctx;
+    mrp_htbl_t *rsets;
+
+    property_o_t *rsets_prop;
+    property_o_t *available_classes_prop;
+
+    /* resource library */
+    const char *zone;
+    mrp_resource_client_t *client;
+};
+
+typedef struct {
+    uint32_t next_id; /* next resource id */
+    char *path;
+    char *owner;
+
+    manager_o_t *mgr; /* backpointer */
+
+    mrp_htbl_t *resources;
+
+    property_o_t *resources_prop;
+    property_o_t *available_resources_prop;
+    property_o_t *class_prop;
+    property_o_t *status_prop;
+
+    /* resource library */
+    bool locked; /* if the library allows the settings to be changed */
+    bool committed; /* set to true when we are committing the resource set */
+    mrp_resource_set_t *set;
+
+    /* pending properties for events that have been received in wrong order */
+    bool update_needed;
+    mrp_resource_mask_t pending_grant;
+    mrp_resource_mask_t pending_advice;
+
+    /* whether we have encountered an error in the library calls */
+    bool error;
+} resource_set_o_t;
+
+typedef struct {
+    char *path;
+
+    resource_set_o_t *rset; /* backpointer */
+
+    property_o_t *status_prop;
+    property_o_t *mandatory_prop;
+    property_o_t *shared_prop;
+    property_o_t *name_prop;
+    property_o_t *arguments_prop;
+    property_o_t *conf_prop;
+} resource_o_t;
+
+static int mgr_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data);
+static int rset_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data);
+static int resource_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data);
+static void dbus_name_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data);
+
+
+/* copy the keys in a hash map to a NULL-terminated array */
+
+struct key_data_s {
+    int curr_key;
+    char **keys;
+};
+
+struct deferred_rset_data_s {
+    char *rset_path;
+    manager_o_t *mgr;
+};
+
+static int copy_keys_cb(void *key, void *object, void *user_data)
+{
+    struct key_data_s *kd = user_data;
+
+    MRP_UNUSED(object);
+
+    kd->keys[kd->curr_key] = mrp_strdup((char *) key);
+    kd->curr_key++;
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static int count_keys_cb(void *key, void *object, void *user_data)
+{
+    int *count = user_data;
+
+    MRP_UNUSED(key);
+    MRP_UNUSED(object);
+
+    *count = *count + 1;
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static char **htbl_keys(mrp_htbl_t *ht)
+{
+    char **keys;
+    int len = 0;
+    struct key_data_s kd;
+
+    if (!ht)
+        return NULL;
+
+    mrp_htbl_foreach(ht, count_keys_cb, &len);
+
+    keys = mrp_alloc_array(char *, len+1);
+
+    kd.curr_key = 0;
+    kd.keys = keys;
+
+    mrp_htbl_foreach(ht, copy_keys_cb, &kd);
+
+    keys[len] = NULL;
+
+    return keys;
+}
+
+/* functions for freeing property values */
+
+static void free_value(void *val) {
+    mrp_free(val);
+}
+
+
+static void free_string_array(void *array) {
+
+    char **i = array;
+
+    if (!array)
+        return;
+
+    while (*i) {
+        mrp_free(*i);
+        i++;
+    }
+
+    mrp_free(array);
+}
+
+
+static void free_attr_array(mrp_attr_t *arr)
+{
+    /* only free the allocated members */
+    mrp_attr_t *i = arr;
+
+    while (i->name) {
+
+        if (i->type == mqi_string)
+            mrp_free((void *) i->value.string);
+
+        mrp_free((void *) i->name);
+        i++;
+    }
+}
+
+
+static char **copy_string_array(const char **array)
+{
+    int count = 0, i;
+    char **tmp = (char **) array;
+    char **ret;
+
+    if (!array)
+        return NULL;
+
+    while (*tmp) {
+        count++;
+        tmp++;
+    }
+
+    ret = mrp_alloc_array(char *, count+1);
+
+    if (!ret)
+        return NULL;
+
+    for (i = 0; i < count; i++) {
+        ret[i] = mrp_strdup(array[i]);
+        if (!ret[i]) {
+            free_string_array(ret);
+            return NULL;
+        }
+    }
+
+    ret[i] = NULL;
+
+    return ret;
+}
+
+static const char *get_dbus_type(mrp_attr_t *v)
+{
+    switch(v->type) {
+        case mqi_string:
+            return MRP_DBUS_TYPE_STRING_AS_STRING;
+        case mqi_integer:
+            return MRP_DBUS_TYPE_INT32_AS_STRING;
+        case mqi_unsignd:
+            return MRP_DBUS_TYPE_UINT32_AS_STRING;
+        case mqi_floating:
+            return MRP_DBUS_TYPE_DOUBLE_AS_STRING;
+        default:
+            goto end;
+    }
+
+end:
+    return NULL;
+}
+
+static int dbus_value_cb(void *key, void *object, void *user_data)
+{
+    mrp_dbus_msg_t *reply = user_data;
+    char *arg_name = key;
+    mrp_attr_t *arg_value = object;
+    const char *sig = get_dbus_type(arg_value);
+    char dsig[3];
+    int ret;
+
+    if (!sig) {
+        mrp_log_error("unknown database type");
+        goto end;
+    }
+
+    ret = snprintf(dsig, sizeof(dsig), "%s%s", sig, MRP_DBUS_TYPE_VARIANT_AS_STRING);
+
+    if (ret < 0 || ret == sizeof(dsig)) {
+        mrp_log_error("invalid signature");
+        goto end;
+    }
+
+    if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_DICT_ENTRY, NULL)) {
+        mrp_log_error("failed to open dict container with sig '%s'", dsig);
+        goto end;
+    }
+
+    if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_STRING, arg_name)) {
+        mrp_log_error("failed to append argument name '%s'", arg_name);
+        goto end_close_dict;
+    }
+
+    switch(arg_value->type) {
+        case mqi_string:
+            if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_VARIANT, sig))
+                goto end_close_dict;
+            if (!mrp_dbus_msg_append_basic(reply, sig[0],
+                    (char *) arg_value->value.string))
+                goto end_close_variant;
+            break;
+        case mqi_integer:
+            if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_VARIANT, sig))
+                goto end_close_dict;
+            if (!mrp_dbus_msg_append_basic(reply, sig[0],
+                    &arg_value->value.integer))
+                goto end_close_variant;
+            break;
+        case mqi_unsignd:
+            if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_VARIANT, sig))
+                goto end_close_dict;
+            if (!mrp_dbus_msg_append_basic(reply, sig[0],
+                    &arg_value->value.unsignd))
+                goto end_close_variant;
+            break;
+        case mqi_floating:
+            if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_VARIANT, sig))
+                goto end_close_dict;
+            if (!mrp_dbus_msg_append_basic(reply, sig[0],
+                    &arg_value->value.floating))
+                goto end_close_variant;
+            break;
+        default:
+            mrp_log_error("unknown type %d in attributes", arg_value->type);
+            break;
+    }
+
+end_close_variant:
+    mrp_dbus_msg_close_container(reply); /* variant container */
+end_close_dict:
+    mrp_dbus_msg_close_container(reply); /* dict container */
+end:
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static bool get_property_entry(property_o_t *prop, mrp_dbus_msg_t *reply)
+{
+    /* FIXME: check return values */
+    if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_STRING, prop->name))
+        goto error;
+
+    if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_VARIANT, prop->dbus_sig))
+        goto error;
+
+    /* TODO: this might be remade to be generic? */
+
+    if (strcmp(prop->dbus_sig, "s") == 0) {
+        if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_STRING, prop->value))
+            goto error_close_variant;
+    }
+    else if (strcmp(prop->dbus_sig, "b") == 0) {
+        bool value = *(bool *) prop->value;
+        uint32_t v = value;
+        mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_BOOLEAN, &v);
+    }
+    else if (strcmp(prop->dbus_sig, "as") == 0) {
+        char **i = prop->value;
+        if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_ARRAY, "s"))
+            goto error_close_variant;
+
+        while (*i) {
+            if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_STRING, *i)) {
+                mrp_dbus_msg_close_container(reply); /* array */
+                goto error_close_variant;
+            }
+            i++;
+        }
+        mrp_dbus_msg_close_container(reply); /* array */
+    }
+    else if (strcmp(prop->dbus_sig, "ao") == 0) {
+        char **i = prop->value;
+        if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_ARRAY, "o"))
+            goto error_close_variant;
+
+        while (*i) {
+            if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_OBJECT_PATH, *i)) {
+                mrp_dbus_msg_close_container(reply); /* array */
+                goto error_close_variant;
+            }
+            i++;
+        }
+        mrp_dbus_msg_close_container(reply); /* array */
+    }
+    else if (strcmp(prop->dbus_sig, "a{sv}") == 0) {
+        if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_ARRAY, "{sv}"))
+            goto error_close_variant;
+
+        /* iterate through the elements in the map */
+        mrp_htbl_foreach(prop->value, dbus_value_cb, reply);
+
+        mrp_dbus_msg_close_container(reply); /* array */
+    }
+    else {
+        mrp_log_error("Unknown sig '%s'", prop->dbus_sig);
+        goto error_close_variant;
+    }
+
+    mrp_dbus_msg_close_container(reply); /* variant */
+
+    return TRUE;
+
+error_close_variant:
+    mrp_dbus_msg_close_container(reply); /* variant */
+error:
+    return FALSE;
+}
+
+
+static bool get_property_dict_entry(property_o_t *prop, mrp_dbus_msg_t *reply)
+{
+    bool ret;
+
+    if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_DICT_ENTRY, NULL))
+        return FALSE;
+
+    ret = get_property_entry(prop, reply);
+
+    mrp_dbus_msg_close_container(reply);
+
+    return ret;
+}
+
+
+static void trigger_property_changed_signal(dbus_data_t *ctx,
+        property_o_t *prop)
+{
+    mrp_dbus_msg_t *sig;
+
+    if (!prop)
+        return;
+
+    mrp_log_info("propertyChanged signal (%s)", prop->name);
+
+    sig = mrp_dbus_msg_signal(ctx->dbus, NULL, prop->path,
+                    prop->interface, SIG_PROPERTYCHANGED);
+
+    if (!sig)
+        return;
+
+    get_property_entry(prop, sig);
+
+    mrp_dbus_send_msg(ctx->dbus, sig);
+    mrp_dbus_msg_unref(sig);
+}
+
+
+static void destroy_property(property_o_t *prop)
+{
+    if (!prop)
+        return;
+
+    mrp_free(prop->dbus_sig);
+    mrp_free(prop->interface);
+    mrp_free(prop->path);
+    mrp_free(prop->name);
+
+    if (prop->free_data)
+        prop->free_data(prop->value);
+
+    mrp_free(prop);
+}
+
+
+static property_o_t *create_property(dbus_data_t *ctx, char *path,
+        const char *interface, const char *sig, const char *name, void *value,
+        void (*free_data)(void *data))
+{
+    property_o_t *prop = mrp_allocz(sizeof(property_o_t));
+
+    if (!prop)
+        goto error;
+
+    prop->dbus_sig = mrp_strdup(sig);
+    prop->interface = mrp_strdup(interface);
+    prop->path = mrp_strdup(path);
+    prop->name = mrp_strdup(name);
+    prop->writable = FALSE;
+    prop->value = value;
+
+    prop->ctx = ctx;
+
+    prop->free_data = free_data;
+
+    if (!prop->dbus_sig || !prop->name || !prop->value)
+        goto error;
+
+    trigger_property_changed_signal(ctx, prop);
+
+    return prop;
+
+error:
+    if (prop) {
+        destroy_property(prop);
+    }
+    else {
+        if (free_data)
+            free_data(value);
+    }
+
+    return NULL;
+}
+
+
+static void update_property(property_o_t *prop, void *value)
+{
+    /* the value is of the same type so we'll use the same function for
+     * freeing it */
+
+    if (prop->free_data)
+        prop->free_data(prop->value);
+
+    prop->value = value;
+
+    trigger_property_changed_signal(prop->ctx, prop);
+}
+
+
+static void destroy_resource(resource_o_t *resource)
+{
+    if (!resource)
+        return;
+
+    mrp_log_info("destroy resource %s", resource->path);
+
+    mrp_dbus_remove_method(resource->rset->mgr->ctx->dbus, resource->path,
+            RESOURCE_IFACE, RESOURCE_GET_PROPERTIES, resource_cb,
+            resource->rset->mgr->ctx);
+    mrp_dbus_remove_method(resource->rset->mgr->ctx->dbus, resource->path,
+            RESOURCE_IFACE, RESOURCE_SET_PROPERTY, resource_cb,
+            resource->rset->mgr->ctx);
+    mrp_dbus_remove_method(resource->rset->mgr->ctx->dbus, resource->path,
+            RESOURCE_IFACE, RESOURCE_DELETE, resource_cb,
+            resource->rset->mgr->ctx);
+
+    destroy_property(resource->mandatory_prop);
+    destroy_property(resource->shared_prop);
+    destroy_property(resource->name_prop);
+    destroy_property(resource->status_prop);
+    destroy_property(resource->arguments_prop);
+    destroy_property(resource->conf_prop);
+
+    /* FIXME: resource library doesn't allow destroying resources? */
+
+    mrp_free(resource->path);
+
+    mrp_free(resource);
+}
+
+
+struct search_data_s {
+    const char *name;
+    resource_o_t *resource;
+};
+
+
+static int find_resource_cb(void *key, void *object, void *user_data)
+{
+    resource_o_t *r = object;
+    struct search_data_s *s = user_data;
+    MRP_UNUSED(key);
+
+    if (strcmp(r->name_prop->value, s->name) == 0) {
+        s->resource = r;
+        return MRP_HTBL_ITER_STOP;
+    }
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static resource_o_t *get_resource_by_name(resource_set_o_t *rset,
+        const char *name)
+{
+    struct search_data_s s;
+
+    s.name = name;
+    s.resource = NULL;
+
+    mrp_htbl_foreach(rset->resources, find_resource_cb, &s);
+
+    return s.resource;
+}
+
+static void update_resources(resource_set_o_t *rset, mrp_resource_mask_t grant,
+        mrp_resource_mask_t advice)
+{
+    mrp_resource_t *resource;
+    void *iter = NULL;
+
+    if (!rset->set || !rset->committed) {
+        mrp_log_error("resource-dbus: update_resources with invalid rset");
+        return;
+    }
+
+    if (rset->update_needed) {
+        /* process pending events first */
+        rset->update_needed = FALSE;
+        update_resources(rset, rset->pending_grant, rset->pending_advice);
+    }
+
+    /* the resource API is "bit" awkward here */
+
+    while ((resource = mrp_resource_set_iterate_resources(rset->set, &iter))) {
+        mrp_resource_mask_t mask;
+        const char *name;
+        resource_o_t *res;
+
+        mask = mrp_resource_get_mask(resource);
+        name = mrp_resource_get_name(resource);
+
+        /* search the matching resource set object */
+
+        res = get_resource_by_name(rset, name);
+
+        if (!res) {
+            mrp_log_error("Resource %s not found", name);
+            continue;
+        }
+
+        if (mask & grant) {
+            update_property(res->status_prop, "acquired");
+        }
+        else if (mask & advice) {
+            update_property(res->status_prop, "available");
+        }
+        else {
+            update_property(res->status_prop, "lost");
+        }
+    }
+
+    if (grant) {
+        update_property(rset->status_prop, "acquired");
+    }
+    else if (advice) {
+        update_property(rset->status_prop, "available");
+    }
+    else {
+        update_property(rset->status_prop, "lost");
+    }
+}
+
+static void update_later_cb(mrp_deferred_t *d, void *data)
+{
+    struct deferred_rset_data_s *r_data = (struct deferred_rset_data_s *) data;
+    manager_o_t *mgr = r_data->mgr;
+
+    resource_set_o_t *rset = mrp_htbl_lookup(mgr->rsets, r_data->rset_path);
+
+    if (rset && rset->update_needed)
+        update_resources(rset, rset->pending_grant, rset->pending_advice);
+
+    mrp_free(r_data->rset_path);
+    mrp_free(r_data);
+
+    mrp_del_deferred(d);
+}
+
+static void event_cb(uint32_t request_id, mrp_resource_set_t *set, void *data)
+{
+    resource_set_o_t *rset = data;
+
+    mrp_resource_mask_t grant = mrp_get_resource_set_grant(set);
+    mrp_resource_mask_t advice = mrp_get_resource_set_advice(set);
+
+    MRP_UNUSED(request_id);
+
+    mrp_log_info("Event for %s: grant 0x%08x, advice 0x%08x",
+        rset->path, grant, advice);
+
+    if (!rset->set || !rset->committed) {
+
+        struct deferred_rset_data_s *r_data =
+                mrp_allocz(sizeof(struct deferred_rset_data_s));
+
+        if (!r_data) {
+            return;
+        }
+
+        r_data->mgr = rset->mgr;
+        r_data->rset_path = mrp_strdup(rset->path);
+
+        if (!r_data->rset_path) {
+            mrp_free(r_data);
+            return;
+        }
+
+        /* We haven't yet returned from the create_set call, and this is before
+         * acquiring the set, or we haven't started the acquitision yet. Filter
+         * out! */
+
+        mrp_log_info("Filtering out the event, trying again soon");
+
+        rset->update_needed = TRUE;
+        rset->pending_grant = grant;
+        rset->pending_advice = advice;
+
+        mrp_add_deferred(rset->mgr->ctx->ml, update_later_cb, r_data);
+
+        return;
+    }
+
+    update_resources(rset, grant, advice);
+}
+
+
+static void htbl_free_resources(void *key, void *object)
+{
+    resource_o_t *resource = object;
+
+    MRP_UNUSED(key);
+
+    destroy_resource(resource);
+}
+
+
+static void htbl_free_args(void *key, void *object)
+{
+    mrp_attr_t *attr = object;
+
+    MRP_UNUSED(key);
+
+    if (attr->type == mqi_string)
+        mrp_free((void *) attr->value.string);
+
+    mrp_free((void *) attr->name);
+    mrp_free(attr);
+}
+
+
+static void free_map(void *object)
+{
+    mrp_htbl_t *ht = object;
+    mrp_htbl_destroy(ht, TRUE);
+}
+
+
+static resource_o_t * create_resource(resource_set_o_t *rset,
+        const char *resource_name, uint32_t id)
+{
+    char buf[MAX_PATH_LENGTH];
+    int ret;
+    char *name = NULL;
+    bool *mandatory = NULL, *shared = NULL;
+
+    /* attribute handling */
+    mrp_attr_t attr_buf[128];
+    mrp_attr_t *i;
+    uint32_t resource_id;
+    mrp_attr_t *attrs;
+    mrp_attr_t *copy;
+
+    mrp_htbl_config_t map_conf;
+    mrp_htbl_t *conf;
+
+    resource_o_t *resource = mrp_allocz(sizeof(resource_o_t));
+
+    if (!resource)
+        goto error;
+
+    ret = snprintf(buf, MAX_PATH_LENGTH, "%s/%u", rset->path, id);
+
+    if (ret < 0 || ret >= MAX_PATH_LENGTH)
+        goto error;
+
+    mandatory = mrp_allocz(sizeof(bool));
+    shared = mrp_allocz(sizeof(bool));
+    name = mrp_strdup(resource_name);
+
+    if (!mandatory || !shared || !name) {
+        mrp_free(mandatory);
+        mrp_free(shared);
+        mrp_free(name);
+        goto error;
+    }
+
+    *mandatory = TRUE;
+    *shared = FALSE;
+
+    map_conf.comp = mrp_string_comp;
+    map_conf.hash = mrp_string_hash;
+    map_conf.free = htbl_free_args;
+    map_conf.nbucket = 0;
+    map_conf.nentry = 10;
+
+    resource->mandatory_prop = create_property(rset->mgr->ctx, buf,
+            RESOURCE_IFACE, "b", PROP_MANDATORY, mandatory, free_value);
+
+    if (!resource->mandatory_prop) {
+        mrp_free(mandatory);
+        mrp_free(shared);
+        mrp_free(name);
+        goto error;
+    }
+
+    resource->mandatory_prop->writable = TRUE;
+
+    resource->shared_prop = create_property(rset->mgr->ctx, buf,
+            RESOURCE_IFACE, "b", PROP_SHARED, shared, free_value);
+
+    if (!resource->shared_prop) {
+        mrp_free(shared);
+        mrp_free(name);
+        goto error;
+    }
+
+    resource->shared_prop->writable = TRUE;
+
+    resource->name_prop = create_property(rset->mgr->ctx, buf,
+            RESOURCE_IFACE, "s", PROP_NAME, name, free_value);
+
+    if (!resource->name_prop) {
+        mrp_free(name);
+        goto error;
+    }
+
+    resource->status_prop = create_property(rset->mgr->ctx, buf,
+            RESOURCE_IFACE, "s", PROP_STATUS, "pending", NULL);
+
+    if (!resource->status_prop)
+        goto error;
+
+    resource_id = mrp_resource_definition_get_resource_id_by_name(name);
+
+    attrs = mrp_resource_definition_read_all_attributes(resource_id, 128,
+            attr_buf);
+    i = attrs;
+
+    resource->rset = rset;
+    resource->path = mrp_strdup(buf);
+
+    if (!resource->path)
+        goto error;
+
+    conf = mrp_htbl_create(&map_conf);
+
+    if (!conf)
+        goto error;
+
+    while (i->name != NULL) {
+
+        copy = mrp_allocz(sizeof(mrp_attr_t));
+
+        if (!copy)
+            goto error_delete_conf;
+
+        memcpy(copy, i, sizeof(mrp_attr_t));
+        copy->name = mrp_strdup(i->name);
+
+        if (!copy->name) {
+            mrp_free(copy);
+            goto error_delete_conf;
+        }
+
+        if (i->type == mqi_string) {
+            copy->value.string = mrp_strdup(i->value.string);
+            if (!copy->value.string) {
+                mrp_free((void *) copy->name);
+                mrp_free(copy);
+                goto error_delete_conf;
+            }
+        }
+        mrp_htbl_insert(conf, (void *) copy->name, copy);
+        i++;
+    }
+
+    resource->conf_prop = create_property(rset->mgr->ctx, buf,
+            RESOURCE_IFACE, "a{sv}", PROP_ATTRIBUTES_CONF, conf, free_map);
+
+    if (!resource->conf_prop) {
+        goto error;
+    }
+
+    resource->arguments_prop = create_property(rset->mgr->ctx, buf,
+            RESOURCE_IFACE, "a{sv}", PROP_ATTRIBUTES, conf, NULL);
+
+    if (!resource->arguments_prop) {
+        goto error;
+    }
+
+    return resource;
+
+error_delete_conf:
+    mrp_htbl_destroy(conf, TRUE);
+
+error:
+    if (resource)
+        destroy_resource(resource);
+
+    return NULL;
+}
+
+
+static void destroy_rset(resource_set_o_t *rset)
+{
+    dbus_data_t *ctx;
+
+    if (!rset)
+        return;
+
+    ctx = rset->mgr->ctx;
+
+    mrp_log_info("destroy rset %s", rset->path);
+
+    mrp_dbus_remove_method(ctx->dbus, rset->path, RSET_IFACE, RSET_DELETE,
+            rset_cb, ctx);
+    mrp_dbus_remove_method(ctx->dbus, rset->path, RSET_IFACE, RSET_RELEASE,
+            rset_cb, ctx);
+    mrp_dbus_remove_method(ctx->dbus, rset->path, RSET_IFACE, RSET_REQUEST,
+            rset_cb, ctx);
+    mrp_dbus_remove_method(ctx->dbus, rset->path, RSET_IFACE, RSET_ADD_RESOURCE,
+            rset_cb, ctx);
+    mrp_dbus_remove_method(ctx->dbus, rset->path, RSET_IFACE, RSET_SET_PROPERTY,
+            rset_cb, ctx);
+    mrp_dbus_remove_method(ctx->dbus, rset->path, RSET_IFACE,
+            RSET_GET_PROPERTIES, rset_cb, ctx);
+
+    if (rset->resources)
+        mrp_htbl_destroy(rset->resources, TRUE);
+
+    destroy_property(rset->class_prop);
+    destroy_property(rset->status_prop);
+    destroy_property(rset->resources_prop);
+    destroy_property(rset->available_resources_prop);
+
+    if (ctx->tracking)
+        mrp_dbus_forget_name(ctx->dbus, rset->owner, dbus_name_cb, rset);
+
+    if (rset->set) {
+        mrp_resource_set_destroy(rset->set);
+        rset->set = NULL;
+    }
+
+    mrp_free(rset->path);
+    mrp_free(rset->owner);
+
+    mrp_free(rset);
+}
+
+
+static resource_set_o_t * create_rset(manager_o_t *mgr, uint32_t id,
+            const char *sender)
+{
+    char buf[MAX_PATH_LENGTH];
+    char *resbuf[128];
+    int ret;
+    mrp_htbl_config_t resources_conf;
+    resource_set_o_t *rset = NULL;
+    char **resources_arr;
+    char **available_resources_arr;
+
+    if (!sender)
+        goto error;
+
+    rset = mrp_allocz(sizeof(resource_set_o_t));
+
+    if (!rset)
+        goto error;
+
+    ret = snprintf(buf, MAX_PATH_LENGTH, "%s/%u", MURPHY_PATH_BASE, id);
+
+    if (ret < 0 || ret >= MAX_PATH_LENGTH)
+        goto error;
+
+    rset->mgr = mgr;
+    rset->path = mrp_strdup(buf);
+
+    if (!rset->path)
+        goto error;
+
+    resources_conf.comp = mrp_string_comp;
+    resources_conf.hash = mrp_string_hash;
+    resources_conf.free = htbl_free_resources;
+    resources_conf.nbucket = 0;
+    resources_conf.nentry = 10;
+
+    rset->resources = mrp_htbl_create(&resources_conf);
+
+    if (!rset->resources)
+        goto error;
+
+    resources_arr = mrp_allocz(sizeof(char **));
+    if (!resources_arr)
+        goto error;
+    resources_arr[0] = NULL;
+
+    rset->resources_prop = create_property(mgr->ctx, rset->path,
+            RSET_IFACE, "ao", PROP_RESOURCES, resources_arr, free_string_array);
+
+    if (!rset->resources_prop)
+        goto error;
+
+    rset->class_prop = create_property(mgr->ctx, rset->path,
+            RSET_IFACE, "s", PROP_CLASS,
+            mrp_strdup(rset->mgr->ctx->default_class), free_value);
+
+    if (!rset->class_prop)
+        goto error;
+
+    rset->class_prop->writable = TRUE;
+
+    rset->status_prop = create_property(mgr->ctx, rset->path,
+            RSET_IFACE, "s", PROP_STATUS, "pending", NULL);
+
+    if (!rset->status_prop)
+        goto error;
+
+    available_resources_arr = copy_string_array(
+                mrp_resource_definition_get_all_names(128,
+                        (const char **) resbuf));
+
+    if (!available_resources_arr)
+        goto error;
+
+    rset->available_resources_prop = create_property(mgr->ctx,
+            rset->path, RSET_IFACE, "as", PROP_AVAILABLE_RESOURCES,
+            available_resources_arr, free_string_array);
+
+    if (!rset->available_resources_prop)
+        goto error;
+
+    rset->owner = mrp_strdup(sender);
+
+    if (!rset->owner)
+        goto error;
+
+    /* start following the owner */
+    if (mgr->ctx->tracking)
+        mrp_dbus_follow_name(mgr->ctx->dbus, rset->owner, dbus_name_cb, rset);
+
+    rset->set = mrp_resource_set_create(mgr->client, 0, 0, 0, event_cb,
+                rset);
+
+    if (!rset->set) {
+        mrp_log_error("Failed to create resource set");
+        goto error;
+    }
+
+    rset->error = FALSE;
+
+    return rset;
+
+error:
+    if (rset) {
+        destroy_rset(rset);
+    }
+
+    return NULL;
+}
+
+
+static void dbus_name_cb(mrp_dbus_t *dbus, const char *name, int up,
+                          const char *owner, void *user_data)
+{
+    mrp_log_info("dbus_name_cb: %s status %d, owner %s", name, up, owner);
+
+    MRP_UNUSED(dbus);
+
+    if (up == 0) {
+        /* a client that we've been tracking has just died */
+        resource_set_o_t *rset = user_data;
+        manager_o_t *mgr = rset->mgr;
+        mrp_htbl_remove(mgr->rsets, (void *) rset->path, TRUE);
+        update_property(mgr->rsets_prop, htbl_keys(mgr->rsets));
+    }
+}
+
+
+static void htbl_free_rsets(void *key, void *object)
+{
+    resource_set_o_t *rset = object;
+
+    MRP_UNUSED(key);
+
+    destroy_rset(rset);
+}
+
+
+static int parse_path(const char *path, uint32_t *rset_id,
+        uint32_t *resource_id)
+{
+    *rset_id = -1;
+    *resource_id = -1;
+
+    int base_len = strlen(MURPHY_PATH_BASE);
+    int path_len = strlen(path);
+
+    char *p = (char *) path;
+    char *first_sep = NULL;
+    char *second_sep = NULL;
+    char *guard = (char *) path + path_len;
+
+    if (base_len < 3)
+        return FALSE; /* parsing corner case */
+
+    if (path_len < base_len + 4)
+        return FALSE; /* need to have at least "/1/2" */
+
+    if (strncmp(path, MURPHY_PATH_BASE, base_len) != 0)
+        return FALSE;
+
+    p += base_len;
+
+    if (*p != '/')
+        return FALSE;
+
+    first_sep = p;
+
+    p++;
+
+    while (p != guard) {
+        if (*p == '/') {
+            second_sep = p;
+        }
+        p++;
+    }
+
+    if (!second_sep)
+        return FALSE;
+
+    if (second_sep + 1 == guard)
+        return FALSE; /* missing resource id */
+
+    /* ok, the rset_id is between first_sep and second_sep, and
+     * resource_id is between second_sep and guard */
+
+    p = NULL;
+
+    *rset_id = strtol(first_sep + 1, &p, 10);
+
+    if (p != second_sep)
+        return FALSE;
+
+    *resource_id = strtol(second_sep + 1, &p, 10);
+
+    if (p != guard)
+        return FALSE;
+
+    return TRUE;
+}
+
+
+struct attr_iter_s {
+    mrp_attr_t *attrs;
+    int count;
+};
+
+
+static int collect_attrs_cb(void *key, void *object, void *user_data)
+{
+    mrp_attr_t *attr = object;
+    mrp_attr_t *copy;
+    struct attr_iter_s *s = user_data;
+    MRP_UNUSED(key);
+
+    copy = &s->attrs[s->count];
+
+    memcpy(copy, attr, sizeof(mrp_attr_t));
+
+    if (attr->type == mqi_string) {
+        copy->value.string = mrp_strdup(attr->value.string);
+    }
+    copy->name = mrp_strdup(attr->name);
+
+    s->count++;
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+
+static void update_attributes(const char *resource_name,
+        mrp_resource_set_t *set, mrp_htbl_t *attr_map)
+{
+    int count = 0;
+    mrp_htbl_foreach(attr_map, count_keys_cb, &count);
+
+    {
+        struct attr_iter_s iter;
+        mrp_attr_t attrs[count+1];
+
+        memset(attrs, 0, (count+1)*sizeof(mrp_attr_t));
+
+        iter.count = 0;
+        iter.attrs = attrs;
+
+        /* add the attributes */
+        mrp_htbl_foreach(attr_map, collect_attrs_cb, &iter);
+
+        /* FIXME: this breaks down if there are two resources of the same name
+         * in a resource set */
+
+        mrp_resource_set_write_attributes(set, resource_name, attrs);
+        free_attr_array(attrs);
+    }
+}
+
+
+static int update_conf_cb(void *key, void *object, void *user_data)
+{
+    mrp_htbl_t **confs = user_data;
+    mrp_attr_t *old_attr = object;
+
+    mrp_htbl_t *new_conf = confs[0];
+
+    if (mrp_htbl_lookup(new_conf, key) == NULL) {
+        /* copy the attribute */
+        mrp_attr_t *attr = mrp_allocz(sizeof(mrp_attr_t));
+
+        if (!attr) {
+            goto error;
+        }
+        attr->name = mrp_strdup(old_attr->name);
+        if (!attr->name) {
+            mrp_free(attr);
+            goto error;
+        }
+        attr->type = old_attr->type;
+        switch (attr->type) {
+            case mqi_string:
+                attr->value.string = mrp_strdup(old_attr->value.string);
+                if (!attr->value.string) {
+                    mrp_free((void *) attr->name);
+                    mrp_free(attr);
+                    goto error;
+                }
+                break;
+            case mqi_integer:
+                attr->value.integer = old_attr->value.integer;
+                break;
+            case mqi_unsignd:
+                attr->value.unsignd = old_attr->value.unsignd;
+                break;
+            case mqi_floating:
+                attr->value.floating = old_attr->value.floating;
+                break;
+            default:
+                goto error;
+        }
+        /* add the value to the conf */
+        mrp_htbl_insert(new_conf, (void *) attr->name, attr);
+    }
+
+    confs[1] = new_conf; /* indicate success */
+
+    return MRP_HTBL_ITER_MORE;
+
+error:
+    confs[1] = NULL; /* indicate error */
+    return MRP_HTBL_ITER_STOP;
+}
+
+
+
+static int resource_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data)
+{
+    const char *member = mrp_dbus_msg_member(msg);
+    const char *iface = mrp_dbus_msg_interface(msg);
+    const char *path = mrp_dbus_msg_path(msg);
+    char *error_msg = "Received invalid message";
+    mrp_dbus_msg_t *reply = NULL;
+    char buf[MAX_PATH_LENGTH];
+
+    dbus_data_t *ctx = data;
+
+    uint32_t rset_id, resource_id;
+
+    resource_set_o_t *rset;
+    resource_o_t *resource;
+
+    int ret;
+
+    mrp_log_info("Resource callback called -- member: '%s', path: '%s',"
+            " interface: '%s'", member, path, iface);
+
+    /* parse the rset id and resource id */
+
+    if (!parse_path(path, &rset_id, &resource_id)) {
+        mrp_log_error("Failed to parse path");
+        goto error_reply;
+    }
+
+    ret = snprintf(buf, MAX_PATH_LENGTH, "%s/%u", MURPHY_PATH_BASE, rset_id);
+
+    if (ret < 0 || ret >= MAX_PATH_LENGTH)
+        goto error_reply;
+
+    rset = mrp_htbl_lookup(ctx->mgr->rsets, buf);
+
+    if (!rset)
+        goto error_reply;
+
+    resource = mrp_htbl_lookup(rset->resources, (void *) path);
+
+    if (!resource)
+        goto error_reply;
+
+    if (strcmp(member, RESOURCE_GET_PROPERTIES) == 0) {
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply)
+            goto error;
+
+        mrp_log_info("getProperties of resource %s", path);
+
+        mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_ARRAY, "{sv}");
+
+        if (!(get_property_dict_entry(resource->name_prop, reply) &&
+                get_property_dict_entry(resource->status_prop, reply) &&
+                get_property_dict_entry(resource->mandatory_prop, reply) &&
+                get_property_dict_entry(resource->shared_prop, reply) &&
+                get_property_dict_entry(resource->arguments_prop, reply) &&
+                get_property_dict_entry(resource->conf_prop, reply))) {
+            goto error_reply;
+        }
+
+        mrp_dbus_msg_close_container(reply);
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    else if (strcmp(member, RESOURCE_SET_PROPERTY) == 0) {
+        const char *name;
+        char *sig;
+
+        mrp_log_info("setProperty of resource %s", path);
+
+        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &name)) {
+            goto error_reply;
+        }
+
+        /* get the type of key 'name' */
+        if (strcmp(name, PROP_MANDATORY) == 0) {
+            uint32_t v = 0;
+            bool *value;
+            sig = "b";
+
+            value = mrp_allocz(sizeof(bool));
+            if (!value) {
+                error_msg = "internal error";
+                goto error_reply;
+            }
+
+            mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_VARIANT, sig);
+
+            mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_BOOLEAN, &v);
+            *value = !!v;
+
+            update_property(resource->mandatory_prop, value);
+
+            mrp_dbus_msg_exit_container(msg);
+        }
+        else if (strcmp(name, PROP_SHARED) == 0) {
+            uint32_t v = 0;
+            bool *value;
+            sig = "b";
+            value = mrp_allocz(sizeof(bool));
+
+            if (!value) {
+                error_msg = "Internal error";
+                goto error_reply;
+            }
+
+            mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_VARIANT, sig);
+
+            mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_BOOLEAN, &v);
+            *value = !!v;
+
+            update_property(resource->shared_prop, value);
+
+            mrp_dbus_msg_exit_container(msg);
+        }
+        else if (strcmp(name, PROP_ATTRIBUTES_CONF) == 0) {
+            mrp_htbl_config_t map_conf;
+            mrp_htbl_t *conf;
+            int new_count = 0;
+            int old_count = 0;
+
+            sig = "a{sv}";
+
+            if (resource->rset->locked != 0) {
+                error_msg = "Resource set cannot be changed after requesting";
+                goto error_reply;
+            }
+
+            if (!mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_VARIANT, sig)) {
+                error_msg = "Invalid message";
+                goto error_reply;
+            }
+
+            if (!mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_ARRAY, "{sv}")) {
+                error_msg = "Invalid message";
+                goto error_reply;
+            }
+
+            map_conf.comp = mrp_string_comp;
+            map_conf.hash = mrp_string_hash;
+            map_conf.free = htbl_free_args;
+            map_conf.nbucket = 0;
+            map_conf.nentry = 10;
+
+            conf = mrp_htbl_create(&map_conf);
+
+            if (!conf) {
+                error_msg = "Internal error";
+                goto error_reply;
+            }
+
+            while (mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_DICT_ENTRY, "sv")) {
+                char *key;
+                mrp_attr_t *prev_value;
+                mrp_attr_t *new_value;
+                const char *value_sig;
+
+                if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &key)) {
+                    mrp_htbl_destroy(conf, TRUE);
+                    goto error_reply;
+                }
+                prev_value = mrp_htbl_lookup(resource->conf_prop->value, key);
+
+                if (!prev_value) {
+                    mrp_log_error("no previous value %s in attributes", key);
+                    error_msg = "Configuration attribute definition missing";
+                    mrp_htbl_destroy(conf, TRUE);
+                    goto error_reply;
+                }
+
+                value_sig = get_dbus_type(prev_value);
+
+                if (!value_sig) {
+                    error_msg = "Failed to map database value to D-Bus signature";
+                    mrp_htbl_destroy(conf, TRUE);
+                    goto error_reply;
+                }
+
+                if (!mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_VARIANT, value_sig)) {
+                    error_msg = "Invalid message";
+                    mrp_htbl_destroy(conf, TRUE);
+                    goto error_reply;
+                }
+
+                new_value = mrp_allocz(sizeof(mrp_attr_t));
+                if (!new_value) {
+                    error_msg = "Internal error";
+                    mrp_htbl_destroy(conf, TRUE);
+                    goto error_reply;
+                }
+
+                switch(prev_value->type) {
+                    case mqi_string:
+                    {
+                        char *value;
+
+                        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &value)) {
+                            mrp_free(new_value);
+                            mrp_htbl_destroy(conf, TRUE);
+                            goto error_reply;
+                        }
+                        new_value->name = mrp_strdup(key);
+                        new_value->type = mqi_string;
+                        new_value->value.string = mrp_strdup(value);
+                        break;
+                    }
+                    case mqi_unsignd:
+                    {
+                        uint32_t value;
+
+                        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_UINT32, &value)) {
+                            mrp_free(new_value);
+                            mrp_htbl_destroy(conf, TRUE);
+                            goto error_reply;
+                        }
+                        new_value->name = mrp_strdup(key);
+                        new_value->type = mqi_unsignd;
+                        new_value->value.unsignd = value;
+                        break;
+                    }
+                    case mqi_integer:
+                    {
+                        int32_t value;
+
+                        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_INT32, &value)) {
+                            mrp_free(new_value);
+                            mrp_htbl_destroy(conf, TRUE);
+                            goto error_reply;
+                        }
+                        new_value->name = mrp_strdup(key);
+                        new_value->type = mqi_integer;
+                        new_value->value.integer = value;
+                        break;
+                    }
+                    case mqi_floating:
+                    {
+                        double value;
+
+                        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_DOUBLE, &value)) {
+                            mrp_free(new_value);
+                            mrp_htbl_destroy(conf, TRUE);
+                            goto error_reply;
+                        }
+                        new_value->name = mrp_strdup(key);
+                        new_value->type = mqi_floating;
+                        new_value->value.floating = value;
+                        break;
+                    }
+                    default:
+                        mrp_htbl_destroy(conf, TRUE);
+                        mrp_free(new_value);
+                        error_msg = "Attribute value unknown";
+                        goto error_reply;
+                }
+
+                mrp_dbus_msg_exit_container(msg);
+
+                mrp_htbl_insert(conf, (void *) new_value->name, new_value);
+                new_count++;
+            }
+
+            /* What about if not all properties were set? Maybe
+             * update_property should merge the old map with the new map.
+             * For now, just check the the size is the same. */
+
+            mrp_htbl_foreach(resource->conf_prop->value, count_keys_cb,
+                    &old_count);
+
+            if (old_count > new_count) {
+                /* for every key in old conf, add the key to new conf if it's
+                 * not there already */
+
+                /* the second value is return value for errors */
+                mrp_htbl_t *confs[2] = { conf, NULL };
+
+                mrp_htbl_foreach(resource->conf_prop->value, update_conf_cb,
+                        confs);
+
+                if (confs[1] == NULL) {
+                    mrp_htbl_destroy(conf, TRUE);
+                    error_msg = "attribute merging failed";
+                    goto error_reply;
+                }
+            }
+            else if (old_count < new_count) {
+                mrp_htbl_destroy(conf, TRUE);
+                error_msg = "setting too many attributes";
+                goto error_reply;
+            }
+
+            update_property(resource->conf_prop, conf);
+            update_property(resource->arguments_prop, conf);
+
+            if (resource->rset->locked) {
+                /* if the resource set is already created for the library,
+                 * we can set the attributes */
+                update_attributes(resource->name_prop->value,
+                        resource->rset->set, conf);
+            }
+
+            mrp_dbus_msg_exit_container(msg);
+        }
+        else {
+            error_msg = "Resource property read-only or missing";
+            goto error_reply;
+        }
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply)
+            goto error;
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    else if (strcmp(member, RESOURCE_DELETE) == 0) {
+        mrp_log_info("Deleting resource %s", path);
+
+        mrp_htbl_remove(rset->resources, (void *) path, TRUE);
+        update_property(rset->resources_prop, htbl_keys(rset->resources));
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply)
+            goto error;
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+
+    return TRUE;
+
+error_reply:
+    {
+        mrp_dbus_err_t err;
+        mrp_dbus_error_init(&err);
+        mrp_dbus_error_set(&err, "org.freedesktop.DBus.Error.Failed", error_msg);
+
+        if (reply) {
+            /* something was already done -- free some memory */
+            mrp_dbus_msg_unref(reply);
+        }
+
+        reply = mrp_dbus_msg_error(dbus, msg, &err);
+
+        if (reply) {
+            mrp_dbus_send_msg(dbus, reply);
+            mrp_dbus_msg_unref(reply);
+        }
+    }
+    return TRUE;
+
+error:
+    return TRUE;
+}
+
+
+static int add_resource_cb(void *key, void *object, void *user_data)
+{
+    resource_o_t *r = object;
+    resource_set_o_t *rset = user_data;
+
+    bool shared = *(bool *) r->shared_prop->value;
+    bool mandatory = *(bool *) r->mandatory_prop->value;
+    char *name = r->name_prop->value;
+
+    int count = 0;
+
+    MRP_UNUSED(key);
+
+    /* count the attributes */
+    mrp_htbl_foreach(r->conf_prop->value, count_keys_cb, &count);
+
+    if (mrp_resource_set_add_resource(rset->set, name, shared, NULL, mandatory)
+                >= 0) {
+        update_attributes(name, rset->set, r->conf_prop->value);
+    }
+    else {
+        mrp_log_error("Error adding the resource to resource set!");
+        rset->error = TRUE;
+    }
+
+    return MRP_HTBL_ITER_MORE;
+}
+
+static inline int initialize_resource_set(resource_set_o_t *rset)
+{
+    /* add the resources */
+    mrp_htbl_foreach(rset->resources, add_resource_cb, rset);
+    if (rset->error) {
+        /* could not add the resource to resource set */
+        rset->error = FALSE;
+        return FALSE;
+    }
+
+    if (mrp_application_class_add_resource_set(
+            (char *) rset->class_prop->value,
+            rset->mgr->zone, rset->set, 0) < 0) {
+        /* This is actually quite serious, since most likely we cannot
+         * ever get this to work. The zone is most likely not defined.
+         * The resource library is known to crash if the rset->set
+         * pointer is used for acquiring.
+         */
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static int rset_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data)
+{
+    const char *member = mrp_dbus_msg_member(msg);
+    const char *iface = mrp_dbus_msg_interface(msg);
+    const char *path = mrp_dbus_msg_path(msg);
+    char *error_msg = "Received invalid message";
+    int requesting = 0;
+
+    mrp_dbus_msg_t *reply;
+
+    dbus_data_t *ctx = data;
+
+    resource_set_o_t *rset = mrp_htbl_lookup(ctx->mgr->rsets, (void *) path);
+
+    mrp_log_info("Resource set callback called -- member: '%s', path: '%s',"
+            " interface: '%s'", member, path, iface);
+
+    if (!rset) {
+        mrp_log_error("Resource set '%s' not found, ignoring", path);
+        goto error;
+    }
+
+    if (strcmp(member, RSET_GET_PROPERTIES) == 0) {
+
+        /* FIXME: check return values */
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply)
+            goto error;
+
+        mrp_log_info("getProperties of rset %s", path);
+
+        if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_ARRAY, "{sv}")) {
+            mrp_dbus_msg_unref(reply);
+            goto error_reply;
+        }
+
+        get_property_dict_entry(rset->class_prop, reply);
+        get_property_dict_entry(rset->status_prop, reply);
+        get_property_dict_entry(rset->resources_prop, reply);
+        get_property_dict_entry(rset->available_resources_prop, reply);
+
+        mrp_dbus_msg_close_container(reply);
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    else if (strcmp(member, RSET_ADD_RESOURCE) == 0) {
+        const char *name;
+
+        resource_o_t *resource;
+
+        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &name)) {
+            goto error_reply;
+        }
+
+        resource = create_resource(rset, name, rset->next_id++);
+
+        if (!resource)
+            goto error_reply;
+
+        if (!mrp_dbus_export_method(ctx->dbus, resource->path,
+                    RESOURCE_IFACE, RESOURCE_GET_PROPERTIES, resource_cb,
+                    ctx)) {
+            destroy_resource(resource);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, resource->path,
+                    RESOURCE_IFACE, RESOURCE_SET_PROPERTY, resource_cb, ctx)) {
+            destroy_resource(resource);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, resource->path,
+                    RESOURCE_IFACE, RESOURCE_DELETE, resource_cb, ctx)) {
+            destroy_resource(resource);
+            goto error_reply;
+        }
+
+        mrp_htbl_insert(rset->resources, (void *) resource->path,
+                resource);
+        update_property(rset->resources_prop, htbl_keys(rset->resources));
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply) {
+            mrp_htbl_remove(rset->resources, (void *) path, TRUE);
+            update_property(rset->resources_prop, htbl_keys(rset->resources));
+            goto error;
+        }
+
+        if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_OBJECT_PATH, resource->path)) {
+            mrp_htbl_remove(rset->resources, (void *) path, TRUE);
+            update_property(rset->resources_prop, htbl_keys(rset->resources));
+            mrp_dbus_msg_unref(reply);
+            goto error_reply;
+        }
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+
+        mrp_log_info("created resource %s\n", resource->path);
+    }
+    /* Requesting and releasing sets mostly shares code,
+     * so we use the same code path, and set a variable to
+     * differentiate between the two modes of operation.
+     */
+    else if ((requesting = !strcmp(member, RSET_REQUEST)) ||
+             strcmp(member, RSET_RELEASE) == 0) {
+        if (requesting)
+            mrp_log_info("Requesting rset %s", path);
+        else
+            mrp_log_info("Releasing rset %s", path);
+
+        if (!rset->locked) {
+            if (!initialize_resource_set(rset)) {
+                error_msg = "Could not set up resource set; "
+                        "possibly an unknown resource or zone";
+                goto error_reply;
+            }
+        }
+
+        rset->committed = TRUE;
+
+        if (requesting)
+            mrp_resource_set_acquire(rset->set, 0);
+        else
+            mrp_resource_set_release(rset->set, 0);
+
+        /* Due to limitations in resource library, this resource set cannot
+         * be changed anymore. This might change in the future.
+         */
+        rset->locked = TRUE;
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+        if (!reply)
+            goto error;
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    else if (strcmp(member, RSET_DELETE) == 0) {
+        mrp_log_info("Deleting rset %s", path);
+
+        mrp_htbl_remove(ctx->mgr->rsets, (void *) path, TRUE);
+        update_property(ctx->mgr->rsets_prop, htbl_keys(ctx->mgr->rsets));
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+        if (!reply)
+            goto error;
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    else if (strcmp(member, RSET_SET_PROPERTY) == 0) {
+        char *name = NULL;
+        char *value = NULL;
+
+        if (rset->locked) {
+            error_msg = "Resource set cannot be changed after requesting";
+            goto error_reply;
+        }
+
+        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &name)) {
+            error_msg = "Message didn't contain the property name";
+            goto error_reply;
+        }
+
+        if (strcmp(name, PROP_CLASS) != 0) {
+            error_msg = "Unknown property name in message";
+            goto error_reply;
+        }
+
+        if (!mrp_dbus_msg_enter_container(msg, MRP_DBUS_TYPE_VARIANT, "s")) {
+            error_msg = "Property value isn't contained inside a variant";
+            goto error_reply;
+        }
+
+        if (!mrp_dbus_msg_read_basic(msg, MRP_DBUS_TYPE_STRING, &value)) {
+            mrp_dbus_msg_exit_container(msg);
+            goto error_reply;
+        }
+
+        update_property(rset->class_prop, mrp_strdup(value));
+
+        mrp_dbus_msg_exit_container(msg);
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply)
+            goto error;
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+
+    return TRUE;
+
+error_reply:
+    {
+        mrp_dbus_err_t err;
+        mrp_dbus_error_init(&err);
+        mrp_dbus_error_set(&err, "org.freedesktop.DBus.Error.Failed", error_msg);
+
+        mrp_log_error("rset_cb failure: %s", error_msg);
+
+        reply = mrp_dbus_msg_error(dbus, msg, &err);
+
+        if (reply) {
+            mrp_dbus_send_msg(dbus, reply);
+            mrp_dbus_msg_unref(reply);
+        }
+    }
+
+error:
+    return TRUE;
+}
+
+/*
+ * Updates or creates a property that contains the available
+ * application classes. Returns -1 in case of failure (property
+ * is kept as-is), 0 if the requested list is empty, and 1 if the
+ * requested list is not empty.
+ */
+static int update_classes(dbus_data_t *ctx, property_o_t **prop)
+{
+    property_o_t *res_classes_prop = NULL;
+    const char **orig_classes_array = mrp_application_class_get_all_names(0,
+                                        NULL);
+    char **res_classes_array = NULL;
+    int arr_has_content = -1;
+
+    if (!orig_classes_array) {
+        mrp_log_error("Failed to get application classes");
+        goto error;
+    }
+
+    res_classes_array = copy_string_array(orig_classes_array);
+    if (!res_classes_array) {
+        mrp_log_error("Failed to copy application classes");
+        goto error;
+    }
+
+    res_classes_prop = create_property(ctx, MURPHY_PATH_BASE,
+            MANAGER_IFACE, "as", PROP_AVAILABLE_CLASSES,
+            res_classes_array, free_string_array);
+    if (!res_classes_prop) {
+        mrp_log_error("Failed to create a property");
+        free_string_array(res_classes_array);
+        goto error;
+    }
+
+    if (*res_classes_array) {
+        mrp_log_info("Application class listing is non-empty");
+        arr_has_content = 1;
+    } else {
+        mrp_log_info("Application class listing is empty");
+        arr_has_content = 0;
+    }
+
+    /* Remove the old prop if new is valid */
+    destroy_property(*prop);
+
+    *prop = res_classes_prop;
+
+    /* Clean up and return */
+error:
+    free_value(orig_classes_array);
+
+    return arr_has_content;
+}
+
+static int mgr_cb(mrp_dbus_t *dbus, mrp_dbus_msg_t *msg, void *data)
+{
+    const char *member = mrp_dbus_msg_member(msg);
+    const char *iface = mrp_dbus_msg_interface(msg);
+    const char *path = mrp_dbus_msg_path(msg);
+    int ret = -1;
+
+    mrp_dbus_msg_t *reply;
+
+    dbus_data_t *ctx = data;
+
+    mrp_log_info("Manager callback called -- member: '%s', path: '%s',"
+            " interface: '%s'", member, path, iface);
+
+    if (strcmp(member, MANAGER_GET_PROPERTIES) == 0) {
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+
+        if (!reply)
+            goto error;
+
+        mrp_log_info("getProperties of manager %s", path);
+
+        if (!mrp_dbus_msg_open_container(reply, MRP_DBUS_TYPE_ARRAY, "{sv}")) {
+            goto error_reply;
+        }
+
+        get_property_dict_entry(ctx->mgr->rsets_prop, reply);
+
+        /* Update classes if our array is empty */
+        if (!ctx->has_classes) {
+            mrp_log_info("Updating resource classes as they were not set");
+            ret = update_classes(ctx, &ctx->mgr->available_classes_prop);
+            if (ret < 0 || !ctx->mgr->available_classes_prop) {
+                mrp_log_error("Updating available classes failed (ret=%d, ptr=%p)",
+                    ret, ctx->mgr->available_classes_prop);
+                goto error_reply;
+            }
+
+            /* Update the status */
+            ctx->has_classes = ret;
+        }
+
+        get_property_dict_entry(ctx->mgr->available_classes_prop, reply);
+
+        mrp_dbus_msg_close_container(reply);
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+    }
+    else if (strcmp(member, MANAGER_CREATE_RESOURCE_SET) == 0) {
+        const char *sender = mrp_dbus_msg_sender(msg);
+        resource_set_o_t *rset = create_rset(ctx->mgr, ctx->mgr->next_id++,
+                sender);
+
+        if (!rset)
+            goto error_reply;
+
+        if (!mrp_dbus_export_method(ctx->dbus, rset->path,
+                    RSET_IFACE, RSET_GET_PROPERTIES, rset_cb, ctx)) {
+            destroy_rset(rset);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, rset->path,
+                    RSET_IFACE, RSET_SET_PROPERTY, rset_cb, ctx)) {
+            destroy_rset(rset);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, rset->path,
+                    RSET_IFACE, RSET_ADD_RESOURCE, rset_cb, ctx)) {
+            destroy_rset(rset);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, rset->path,
+                    RSET_IFACE, RSET_REQUEST, rset_cb, ctx)) {
+            destroy_rset(rset);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, rset->path,
+                    RSET_IFACE, RSET_RELEASE, rset_cb, ctx)) {
+            destroy_rset(rset);
+            goto error_reply;
+        }
+        if (!mrp_dbus_export_method(ctx->dbus, rset->path,
+                    RSET_IFACE, RSET_DELETE, rset_cb, ctx)) {
+            destroy_rset(rset);
+            goto error_reply;
+        }
+
+        mrp_htbl_insert(ctx->mgr->rsets, (void *) rset->path, rset);
+        update_property(ctx->mgr->rsets_prop, htbl_keys(ctx->mgr->rsets));
+
+        reply = mrp_dbus_msg_method_return(dbus, msg);
+        if (!reply) {
+            mrp_htbl_remove(ctx->mgr->rsets, (void *) rset->path, TRUE);
+            update_property(ctx->mgr->rsets_prop, htbl_keys(ctx->mgr->rsets));
+            goto error;
+        }
+
+        if (!mrp_dbus_msg_append_basic(reply, MRP_DBUS_TYPE_OBJECT_PATH,
+                    rset->path)) {
+            mrp_htbl_remove(ctx->mgr->rsets, (void *) rset->path, TRUE);
+            update_property(ctx->mgr->rsets_prop, htbl_keys(ctx->mgr->rsets));
+            mrp_dbus_msg_unref(reply);
+            goto error_reply;
+        }
+
+        mrp_dbus_send_msg(dbus, reply);
+        mrp_dbus_msg_unref(reply);
+
+        mrp_log_info("created resource set %s\n", rset->path);
+    }
+
+    return TRUE;
+
+error_reply:
+    {
+        mrp_dbus_err_t err;
+        mrp_dbus_error_init(&err);
+        mrp_dbus_error_set(&err, "org.freedesktop.DBus.Error.Failed", "Received invalid message");
+
+        reply = mrp_dbus_msg_error(dbus, msg, &err);
+
+        if (reply) {
+            mrp_dbus_send_msg(dbus, reply);
+            mrp_dbus_msg_unref(reply);
+        }
+    }
+
+error:
+    return TRUE;
+}
+
+
+static void destroy_manager(manager_o_t *mgr)
+{
+    if (!mgr)
+        return;
+
+    mrp_dbus_remove_method(mgr->ctx->dbus, MURPHY_PATH_BASE,
+            MANAGER_IFACE, MANAGER_CREATE_RESOURCE_SET, mgr_cb, mgr->ctx);
+
+    mrp_dbus_remove_method(mgr->ctx->dbus, MURPHY_PATH_BASE,
+            MANAGER_IFACE, MANAGER_GET_PROPERTIES, mgr_cb, mgr->ctx);
+
+    mrp_htbl_destroy(mgr->rsets, TRUE);
+    destroy_property(mgr->rsets_prop);
+    destroy_property(mgr->available_classes_prop);
+
+    mrp_resource_client_destroy(mgr->client);
+
+    mrp_free(mgr);
+}
+
+
+static manager_o_t *create_manager(dbus_data_t *ctx)
+{
+    manager_o_t *mgr = mrp_allocz(sizeof(manager_o_t));
+    char **rset_arr = NULL;
+    mrp_htbl_config_t rsets_conf;
+    int ret = -1;
+
+    if (!mgr)
+        goto error;
+
+    mgr->ctx = ctx;
+
+    rset_arr = mrp_allocz(sizeof(char **));
+    if (!rset_arr)
+        goto error;
+    rset_arr[0] = NULL;
+
+    /* FIXME: duplication of code? */
+
+    mgr->rsets_prop = create_property(ctx, MURPHY_PATH_BASE, MANAGER_IFACE,
+            "ao", PROP_RESOURCE_SETS, rset_arr, free_string_array);
+
+    if (!mgr->rsets_prop)
+        goto error;
+
+    ret = update_classes(ctx, &mgr->available_classes_prop);
+    if (ret < 0 || !mgr->available_classes_prop) {
+        mrp_log_error("Failure to get the resource classes (ret=%d, p=%p)",
+            ret, mgr->available_classes_prop);
+        goto error;
+    }
+
+    ctx->has_classes = ret;
+
+    rsets_conf.comp = mrp_string_comp;
+    rsets_conf.hash = mrp_string_hash;
+    rsets_conf.free = htbl_free_rsets;
+    rsets_conf.nbucket = 0;
+    rsets_conf.nentry = 10;
+
+    mgr->rsets = mrp_htbl_create(&rsets_conf);
+
+    if (!mgr->rsets)
+        goto error;
+
+    mgr->client = mrp_resource_client_create("dbus", ctx);
+
+    if (!mgr->client)
+        goto error;
+
+    mgr->zone = ctx->default_zone;
+
+    return mgr;
+
+error:
+
+    destroy_manager(mgr);
+
+    return NULL;
+}
+
+
+static int dbus_resource_init(mrp_plugin_t *plugin)
+{
+    mrp_plugin_arg_t *args = plugin->args;
+    dbus_data_t *ctx = mrp_allocz(sizeof(dbus_data_t));
+    mrp_dbus_err_t err;
+
+    if (!ctx)
+        goto error;
+
+    ctx->ml = plugin->ctx->ml;
+    ctx->addr = args[ARG_DR_SERVICE].str;
+    ctx->tracking = args[ARG_DR_TRACK_CLIENTS].bln;
+    ctx->default_zone = args[ARG_DR_DEFAULT_ZONE].str;
+    ctx->default_class = args[ARG_DR_DEFAULT_CLASS].str;
+    ctx->bus = args[ARG_DR_BUS].str;
+
+    mrp_log_info("Connecting to bus '%s'", ctx->bus);
+
+    ctx->dbus = mrp_dbus_connect(plugin->ctx->ml, ctx->bus,
+            mrp_dbus_error_init(&err));
+
+    if (!ctx->dbus) {
+        mrp_log_error("Failed to connect to D-Bus: %s", err.message);
+        goto error;
+    }
+
+    ctx->mgr = create_manager(ctx);
+
+    if (!ctx->mgr) {
+        mrp_log_error("Failed to create manager");
+        goto error;
+    }
+
+    if (!mrp_dbus_acquire_name(ctx->dbus, ctx->addr, NULL)) {
+        mrp_log_error("Failed to acquire name '%s' on D-Bus", ctx->addr);
+        goto error;
+    }
+
+    /* in the beginning we only export the manager interface -- the
+     * rest is created dynamically
+     */
+
+    if (!mrp_dbus_export_method(ctx->dbus, MURPHY_PATH_BASE,
+                MANAGER_IFACE, MANAGER_CREATE_RESOURCE_SET, mgr_cb, ctx)) {
+       mrp_log_error("Failed to register manager object");
+       goto error;
+    }
+
+    if (!mrp_dbus_export_method(ctx->dbus, MURPHY_PATH_BASE,
+                MANAGER_IFACE, MANAGER_GET_PROPERTIES, mgr_cb, ctx)) {
+       mrp_log_error("Failed to register manager object");
+       goto error;
+    }
+
+    return TRUE;
+
+error:
+    if (ctx) {
+        destroy_manager(ctx->mgr);
+        mrp_free(ctx);
+    }
+
+    return FALSE;
+}
+
+
+static void dbus_resource_exit(mrp_plugin_t *plugin)
+{
+    dbus_data_t *ctx = plugin->data;
+
+    mrp_dbus_release_name(ctx->dbus, ctx->addr, NULL);
+    mrp_dbus_unref(ctx->dbus);
+    ctx->dbus = NULL;
+
+    mrp_htbl_destroy(ctx->mgr->rsets, TRUE);
+    destroy_manager(ctx->mgr);
+    mrp_free(ctx);
+
+    plugin->data = NULL;
+}
+
+
+#define DBUS_RESOURCE_DESCRIPTION "A plugin to implement D-Bus resource API."
+#define DBUS_RESOURCE_HELP        "D-Bus resource manager backend"
+#define DBUS_RESOURCE_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define DBUS_RESOURCE_AUTHORS     "Ismo Puustinen <ismo.puustinen@intel.com>"
+
+/* TODO: more arguments needed, such as:
+ *    - security settings?
+ */
+static mrp_plugin_arg_t args[] = {
+    MRP_PLUGIN_ARGIDX(ARG_DR_BUS, STRING, "dbus_bus", "system"),
+    MRP_PLUGIN_ARGIDX(ARG_DR_SERVICE, STRING, "dbus_service", "org.Murphy"),
+    MRP_PLUGIN_ARGIDX(ARG_DR_DEFAULT_ZONE, STRING, "default_zone", "default"),
+    MRP_PLUGIN_ARGIDX(ARG_DR_DEFAULT_CLASS, STRING, "default_class", "default"),
+    MRP_PLUGIN_ARGIDX(ARG_DR_TRACK_CLIENTS, BOOL, "dbus_track", TRUE),
+};
+
+
+MURPHY_REGISTER_PLUGIN("resource-dbus",
+                       DBUS_RESOURCE_VERSION, DBUS_RESOURCE_DESCRIPTION,
+                       DBUS_RESOURCE_AUTHORS, DBUS_RESOURCE_HELP,
+                       MRP_MULTIPLE, dbus_resource_init, dbus_resource_exit,
+                       args, MRP_ARRAY_SIZE(args),
+                       NULL, 0, NULL, 0, NULL);
diff --git a/src/plugins/plugin-systemd.c b/src/plugins/plugin-systemd.c
new file mode 100644 (file)
index 0000000..c47d66d
--- /dev/null
@@ -0,0 +1,67 @@
+#include <stdlib.h>
+#include <syslog.h>
+
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-journal.h>
+
+#include <murphy/common.h>
+#include <murphy/core.h>
+
+static void sdlogger(void *data, mrp_log_level_t level, const char *file,
+                     int line, const char *func, const char *format, va_list ap)
+{
+    va_list cp;
+    int     prio;
+    char    filebuf[1024], linebuf[64];
+
+    MRP_UNUSED(data);
+
+    va_copy(cp, ap);
+    switch (level) {
+    case MRP_LOG_ERROR:   prio = LOG_ERR;     break;
+    case MRP_LOG_WARNING: prio = LOG_WARNING; break;
+    case MRP_LOG_INFO:    prio = LOG_INFO;    break;
+    case MRP_LOG_DEBUG:   prio = LOG_DEBUG;   break;
+    default:              prio = LOG_INFO;
+    }
+
+    snprintf(filebuf, sizeof(filebuf), "CODE_FILE=%s", file);
+    snprintf(linebuf, sizeof(linebuf), "CODE_LINE=%d", line);
+    sd_journal_printv_with_location(prio, filebuf, linebuf, func, format, cp);
+
+    va_end(cp);
+}
+
+
+static int sdlogger_init(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    if (mrp_log_register_target("systemd", sdlogger, NULL))
+        mrp_log_info("systemd: registered logging target.");
+    else
+        mrp_log_error("systemd: failed to register logging target.");
+
+    return TRUE;
+}
+
+
+static void sdlogger_exit(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    mrp_log_unregister_target("systemd");
+
+    return;
+}
+
+#define SDLOGGER_DESCRIPTION "A systemd logger for Murphy."
+#define SDLOGGER_HELP        "systemd logger support for Murphy."
+#define SDLOGGER_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define SDLOGGER_AUTHORS     "Krisztian Litkey <kli@iki.fi>"
+
+MURPHY_REGISTER_PLUGIN("systemd",
+                       SDLOGGER_VERSION, SDLOGGER_DESCRIPTION,
+                       SDLOGGER_AUTHORS, SDLOGGER_HELP, MRP_SINGLETON,
+                       sdlogger_init, sdlogger_exit,
+                       NULL, 0, NULL, 0, NULL, 0, NULL);
diff --git a/src/plugins/plugin-test.c b/src/plugins/plugin-test.c
new file mode 100644 (file)
index 0000000..3885c4f
--- /dev/null
@@ -0,0 +1,810 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/json.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/console.h>
+#include <murphy/core/auth.h>
+#include <murphy/core/domain.h>
+
+
+typedef struct {
+    mrp_event_watch_t *w;
+} test_data_t;
+
+
+enum {
+    ARG_STRING1,
+    ARG_STRING2,
+    ARG_BOOLEAN1,
+    ARG_BOOLEAN2,
+    ARG_UINT321,
+    ARG_INT321,
+    ARG_DOUBLE1,
+    ARG_FAILINIT,
+    ARG_FAILEXIT,
+    ARG_OBJECT,
+    ARG_REST,
+};
+
+
+void one_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void two_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void three_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void four_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void resolve_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void auth_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void ping_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+void invoke_cb(mrp_console_t *c, void *user_data, int argc, char **argv);
+
+MRP_CONSOLE_GROUP(test_group, "test", NULL, NULL, {
+        MRP_TOKENIZED_CMD("one"  , one_cb  , TRUE,
+                          "one [args]", "command 1", "description 1"),
+        MRP_TOKENIZED_CMD("two"  , two_cb  , FALSE,
+                          "two [args]", "command 2", "description 2"),
+        MRP_TOKENIZED_CMD("three", three_cb, FALSE,
+                          "three [args]", "command 3", "description 3"),
+        MRP_TOKENIZED_CMD("four" , four_cb , TRUE,
+                          "four [args]", "command 4", "description 4"),
+        MRP_TOKENIZED_CMD("update" , resolve_cb , TRUE,
+                          "update <target>", "update target", "update target"),
+        MRP_TOKENIZED_CMD("auth-test", auth_cb, TRUE,
+                          "auth-test [@backend] target mode id [token]",
+                          "test authentication", "test authentication"),
+        MRP_TOKENIZED_CMD("ping", ping_cb, FALSE,
+                          "ping domain",
+                          "ping the given domain", "ping a domain"),
+        MRP_TOKENIZED_CMD("invoke", invoke_cb, TRUE,
+                          "invoke domain method [É™rguments]",
+                          "invoke the given domain method",
+                          "invoke a domain method")
+});
+
+
+void one_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    int i;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    for (i = 0; i < argc; i++) {
+        printf("%s(): #%d: '%s'\n", __FUNCTION__, i, argv[i]);
+    }
+}
+
+
+void two_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    int i;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    for (i = 0; i < argc; i++) {
+        printf("%s(): #%d: '%s'\n", __FUNCTION__, i, argv[i]);
+    }
+}
+
+
+void three_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    int i;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    for (i = 0; i < argc; i++) {
+        printf("%s(): #%d: '%s'\n", __FUNCTION__, i, argv[i]);
+    }
+}
+
+
+void four_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    int i;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    for (i = 0; i < argc; i++) {
+        printf("%s(): #%d: '%s'\n", __FUNCTION__, i, argv[i]);
+    }
+}
+
+
+void resolve_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    mrp_context_t  *ctx = c->ctx;
+    const char     *target;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+
+    if (argc == 3) {
+        target = argv[2];
+
+        if (ctx->r != NULL) {
+            if (mrp_resolver_update_target(ctx->r, target, NULL) > 0)
+                printf("'%s' updated OK.\n", target);
+            else
+                printf("Failed to update '%s'.\n", target);
+        }
+        else
+            printf("Resolver/ruleset is not available.\n");
+    }
+    else {
+        printf("Usage: %s %s <target-name>\n", argv[0], argv[1]);
+    }
+}
+
+
+void auth_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    mrp_context_t  *ctx = c->ctx;
+    const char     *backend, *target, *id, *token, *p;
+    int             idx, mode, status;
+
+    MRP_UNUSED(user_data);
+
+    if (argc < 4) {
+    error:
+        printf("Usage: %s %s [@backend] target mode id [token]\n", argv[0],
+               argv[1]);
+        return;
+    }
+
+    if (argv[2][0] == '@') {
+        if (argc < 5)
+            goto error;
+
+        backend = argv[2] + 1;
+        idx     = 3;
+    }
+    else {
+        backend = NULL;
+        idx     = 2;
+    }
+
+    target = argv[idx++];
+
+    p = argv[idx++];
+
+    for (mode = 0; *p; p++) {
+        switch(*p) {
+        case 'r': mode |= MRP_AUTH_MODE_READ;  break;
+        case 'w': mode |= MRP_AUTH_MODE_WRITE; break;
+        case 'x': mode |= MRP_AUTH_MODE_EXEC;  break;
+        case '-':                              break;
+        default:
+            printf("Invalid character '%c' in mode.\n", *p);
+            goto error;
+        }
+    }
+
+    if (mode == 0)
+        mode = MRP_AUTH_MODE_READ;
+
+    id = argv[idx++];
+
+    if (idx >= argc - 1)
+        token = NULL;
+    else {
+        if (idx == argc - 1)
+            token = argv[idx];
+        else
+            goto error;
+    }
+
+    status = mrp_authenticate(ctx, backend, target, mode, id, token);
+
+    printf("authentication status: %d\n", status);
+}
+
+
+void pong_cb(int error, int retval, int narg, mrp_domctl_arg_t *args,
+             void *user_data)
+{
+    mrp_console_t *c = (mrp_console_t *)user_data;
+    int            i;
+
+    MRP_UNUSED(c);
+
+    if (error) {
+        printf("ping failed with error code %d\n", error);
+    }
+
+    printf("pong (return value %d)\n", retval);
+
+    for (i = 0; i < narg; i++) {
+        switch (args[i].type) {
+        case MRP_DOMCTL_STRING:
+            printf("    #%d: %s\n", i, args[i].str);
+            break;
+        case MRP_DOMCTL_UINT32:
+            printf("    #%d: %u\n", i, args[i].u32);
+            break;
+        default:
+            if (MRP_DOMCTL_IS_ARRAY(args[i].type)) {
+                uint32_t j;
+
+                printf("    #%d: array of %u items:\n", i, args[i].size);
+                for (j = 0; j < args[i].size; j++) {
+                    switch (MRP_DOMCTL_ARRAY_TYPE(args[i].type)) {
+                    case MRP_DOMCTL_STRING:
+                        printf("        #%d: '%s'\n", j,
+                               ((char **)args[i].arr)[j]);
+                        break;
+                    case MRP_DOMCTL_UINT32:
+                        printf("        #%d: %u\n", j,
+                               ((uint32_t *)args[i].arr)[j]);
+                        break;
+                    default:
+                        printf("        #%d: <type 0x%x\n", j,
+                               MRP_DOMCTL_ARRAY_TYPE(args[i].type));
+                        break;
+                    }
+                }
+            }
+            else
+                printf("    <type 0x%x>\n", args[i].type);
+        }
+    }
+}
+
+
+void ping_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    static uint32_t   cnt = 1;
+    const char       *domain;
+    char             *strings[] = { "foo", "bar", "foobar", "barfoo" };
+    uint32_t          uints[]   = { 69, 96, 696, 969 };
+    mrp_domctl_arg_t  args[32];
+    int               narg, i;
+
+    MRP_UNUSED(user_data);
+
+    if (argc < 3) {
+        printf("Usage: %s domain\n", argv[0]);
+        return;
+    }
+
+    domain = argv[2];
+    narg   = MRP_ARRAY_SIZE(args);
+
+    args[0].type = MRP_DOMCTL_UINT32;
+    args[0].u32  = cnt++;
+    args[1].type = MRP_DOMCTL_ARRAY(STRING);
+    args[1].arr  = strings;
+    args[1].size = MRP_ARRAY_SIZE(strings);
+    args[2].type = MRP_DOMCTL_ARRAY(UINT32);
+    args[2].arr  = uints;
+    args[2].size = MRP_ARRAY_SIZE(uints);
+
+    for (i = 3; i < narg; i++) {
+        if (i + 2 < argc) {
+            args[i].type = MRP_DOMCTL_STRING;
+            args[i].str  = argv[i + 2];
+        }
+        else {
+            args[i].type = MRP_DOMCTL_UINT32;
+            args[i].u32  = i;
+        }
+    }
+
+    if (!mrp_invoke_domain(c->ctx, domain, "ping", narg, args, pong_cb, c))
+        printf("Failed to ping domain '%s'.\n", domain);
+}
+
+
+void invoke_reply(int error, int retval, int narg, mrp_domctl_arg_t *args,
+                  void *user_data)
+{
+    mrp_console_t *c = (mrp_console_t *)user_data;
+    int            i;
+
+    if (error) {
+        mrp_console_printf(c, "invoked method failed with error code %d\n",
+                           error);
+        return;
+    }
+
+    mrp_console_printf(c, "invoked method returned (return value %d)\n", retval);
+
+    for (i = 0; i < narg; i++) {
+        switch (args[i].type) {
+        case MRP_DOMCTL_STRING:
+            mrp_console_printf(c, "    #%d: %s\n", i, args[i].str);
+            break;
+        case MRP_DOMCTL_UINT16:
+            mrp_console_printf(c, "    #%d: %u\n", i, args[i].u16);
+            break;
+        case MRP_DOMCTL_INT16:
+            mrp_console_printf(c, "    #%d: %u\n", i, args[i].s16);
+            break;
+        case MRP_DOMCTL_UINT32:
+            mrp_console_printf(c, "    #%d: %u\n", i, args[i].u32);
+            break;
+        case MRP_DOMCTL_INT32:
+            mrp_console_printf(c, "    #%d: %u\n", i, args[i].s32);
+            break;
+        default:
+            mrp_console_printf(c, "    #%d: <type 0x%x\n", i, args[i].type);
+            break;
+        }
+    }
+}
+
+
+void invoke_cb(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    const char       *domain, *method, *type, *value;
+    mrp_domctl_arg_t  args[32];
+    int               tlen, narg, i;
+
+    MRP_UNUSED(user_data);
+
+    if (argc < 4) {
+        printf("Usage: %s %s <domain> <method> [args]\n", argv[0], argv[1]);
+        return;
+    }
+
+    domain = argv[2];
+    method = argv[3];
+    narg   = MRP_ARRAY_SIZE(args);
+
+    for (i = 4, narg = 0;
+         i < argc && narg < (int)MRP_ARRAY_SIZE(args);
+         i++, narg++) {
+        type  = argv[i];
+        value = strchr(type, ':');
+
+        if (value == NULL) {
+            value = type;
+            type  = "string";
+            tlen  = 6;
+        }
+        else {
+            tlen  = value - type;
+            value++;
+        }
+
+        if (!strncmp(type, "string", tlen)) {
+            args[narg].type = MRP_DOMCTL_STRING;
+            args[narg].str  = value;
+        }
+        else if (!strncmp(type, "u16"     , tlen) ||
+                 !strncmp(type, "uint16_t", tlen)) {
+            args[narg].type = MRP_DOMCTL_UINT16;
+            args[narg].u16  = (uint16_t)strtoul(value, NULL, 0);
+        }
+        else if (!strncmp(type, "u16"     , tlen) ||
+                 !strncmp(type, "uint16_t", tlen)) {
+            args[narg].type = MRP_DOMCTL_INT16;
+            args[narg].s16  = (int16_t)strtol(value, NULL, 0);
+        }
+        else if (!strncmp(type, "u32"     , tlen) ||
+                 !strncmp(type, "uint32_t", tlen)) {
+            args[narg].type = MRP_DOMCTL_UINT32;
+            args[narg].u32  = (uint32_t)strtoul(value, NULL, 0);
+        }
+        else if (!strncmp(type, "u32"     , tlen) ||
+                 !strncmp(type, "uint32_t", tlen)) {
+            args[narg].type = MRP_DOMCTL_INT32;
+            args[narg].s32  = (int32_t)strtol(value, NULL, 0);
+        }
+        else {
+            printf("invalid typecast in %s\n", argv[i]);
+            return;
+        }
+    }
+
+    printf("Invoking domain method '%s.%s' with %d args...\n", domain, method,
+           narg);
+
+    if (!mrp_invoke_domain(c->ctx, domain, method, narg, args, invoke_reply, c))
+        printf("Failed to invoke '%s.%s'.\n", domain, method);
+}
+
+
+MRP_EXPORTABLE(char *, method1, (int arg1, char *arg2, double arg3))
+{
+    MRP_UNUSED(arg1);
+    MRP_UNUSED(arg2);
+    MRP_UNUSED(arg3);
+
+    mrp_log_info("%s()...", __FUNCTION__);
+
+    return "method1 was here...";
+}
+
+static int boilerplate1(mrp_plugin_t *plugin,
+                        const char *name, mrp_script_env_t *env)
+{
+    MRP_UNUSED(plugin);
+    MRP_UNUSED(name);
+    MRP_UNUSED(env);
+
+    method1(1, "foo", 9.81);
+
+    return TRUE;
+}
+
+MRP_EXPORTABLE(int, method2, (char *arg1, double arg2, int arg3))
+{
+    MRP_UNUSED(arg1);
+    MRP_UNUSED(arg2);
+    MRP_UNUSED(arg3);
+
+    mrp_log_info("%s()...", __FUNCTION__);
+
+    return 313;
+}
+
+static int boilerplate2(mrp_plugin_t *plugin,
+                        const char *name, mrp_script_env_t *env)
+{
+    MRP_UNUSED(plugin);
+    MRP_UNUSED(name);
+    MRP_UNUSED(env);
+
+    return -1;
+}
+
+
+MRP_IMPORTABLE(char *, method1ptr, (int arg1, char *arg2, double arg3));
+MRP_IMPORTABLE(int, method2ptr, (char *arg1, double arg2, int arg3));
+
+#if 0
+static int export_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t methods[] = {
+        { "method1", "char *(int arg1, char *arg2, double arg3)",
+          method1, boilerplate1, plugin },
+        { "method2", "int (char *arg1, double arg2, int arg3)",
+          method2, boilerplate2, plugin },
+        { NULL, NULL, NULL, NULL, NULL }
+    };
+    mrp_method_descr_t *m;
+
+    for (m = methods; m->name != NULL; m++)
+        if (mrp_export_method(m) < 0)
+            return FALSE;
+        else
+            mrp_log_info("Successfully exported method '%s'...", m->name);
+
+    return TRUE;
+}
+
+
+static int remove_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t methods[] = {
+        { "method1", "char *(int arg1, char *arg2, double arg3)",
+          method1, boilerplate1, plugin },
+        { "method2", "int (char *arg1, double arg2, int arg3)",
+          method2, boilerplate2, plugin },
+        { NULL, NULL, NULL, NULL, NULL }
+    };
+    mrp_method_descr_t *m;
+
+    for (m = methods; m->name != NULL; m++)
+        if (mrp_remove_method(m) < 0)
+            mrp_log_info("Failed to remove method '%s'...", m->name);
+        else
+            mrp_log_info("Failed to remove method '%s'...", m->name);
+
+    return TRUE;
+}
+
+
+static int import_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t methods[] = {
+        { "method1", "char *(int arg1, char *arg2, double arg3)",
+          method1, boilerplate1, plugin },
+        { "method2", "int (char *arg1, double arg2, int arg3)",
+          method2, boilerplate2, plugin },
+        { NULL, NULL, NULL, NULL, NULL }
+    };
+
+    void *native_check[] = { method1, method2 };
+    void *script_check[] = { boilerplate1, boilerplate2 };
+    int   i;
+
+    mrp_method_descr_t *m;
+
+    const char    *name, *sig;
+    char           buf[512];
+    void          *native;
+    int          (*script)(mrp_plugin_t *, const char *, mrp_script_env_t *);
+
+    for (i = 0, m = methods; m->name != NULL; i++, m++) {
+        name = m->name;
+        sig  = m->signature;
+
+        if (mrp_import_method(name, sig, &native, &script) < 0)
+            return FALSE;
+
+        if (native != native_check[i] || script != script_check[i])
+            return FALSE;
+
+        mrp_log_info("%s imported as %p, %p...", name, native, script);
+
+        snprintf(buf, sizeof(buf), "%s.%s", plugin->instance, m->name);
+        name = buf;
+
+        if (mrp_import_method(name, sig, &native, &script) < 0)
+            return FALSE;
+
+        if (native != native_check[i] || script != script_check[i])
+            return FALSE;
+
+        mrp_log_info("%s imported as %p, %p...", name, native, script);
+    }
+
+    return TRUE;
+}
+
+
+static int release_methods(mrp_plugin_t *plugin)
+{
+    mrp_method_descr_t methods[] = {
+        { "method1", "char *(int arg1, char *arg2, double arg3)",
+          method1, boilerplate1, plugin },
+        { "method2", "int (char *arg1, double arg2, int arg3)",
+          method2, boilerplate2, plugin },
+        { NULL, NULL, NULL, NULL, NULL }
+    };
+    const char *name, *sig;
+    char        buf[512];
+    mrp_method_descr_t *m;
+    void *native, *natives[] = { method1     , method2      };
+    int (*script)(mrp_plugin_t *, const char *, mrp_script_env_t *);
+    void *scripts[] = { boilerplate1, boilerplate2 };
+    int   i;
+
+    for (i = 0, m = methods; m->name != NULL; i++, m++) {
+        name   = m->name;
+        sig    = m->signature;
+        native = natives[i];
+        script = scripts[i];
+
+        if (mrp_release_method(name, sig, &native, &script) < 0)
+            mrp_log_error("Failed to release method '%s'...", name);
+        else
+            mrp_log_info("Successfully released method '%s'...", name);
+
+        snprintf(buf, sizeof(buf), "%s.%s", plugin->instance, m->name);
+        name   = buf;
+        native = natives[i];
+        script = scripts[i];
+
+        if (mrp_release_method(name, sig, &native, &script) < 0)
+            mrp_log_error("Failed to release method '%s'...", name);
+        else
+            mrp_log_info("Successfully released method '%s'...", name);
+    }
+
+    return TRUE;
+}
+
+#endif
+
+int test_imports(void)
+{
+    if (method1ptr == NULL || method2ptr == NULL) {
+        mrp_log_error("Failed to import methods...");
+        return FALSE;
+    }
+
+    mrp_log_info("method1ptr returned '%s'...", method1ptr(1, "foo", 3.141));
+    mrp_log_info("method2ptr returned '%d'...", method2ptr("bar", 9.81, 2));
+
+    return TRUE;
+}
+
+
+static void event_cb(mrp_event_watch_t *w, uint32_t id, int format,
+                     void *event_data, void *user_data)
+{
+    mrp_plugin_t *plugin = (mrp_plugin_t *)user_data;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(format);
+
+    mrp_log_info("%s: got event 0x%x (%s):", plugin->instance, id,
+                 mrp_event_name(id));
+    mrp_msg_dump(event_data, stdout);
+}
+
+
+static int subscribe_events(mrp_plugin_t *plugin)
+{
+    mrp_mainloop_t   *ml     = plugin->ctx->ml;
+    mrp_event_bus_t  *bus    = mrp_event_bus_get(ml, MRP_PLUGIN_BUS);
+    test_data_t      *data   = (test_data_t *)plugin->data;
+    mrp_event_mask_t  events = MRP_MASK_EMPTY;
+
+
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_LOADED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_STARTED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_FAILED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_STOPPING));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_STOPPED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_UNLOADED));
+
+    data->w = mrp_event_add_watch_mask(bus, &events, event_cb, plugin);
+
+    return (data->w != NULL);
+}
+
+
+static void unsubscribe_events(mrp_plugin_t *plugin)
+{
+    test_data_t *data = (test_data_t *)plugin->data;
+
+    mrp_event_del_watch(data->w);
+    data->w = NULL;
+}
+
+
+static int test_init(mrp_plugin_t *plugin)
+{
+    mrp_plugin_arg_t *args, *arg;
+    mrp_json_t       *json;
+    test_data_t      *data;
+
+    mrp_log_info("%s() called for test instance '%s'...", __FUNCTION__,
+                 plugin->instance);
+
+    args = plugin->args;
+    json = args[ARG_OBJECT].obj.json;
+    printf(" string1:  %s\n", args[ARG_STRING1].str);
+    printf(" string2:  %s\n", args[ARG_STRING2].str);
+    printf("boolean1:  %s\n", args[ARG_BOOLEAN1].bln ? "TRUE" : "FALSE");
+    printf("boolean2:  %s\n", args[ARG_BOOLEAN2].bln ? "TRUE" : "FALSE");
+    printf("  uint32:  %u\n", args[ARG_UINT321].u32);
+    printf("   int32:  %d\n", args[ARG_INT321].i32);
+    printf("  double:  %f\n", args[ARG_DOUBLE1].dbl);
+    printf("init fail: %s\n", args[ARG_FAILINIT].bln ? "TRUE" : "FALSE");
+    printf("exit fail: %s\n", args[ARG_FAILEXIT].bln ? "TRUE" : "FALSE");
+    printf("   object: %s\n", mrp_json_object_to_string(json));
+
+    mrp_plugin_foreach_undecl_arg(&args[ARG_REST], arg) {
+        mrp_log_info("got argument %s of type 0x%x", arg->key, arg->type);
+    }
+
+    {
+        char *rkeys[] = { "foo", "bar", "foobar", "barfoo", NULL };
+        int   i;
+
+        for (i = 0; rkeys[i] != NULL; i++) {
+            arg = mrp_plugin_find_undecl_arg(&args[ARG_REST], rkeys[i], 0);
+
+            if (arg != NULL)
+                mrp_log_info("found undeclared arg '%s' (type 0x%x)", arg->key,
+                             arg->type);
+            else
+                mrp_log_info("undeclared arg '%s' not found", rkeys[i]);
+        }
+    }
+
+
+#if 0
+    if (!export_methods(plugin))
+        return FALSE;
+
+    if (!import_methods(plugin))
+        return FALSE;
+#endif
+
+    data = mrp_allocz(sizeof(*data));
+
+    if (data == NULL) {
+        mrp_log_error("Failed to allocate private data for test plugin "
+                      "instance %s.", plugin->instance);
+        return FALSE;
+    }
+    else
+        plugin->data = data;
+
+    test_imports();
+
+    subscribe_events(plugin);
+
+    return !args[ARG_FAILINIT].bln;
+}
+
+
+static void test_exit(mrp_plugin_t *plugin)
+{
+    mrp_log_info("%s() called for test instance '%s'...", __FUNCTION__,
+                 plugin->instance);
+
+    unsubscribe_events(plugin);
+
+#if 0
+    release_methods(plugin);
+    remove_methods(plugin);
+#endif
+
+    /*return !args[ARG_FAILINIT].bln;*/
+}
+
+
+#define TEST_DESCRIPTION "A primitive plugin just to test the plugin infra."
+#define TEST_HELP        "Just a load/unload test."
+#define TEST_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define TEST_AUTHORS     "D. Duck <donald.duck@ducksburg.org>"
+
+#define DEFAULT_OBJECT   "{\n"                             \
+    "    'foo':   'this is json.foo',\n"                   \
+    "    'bar':   'this is json.bar',\n"                   \
+    "    'one':   1,\n"                                    \
+    "    'two':   2,\n"                                    \
+    "    'pi':    3.141,\n"                                \
+    "    'array': [ 1, 2, 'three', 'four', 5 ]\n"          \
+    "}\n"
+
+static mrp_plugin_arg_t args[] = {
+    MRP_PLUGIN_ARGIDX(ARG_STRING1 , STRING, "string1" , "default string1"),
+    MRP_PLUGIN_ARGIDX(ARG_STRING2 , STRING, "string2" , "default string2"),
+    MRP_PLUGIN_ARGIDX(ARG_BOOLEAN1, BOOL  , "boolean1", TRUE             ),
+    MRP_PLUGIN_ARGIDX(ARG_BOOLEAN2, BOOL  , "boolean2", FALSE            ),
+    MRP_PLUGIN_ARGIDX(ARG_UINT321 , UINT32, "uint32"  , 3141             ),
+    MRP_PLUGIN_ARGIDX(ARG_INT321  , INT32 , "int32"   , -3141            ),
+    MRP_PLUGIN_ARGIDX(ARG_DOUBLE1 , DOUBLE, "double"  , -3.141           ),
+    MRP_PLUGIN_ARGIDX(ARG_FAILINIT, BOOL  , "failinit", FALSE            ),
+    MRP_PLUGIN_ARGIDX(ARG_FAILEXIT, BOOL  , "failexit", FALSE            ),
+    MRP_PLUGIN_ARGIDX(ARG_OBJECT  , OBJECT, "object"  , DEFAULT_OBJECT   ),
+    MRP_PLUGIN_ARGIDX(ARG_REST    , UNDECL, NULL      , NULL             ),
+};
+
+static mrp_method_descr_t exports[] = {
+    MRP_GENERIC_METHOD("method1", method1, boilerplate1),
+    MRP_GENERIC_METHOD("method2", method2, boilerplate2),
+};
+
+static mrp_method_descr_t imports[] = {
+    MRP_IMPORT_METHOD("method1", method1ptr),
+    MRP_IMPORT_METHOD("method2", method2ptr),
+};
+
+
+MURPHY_REGISTER_PLUGIN("test",
+                       TEST_VERSION, TEST_DESCRIPTION, TEST_AUTHORS, TEST_HELP,
+                       MRP_MULTIPLE, test_init, test_exit,
+                       args, MRP_ARRAY_SIZE(args),
+                       exports, MRP_ARRAY_SIZE(exports),
+                       imports, MRP_ARRAY_SIZE(imports),
+                       &test_group);
diff --git a/src/plugins/resource-dbus/org.Murphy.conf b/src/plugins/resource-dbus/org.Murphy.conf
new file mode 100644 (file)
index 0000000..2d4542c
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0"?>
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<busconfig>
+        <policy user="root">
+                <allow own="org.Murphy"/>
+                <allow receive_sender="org.Murphy"/>
+                <allow send_destination="org.Murphy"/>
+        </policy>
+        <policy context="default">
+                <allow receive_sender="org.Murphy"/>
+                <allow send_destination="org.Murphy"/>
+        </policy>
+</busconfig>
diff --git a/src/plugins/resource-native/Makefile b/src/plugins/resource-native/Makefile
new file mode 100644 (file)
index 0000000..cfeca66
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C ../.. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C ../.. all
+endif
diff --git a/src/plugins/resource-native/libmurphy-resource/api_test.c b/src/plugins/resource-native/libmurphy-resource/api_test.c
new file mode 100644 (file)
index 0000000..2e80915
--- /dev/null
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <murphy/plugins/resource-native/libmurphy-resource/resource-api.h>
+
+typedef struct my_app_data {
+    mrp_res_context_t *cx;
+    mrp_res_resource_set_t *rs;
+} my_app_data;
+
+
+static bool accept_input;
+
+/* state callback for murphy connection */
+static void state_callback(mrp_res_context_t *cx,
+               mrp_res_error_t,
+               void *ud);
+
+/* callback for resource set update */
+static void resource_callback(mrp_res_context_t *cx,
+                  const mrp_res_resource_set_t *rs,
+                  void *userdata);
+
+void create_resources(my_app_data *app_data)
+{
+    mrp_res_resource_t *resource = NULL;
+    mrp_res_attribute_t *attr;
+
+    /* if we already have a decent set, just re-acquire it */
+    if (app_data->rs) {
+        mrp_res_acquire_resource_set(app_data->rs);
+        return;
+    }
+
+    /* otherwise create resource set and resources */
+    app_data->rs = mrp_res_create_resource_set(app_data->cx,
+                          "player",
+                          resource_callback,
+                          (void*)app_data);
+
+    if (app_data->rs == NULL) {
+        printf("Couldn't create resource set\n");
+        return;
+    }
+
+    if (!mrp_res_set_autorelease(TRUE, app_data->rs)) {
+        printf("Could not set autorelease flag!\n");
+        return;
+    }
+
+    resource = mrp_res_create_resource(app_data->rs,
+                      "audio_playback",
+                      true,
+                      false);
+
+    if (resource == NULL) {
+        printf("Couldn't create audio resource\n");
+        mrp_res_delete_resource_set(app_data->rs);
+        return;
+    }
+
+    /* set a resource attribute */
+
+    attr = mrp_res_get_attribute_by_name(resource, "role");
+
+    if (attr) {
+        mrp_res_set_attribute_string(attr, "call");
+    }
+
+    resource = mrp_res_create_resource(app_data->rs,
+                      "video_playback",
+                      true,
+                      false);
+
+    if (resource == NULL) {
+        printf("Couldn't create video resource\n");
+        mrp_res_delete_resource_set(app_data->rs);
+        return;
+    }
+
+    printf("created the resource set!\n");
+}
+
+void acquire_resources(my_app_data *app_data)
+{
+    /* acquire the resources */
+    if (app_data->rs)
+        mrp_res_acquire_resource_set(app_data->rs);
+    else
+        printf("No release set created!\n");
+}
+
+void giveup_resources(my_app_data *app_data)
+{
+    /* release resources */
+    if (app_data->rs)
+        mrp_res_release_resource_set(app_data->rs);
+    else
+        printf("No release set acquired!\n");
+}
+
+static void state_callback(mrp_res_context_t *context,
+               mrp_res_error_t err,
+               void *userdata)
+{
+    int i = 0, j = 0;
+    const mrp_res_string_array_t *app_classes = NULL;
+    const mrp_res_resource_set_t *rs;
+    mrp_res_string_array_t *attributes = NULL;
+    mrp_res_attribute_t *attr;
+    bool system_handles_audio = FALSE;
+    bool system_handles_video = FALSE;
+    mrp_res_resource_t *resource;
+
+    my_app_data *app_data = (my_app_data *) userdata;
+
+    if (err != MRP_RES_ERROR_NONE) {
+        printf("error message received from Murphy\n");
+        return;
+    }
+
+    switch (context->state) {
+
+        case MRP_RES_CONNECTED:
+
+            printf("connected to murphy\n");
+
+            if ((app_classes =
+                        mrp_res_list_application_classes(context)) != NULL) {
+                printf("listing all application classes in the system\n");
+
+                for (i = 0; i < app_classes->num_strings; i++) {
+                    printf("app class %d is %s\n", i, app_classes->strings[i]);
+                }
+            }
+
+            if ((rs = mrp_res_list_resources(context)) != NULL) {
+                mrp_res_string_array_t *resource_names;
+
+                printf("listing all resources available in the system\n");
+
+                resource_names = mrp_res_list_resource_names(rs);
+
+                if (!resource_names) {
+                    printf("No resources available in the system!\n");
+                    return;
+                }
+
+                for (i = 0; i < resource_names->num_strings; i++) {
+
+                    resource = mrp_res_get_resource_by_name(rs,
+                            resource_names->strings[i]);
+
+                    if (!resource)
+                        continue;
+
+                    printf("resource %d is %s\n", i, resource->name);
+                    if (strcmp(resource->name, "audio_playback") == 0)
+                        system_handles_audio = TRUE;
+                    if (strcmp(resource->name, "video_playback") == 0)
+                        system_handles_video = TRUE;
+
+                    attributes = mrp_res_list_attribute_names(resource);
+
+                    if (!attributes)
+                        continue;
+
+                    for (j = 0; j < attributes->num_strings; j++) {
+                        attr = mrp_res_get_attribute_by_name(resource,
+                                attributes->strings[j]);
+
+                        if (!attr)
+                            continue;
+
+                        printf("attr %s has ", attr->name);
+                        switch(attr->type) {
+                            case mrp_string:
+                                printf("type string and value %s\n",
+                                        attr->string);
+                                break;
+                            case mrp_int32:
+                                printf("type int32 and value %d\n",
+                                        (int) attr->integer);
+                                break;
+                            case mrp_uint32:
+                                printf("type uint32 and value %u\n",
+                                        attr->unsignd);
+                                break;
+                            case mrp_double:
+                                printf("type double and value %f\n",
+                                        attr->floating);
+                                break;
+                            default:
+                                printf("type unknown\n");
+                                break;
+                        }
+                    }
+                    mrp_res_free_string_array(attributes);
+                }
+                mrp_res_free_string_array(resource_names);
+            }
+
+            if (system_handles_audio && system_handles_video) {
+                printf("system provides all necessary resources\n");
+                accept_input = TRUE;
+            }
+
+            break;
+
+        case MRP_RES_DISCONNECTED:
+            printf("disconnected from murphy\n");
+            mrp_res_delete_resource_set(app_data->rs);
+            mrp_res_destroy(app_data->cx);
+            exit(1);
+    }
+}
+
+
+static char *state_to_str(mrp_res_resource_state_t st)
+{
+    char *state = "unknown";
+    switch (st) {
+        case MRP_RES_RESOURCE_ACQUIRED:
+            state = "acquired";
+            break;
+        case MRP_RES_RESOURCE_LOST:
+            state = "lost";
+            break;
+        case MRP_RES_RESOURCE_AVAILABLE:
+            state = "available";
+            break;
+        case MRP_RES_RESOURCE_PENDING:
+            state = "pending";
+            break;
+    }
+    return state;
+}
+
+static void resource_callback(mrp_res_context_t *cx,
+                  const mrp_res_resource_set_t *rs,
+                  void *userdata)
+{
+    my_app_data *my_data = (my_app_data *) userdata;
+    mrp_res_resource_t *res;
+    mrp_res_attribute_t *attr;
+
+    MRP_UNUSED(cx);
+
+    printf("> resource_callback\n");
+
+    if (!mrp_res_equal_resource_set(rs, my_data->rs))
+        return;
+
+    /* here compare the resource set difference */
+
+    res = mrp_res_get_resource_by_name(rs, "audio_playback");
+
+    if (!res) {
+        printf("audio_playback not present in resource set\n");
+        return;
+    }
+
+    printf("resource set state: %s\n", state_to_str(rs->state));
+
+    printf("resource 0 name '%s' -> '%s'\n", res->name, state_to_str(res->state));
+
+    res = mrp_res_get_resource_by_name(rs, "video_playback");
+
+    if (!res) {
+        printf("video_playback not present in resource set\n");
+        return;
+    }
+
+    printf("resource 1 name '%s' -> '%s'\n", res->name, state_to_str(res->state));
+
+    /* let's copy the changed set for ourselves */
+
+    /* Delete must not mean releasing the set! Otherwise this won't work.
+     * It's up to the user to make sure that there's a working reference
+     * to the resource set.
+     */
+    mrp_res_delete_resource_set(my_data->rs);
+
+    /* copying must also have no semantic meaning */
+    my_data->rs = mrp_res_copy_resource_set(rs);
+
+    /* print the current role attribute */
+
+    res = mrp_res_get_resource_by_name(rs, "audio_playback");
+    attr = mrp_res_get_attribute_by_name(res, "role");
+
+    if (res && attr)
+        printf("attribute '%s' has role '%s'\n", res->name, attr->string);
+
+    /* acquiring a copy of an existing release set means:
+     *  - acquired state: update, since otherwise no meaning
+     *  - pending state: acquire, since previous state not known/meaningless
+     *  - lost state: update, since otherwise will fail
+     *  - available: update or acquire
+     */
+}
+
+static void handle_input(mrp_io_watch_t *watch, int fd, mrp_io_event_t events,
+                         void *user_data)
+{
+    mrp_mainloop_t *ml = mrp_get_io_watch_mainloop(watch);
+    char buf[1024];
+    int size;
+
+    my_app_data *app_data = (my_app_data *) user_data;
+
+    memset(buf, 0, sizeof(buf));
+
+    if (events & MRP_IO_EVENT_IN) {
+        size = read(fd, buf, sizeof(buf) - 1);
+
+        if (size > 0) {
+            buf[size] = '\0';
+            printf("read line %s\n", buf);
+        }
+    }
+
+    if (events & MRP_IO_EVENT_HUP) {
+        mrp_del_io_watch(watch);
+    }
+
+    if (accept_input) {
+        switch (buf[0]) {
+            case 'C':
+                create_resources(app_data);
+                break;
+            case 'A':
+                acquire_resources(app_data);
+                break;
+            case 'D':
+                giveup_resources(app_data);
+                break;
+            case 'Q':
+                if (app_data->rs)
+                    mrp_res_delete_resource_set(app_data->rs);
+                if (ml)
+                    mrp_mainloop_quit(ml, 0);
+                break;
+            default:
+                printf("'C' to create resource set\n'A' to acquire\n'D' to release\n'Q' to quit\n");
+                break;
+       }
+   }
+   else {
+       printf("not connected to Murphy\n");
+   }
+}
+
+int main(int argc, char **argv)
+{
+    mrp_mainloop_t *ml;
+    int mask;
+    mrp_io_watch_t *watch;
+
+    my_app_data app_data;
+
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    if ((ml = mrp_mainloop_create()) == NULL)
+        exit(1);
+
+    app_data.rs = NULL;
+    app_data.cx = mrp_res_create(ml, state_callback, &app_data);
+
+    mask = MRP_IO_EVENT_IN | MRP_IO_EVENT_HUP | MRP_IO_EVENT_ERR;
+    watch = mrp_add_io_watch(ml, fileno(stdin), (mrp_io_event_t) mask,
+            handle_input, &app_data);
+
+    if (!watch)
+        exit(1);
+
+    /* start looping */
+    mrp_mainloop_run(ml);
+
+    mrp_res_destroy(app_data.cx);
+    mrp_mainloop_destroy(ml);
+
+    app_data.cx = NULL;
+    app_data.rs = NULL;
+
+    return 0;
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/attribute.c b/src/plugins/resource-native/libmurphy-resource/attribute.c
new file mode 100644 (file)
index 0000000..afc9eeb
--- /dev/null
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include "resource-api.h"
+
+#include "attribute.h"
+#include "string_array.h"
+
+void mrp_attribute_array_free(mrp_res_attribute_t *arr,
+        uint32_t dim)
+{
+    uint32_t i;
+    mrp_res_attribute_t *attr;
+
+    if (arr) {
+        for (i = 0; i < dim; i++) {
+            attr = arr + i;
+
+            mrp_free((void *)attr->name);
+
+            if (attr->type == mrp_string)
+                mrp_free((void *)attr->string);
+        }
+        mrp_free(arr);
+    }
+}
+
+
+mrp_res_attribute_t *mrp_attribute_array_dup(uint32_t dim,
+                                 mrp_res_attribute_t *arr)
+{
+    size_t size;
+    uint32_t i;
+    mrp_res_attribute_t *sattr, *dattr;
+    mrp_res_attribute_t *dup;
+    int err;
+
+    size = (sizeof(mrp_res_attribute_t) * (dim + 1));
+
+    if (!(dup = mrp_allocz(size))) {
+        err = ENOMEM;
+        goto failed;
+    }
+
+    for (i = 0; i < dim; i++) {
+        sattr = arr + i;
+        dattr = dup + i;
+
+        if (!(dattr->name = mrp_strdup(sattr->name))) {
+            err = ENOMEM;
+            goto failed;
+        }
+
+        switch ((dattr->type = sattr->type)) {
+        case mrp_string:
+            if (!(dattr->string = mrp_strdup(sattr->string))) {
+                err = ENOMEM;
+                goto failed;
+            }
+            break;
+        case mrp_int32:
+            dattr->integer = sattr->integer;
+            break;
+        case mrp_uint32:
+            dattr->type = mrp_uint32;
+            dattr->unsignd = sattr->unsignd;
+            break;
+        case mrp_double:
+            dattr->type = mrp_double;
+            dattr->floating = sattr->floating;
+            break;
+        default:
+            err = EINVAL;
+            goto failed;
+        }
+    }
+
+    return dup;
+
+ failed:
+    mrp_attribute_array_free(dup, dim);
+    errno = err;
+    return NULL;
+}
+
+/* public API */
+
+mrp_res_string_array_t * mrp_res_list_attribute_names(
+        const mrp_res_resource_t *res)
+{
+    int i;
+    mrp_res_string_array_t *ret;
+    mrp_res_context_t *cx = NULL;
+
+    if (!res)
+        return NULL;
+
+    cx = res->priv->set->priv->cx;
+
+    if (!cx)
+        return NULL;
+
+    ret = mrp_allocz(sizeof(mrp_res_string_array_t));
+
+    if (!ret)
+        return NULL;
+
+    ret->num_strings = res->priv->num_attributes;
+    ret->strings = mrp_allocz_array(const char *, res->priv->num_attributes);
+
+    if (!ret->strings) {
+        mrp_free(ret);
+        return NULL;
+    }
+
+    for (i = 0; i < res->priv->num_attributes; i++) {
+        ret->strings[i] = mrp_strdup(res->priv->attrs[i].name);
+        if (!ret->strings[i]) {
+            ret->num_strings = i;
+            mrp_res_free_string_array(ret);
+            return NULL;
+        }
+    }
+
+    return ret;
+}
+
+
+mrp_res_attribute_t * mrp_res_get_attribute_by_name(
+        mrp_res_resource_t *res, const char *name)
+{
+    int i;
+
+    if (!res)
+        return NULL;
+
+    for (i = 0; i < res->priv->num_attributes; i++) {
+        if (strcmp(name, res->priv->attrs[i].name) == 0) {
+            return &res->priv->attrs[i];
+        }
+    }
+
+    return NULL;
+}
+
+
+int mrp_res_set_attribute_string(mrp_res_attribute_t *attr,
+        const char *value)
+{
+    char *str;
+
+    if (!attr)
+        return -1;
+
+    /* check the attribute type */
+
+    if (attr->type != mrp_string)
+        return -1;
+
+    str = mrp_strdup(value);
+
+    if (!str)
+        return -1;
+
+    mrp_free((void *) attr->string);
+    attr->string = str;
+
+    return 0;
+}
+
+
+int mrp_res_set_attribute_uint(mrp_res_attribute_t *attr,
+        uint32_t value)
+{
+    if (!attr || attr->type != mrp_uint32)
+        return -1;
+
+    attr->unsignd = value;
+
+    return 0;
+}
+
+
+int mrp_res_set_attribute_int(mrp_res_attribute_t *attr,
+        int32_t value)
+{
+    if (!attr || attr->type != mrp_int32)
+        return -1;
+
+    attr->integer = value;
+
+    return 0;
+}
+
+
+int mrp_res_set_attribute_double(mrp_res_attribute_t *attr,
+        double value)
+{
+    if (!attr || attr->type != mrp_double)
+        return -1;
+
+    attr->floating = value;
+
+    return 0;
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/attribute.h b/src/plugins/resource-native/libmurphy-resource/attribute.h
new file mode 100644 (file)
index 0000000..4036296
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_API_ATTRIBUTE_H__
+#define __MURPHY_RESOURCE_API_ATTRIBUTE_H__
+
+#include <errno.h>
+
+#include "resource-private.h"
+
+#define ATTRIBUTE_MAX  32
+
+void mrp_attribute_array_free(mrp_res_attribute_t *arr,
+        uint32_t dim);
+
+mrp_res_attribute_t *mrp_attribute_array_dup(uint32_t dim,
+        mrp_res_attribute_t *arr);
+
+#endif
diff --git a/src/plugins/resource-native/libmurphy-resource/context-create.c b/src/plugins/resource-native/libmurphy-resource/context-create.c
new file mode 100644 (file)
index 0000000..eb94836
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2014, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <murphy/plugins/resource-native/libmurphy-resource/resource-api.h>
+
+mrp_mainloop_t *ml;
+
+void state_cb(mrp_res_context_t *cx, mrp_res_error_t err, void *userdata)
+{
+    MRP_UNUSED(cx);
+    MRP_UNUSED(err);
+    MRP_UNUSED(userdata);
+}
+
+void deferred_cb(mrp_deferred_t *d, void *user_data)
+{
+    uint *iterations = user_data;
+
+    (*iterations)--;
+
+    if (*iterations == 0) {
+        mrp_del_deferred(d);
+        mrp_mainloop_quit(ml, 0);
+        return;
+    }
+
+    mrp_res_context_t *ctx = mrp_res_create(ml, state_cb, NULL);
+    mrp_res_destroy(ctx);
+}
+
+void usage()
+{
+    printf("context-create <iterations>\n");
+}
+
+int main(int argc, char **argv)
+{
+    uint iterations = 0;
+
+    if (argc != 2) {
+        usage();
+        exit(1);
+    }
+
+    if ((ml = mrp_mainloop_create()) == NULL)
+        exit(1);
+
+    iterations = strtoul(argv[1], NULL, 10);
+
+    mrp_add_deferred(ml, deferred_cb, &iterations);
+
+    /* start looping */
+    mrp_mainloop_run(ml);
+
+    return 0;
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/message.c b/src/plugins/resource-native/libmurphy-resource/message.c
new file mode 100644 (file)
index 0000000..8ae161a
--- /dev/null
@@ -0,0 +1,672 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include "message.h"
+
+#include "rset.h"
+#include "attribute.h"
+
+bool fetch_resource_set_state(mrp_msg_t *msg, void **pcursor,
+                                     mrp_resproto_state_t *pstate)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_STATE || type != MRP_MSG_FIELD_UINT16)
+    {
+        *pstate = 0;
+        return false;
+    }
+
+    *pstate = value.u16;
+    return true;
+}
+
+
+bool fetch_resource_set_mask(mrp_msg_t *msg, void **pcursor,
+                                    int mask_type, uint32_t *pmask)
+{
+    uint16_t expected_tag;
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    switch (mask_type) {
+    case 0:    expected_tag = RESPROTO_RESOURCE_GRANT;     break;
+    case 1:   expected_tag = RESPROTO_RESOURCE_ADVICE;    break;
+    default:       /* don't know what to fetch */              return false;
+    }
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != expected_tag || type != MRP_MSG_FIELD_UINT32)
+    {
+        *pmask = 0;
+        return false;
+    }
+
+    *pmask = value.u32;
+    return true;
+}
+
+
+bool fetch_resource_set_id(mrp_msg_t *msg, void **pcursor,uint32_t *pid)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_SET_ID || type != MRP_MSG_FIELD_UINT32)
+    {
+        *pid = 0;
+        return false;
+    }
+
+    *pid = value.u32;
+    return true;
+}
+
+
+bool fetch_mrp_str_array(mrp_msg_t *msg, void **pcursor,
+                   uint16_t expected_tag, mrp_res_string_array_t **parr)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != expected_tag || type != MRP_MSG_FIELD_ARRAY_OF(STRING))
+    {
+        *parr = mrp_str_array_dup(0, NULL);
+        return false;
+    }
+
+    if (!(*parr = mrp_str_array_dup(size, (const char **)value.astr)))
+        return false;
+
+    return true;
+}
+
+
+bool fetch_seqno(mrp_msg_t *msg, void **pcursor, uint32_t *pseqno)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_SEQUENCE_NO || type != MRP_MSG_FIELD_UINT32)
+    {
+        *pseqno = 0;
+        return false;
+    }
+
+    *pseqno = value.u32;
+    return true;
+}
+
+
+bool fetch_request(mrp_msg_t *msg, void **pcursor, uint16_t *preqtype)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_REQUEST_TYPE || type != MRP_MSG_FIELD_UINT16)
+    {
+        *preqtype = 0;
+        return false;
+    }
+
+    *preqtype = value.u16;
+    return true;
+}
+
+
+bool fetch_status(mrp_msg_t *msg, void **pcursor, int *pstatus)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_REQUEST_STATUS || type != MRP_MSG_FIELD_SINT16)
+    {
+        *pstatus = EIO;
+        return false;
+    }
+
+    *pstatus = value.s16;
+    return true;
+}
+
+
+
+bool fetch_attribute_array(mrp_msg_t *msg, void **pcursor,
+                                 size_t dim, mrp_res_attribute_t *arr,
+                                 int *n_arr)
+{
+    mrp_res_attribute_t *attr;
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+    size_t i;
+    *n_arr = 0;
+
+    i = 0;
+
+    while (mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size)) {
+        if (tag == RESPROTO_SECTION_END && type == MRP_MSG_FIELD_UINT8)
+            break;
+
+        if (tag  != RESPROTO_ATTRIBUTE_NAME ||
+            type != MRP_MSG_FIELD_STRING ||
+            i >= dim - 1) {
+            return false;
+        }
+
+        attr = arr + i++;
+        attr->name = value.str;
+
+        if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+            tag != RESPROTO_ATTRIBUTE_VALUE) {
+            return false;
+        }
+
+        switch (type) {
+        case MRP_MSG_FIELD_STRING:
+            attr->type = 's';
+            attr->string = value.str;
+            break;
+        case MRP_MSG_FIELD_SINT32:
+            attr->type = 'i';
+            attr->integer = value.s32;
+            break;
+        case MRP_MSG_FIELD_UINT32:
+            attr->type = 'u';
+            attr->unsignd = value.u32;
+            break;
+        case MRP_MSG_FIELD_DOUBLE:
+            attr->type = 'f';
+            attr->floating = value.dbl;
+            break;
+        default:
+            return false;
+        }
+    }
+
+    memset(arr + i, 0, sizeof(mrp_res_attribute_t));
+
+    *n_arr = i;
+
+    return TRUE;
+}
+
+
+bool fetch_resource_name(mrp_msg_t *msg, void **pcursor,
+                                const char **pname)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_NAME || type != MRP_MSG_FIELD_STRING)
+    {
+        *pname = "<unknown>";
+        return false;
+    }
+
+    *pname = value.str;
+    return true;
+}
+
+
+static int priv_res_to_mrp_res(uint32_t id, resource_def_t *src, mrp_res_resource_t *dst,
+        mrp_res_resource_set_t *set)
+{
+    dst->name  = mrp_strdup(src->name);
+    dst->state = MRP_RES_RESOURCE_LOST;
+    dst->priv->mandatory = false;
+    dst->priv->shared = false;
+
+    dst->priv->server_id = id;
+
+    dst->priv->num_attributes = src->num_attrs;
+    dst->priv->attrs = src->attrs;
+    dst->priv->set = set;
+    return 0;
+}
+
+
+mrp_res_resource_set_t *resource_query_response(mrp_res_context_t *cx,
+        mrp_msg_t *msg, void **pcursor)
+{
+    int             status;
+    uint32_t        dim, i;
+    resource_def_t  rdef[RESOURCE_MAX];
+    mrp_res_attribute_t attrs[ATTRIBUTE_MAX + 1];
+    resource_def_t *src;
+    mrp_res_resource_set_t *arr = NULL;
+
+    if (!cx)
+        goto failed;
+
+    if (!fetch_status(msg, pcursor, &status))
+        goto failed;
+
+    if (status != 0)
+        mrp_res_error("Resource query failed (%u): %s", status, strerror(status));
+    else {
+        dim = 0;
+
+        while (fetch_resource_name(msg, pcursor, &rdef[dim].name)) {
+            int n_attrs = 0;
+
+            if (!fetch_attribute_array(msg, pcursor, ATTRIBUTE_MAX+1,
+                    attrs, &n_attrs))
+                goto failed;
+
+            if (!(rdef[dim].attrs = mrp_attribute_array_dup(n_attrs, attrs))) {
+                mrp_res_error("failed to duplicate attributes");
+                goto failed;
+            }
+
+            rdef[dim].num_attrs = n_attrs;
+
+            dim++;
+        }
+
+        arr = mrp_allocz(sizeof(mrp_res_resource_set_t));
+
+        if (!arr)
+            goto failed;
+
+        arr->priv = mrp_allocz(sizeof(mrp_res_resource_set_private_t));
+
+        if (!arr->priv)
+            goto failed;
+
+        arr->application_class = NULL;
+        arr->state = MRP_RES_RESOURCE_LOST;
+        arr->priv->cx = cx;
+        arr->priv->num_resources = dim;
+
+        arr->priv->resources = mrp_allocz_array(mrp_res_resource_t *, dim);
+
+        if (!arr->priv->resources)
+            goto failed;
+
+        for (i = 0; i < dim; i++) {
+            src = rdef + i;
+            arr->priv->resources[i] = mrp_allocz(sizeof(mrp_res_resource_t));
+            if (!arr->priv->resources[i]) {
+                arr->priv->num_resources = i;
+                goto failed;
+            }
+            arr->priv->resources[i]->priv =
+                    mrp_allocz(sizeof(mrp_res_resource_private_t));
+            if (!arr->priv->resources[i]->priv) {
+                mrp_free(arr->priv->resources[i]);
+                arr->priv->num_resources = i;
+                goto failed;
+            }
+            priv_res_to_mrp_res(i, src, arr->priv->resources[i], arr);
+        }
+    }
+
+    return arr;
+
+ failed:
+    mrp_res_error("malformed reply to resource query");
+    free_resource_set(arr);
+
+    return NULL;
+}
+
+
+mrp_res_string_array_t *class_query_response(mrp_msg_t *msg, void **pcursor)
+{
+    int status;
+    mrp_res_string_array_t *arr = NULL;
+
+    if (!fetch_status(msg, pcursor, &status) || (status == 0 &&
+        !fetch_mrp_str_array(msg, pcursor, RESPROTO_CLASS_NAME, &arr)))
+    {
+        mrp_res_error("ignoring malformed response to class query");
+        return NULL;
+    }
+
+    if (status) {
+        mrp_res_error("class query failed with error code %u", status);
+        mrp_res_free_string_array(arr);
+        return NULL;
+    }
+
+    return arr;
+}
+
+
+bool create_resource_set_response(mrp_msg_t *msg,
+        mrp_res_resource_set_t *rset, void **pcursor)
+{
+    int status;
+    uint32_t rset_id;
+
+    if (!fetch_status(msg, pcursor, &status) || (status == 0 &&
+        !fetch_resource_set_id(msg, pcursor, &rset_id)))
+    {
+        mrp_res_error("ignoring malformed response to resource set creation");
+        goto error;
+    }
+
+    if (status) {
+        mrp_res_error("creation of resource set failed. error code %u",status);
+        goto error;
+    }
+
+    rset->priv->id = rset_id;
+
+    return true;
+error:
+    return false;
+}
+
+
+mrp_res_resource_set_t *acquire_resource_set_response(mrp_msg_t *msg,
+            mrp_res_context_t *cx, void **pcursor)
+{
+    int status;
+    uint32_t rset_id;
+    mrp_res_resource_set_t *rset = NULL;
+
+    if (!fetch_resource_set_id(msg, pcursor, &rset_id) ||
+        !fetch_status(msg, pcursor, &status))
+    {
+        mrp_res_error("ignoring malformed response to resource set");
+        goto error;
+    }
+
+    if (status) {
+        mrp_res_error("acquiring of resource set failed. error code %u",status);
+        goto error;
+    }
+
+    /* we need the previous resource set because the new one doesn't
+     * tell us the resource set class */
+
+    rset = mrp_htbl_lookup(cx->priv->rset_mapping, u_to_p(rset_id));
+
+    if (!rset) {
+        mrp_res_error("no rset found!");
+        goto error;
+    }
+
+    return rset;
+
+error:
+    return NULL;
+}
+
+
+int acquire_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset)
+{
+    mrp_msg_t *msg = NULL;
+
+    if (!cx->priv->connected)
+        return -1;
+
+    msg = mrp_msg_create(
+            RESPROTO_SEQUENCE_NO, MRP_MSG_FIELD_UINT32, cx->priv->next_seqno,
+            RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16,
+                    RESPROTO_ACQUIRE_RESOURCE_SET,
+            RESPROTO_RESOURCE_SET_ID, MRP_MSG_FIELD_UINT32, rset->priv->id,
+            RESPROTO_MESSAGE_END);
+
+    if (!msg)
+        return -1;
+
+    rset->priv->seqno = cx->priv->next_seqno;
+    cx->priv->next_seqno++;
+
+    if (!mrp_transport_send(cx->priv->transp, msg))
+        goto error;
+
+    mrp_msg_unref(msg);
+    return 0;
+
+error:
+    mrp_msg_unref(msg);
+    return -1;
+}
+
+
+int release_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset)
+{
+    mrp_msg_t *msg = NULL;
+
+    if (!cx->priv->connected)
+        return -1;
+
+    msg = mrp_msg_create(
+            RESPROTO_SEQUENCE_NO, MRP_MSG_FIELD_UINT32, cx->priv->next_seqno,
+            RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16,
+                    RESPROTO_RELEASE_RESOURCE_SET,
+            RESPROTO_RESOURCE_SET_ID, MRP_MSG_FIELD_UINT32, rset->priv->id,
+            RESPROTO_MESSAGE_END);
+
+    if (!msg)
+        return -1;
+
+    rset->priv->seqno = cx->priv->next_seqno;
+    cx->priv->next_seqno++;
+
+    if (!mrp_transport_send(cx->priv->transp, msg))
+        goto error;
+
+    mrp_msg_unref(msg);
+    return 0;
+
+error:
+    mrp_msg_unref(msg);
+    return -1;
+}
+
+
+int create_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset)
+{
+    mrp_msg_t *msg = NULL;
+    uint32_t i;
+    uint32_t rset_flags = 0;
+
+    if (!cx || !rset)
+        return -1;
+
+    if (!cx->priv->connected)
+        return -1;
+
+    if (rset->priv->autorelease)
+        rset_flags |= RESPROTO_RSETFLAG_AUTORELEASE;
+
+    msg = mrp_msg_create(
+            RESPROTO_SEQUENCE_NO, MRP_MSG_FIELD_UINT32, cx->priv->next_seqno,
+            RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16,
+                    RESPROTO_CREATE_RESOURCE_SET,
+            RESPROTO_RESOURCE_FLAGS, MRP_MSG_FIELD_UINT32, rset_flags,
+            RESPROTO_RESOURCE_PRIORITY, MRP_MSG_FIELD_UINT32, 0,
+            RESPROTO_CLASS_NAME, MRP_MSG_FIELD_STRING, rset->application_class,
+            RESPROTO_ZONE_NAME, MRP_MSG_FIELD_STRING, cx->zone,
+            RESPROTO_MESSAGE_END);
+
+    if (!msg)
+        return -1;
+
+    rset->priv->seqno = cx->priv->next_seqno;
+    cx->priv->next_seqno++;
+
+    for (i = 0; i < rset->priv->num_resources; i++) {
+        int j;
+        uint32_t res_flags = 0;
+        mrp_res_resource_t *res = rset->priv->resources[i];
+
+        if (!res)
+            goto error;
+
+        if (res->priv->shared)
+            res_flags |= RESPROTO_RESFLAG_SHARED;
+
+        if (res->priv->mandatory)
+            res_flags |= RESPROTO_RESFLAG_MANDATORY;
+
+        if (!mrp_msg_append(msg, RESPROTO_RESOURCE_NAME, MRP_MSG_FIELD_STRING,
+                res->name))
+            goto error;
+
+        if (!mrp_msg_append(msg, RESPROTO_RESOURCE_FLAGS, MRP_MSG_FIELD_UINT32,
+                res_flags))
+            goto error;
+
+        for (j = 0; j < res->priv->num_attributes; j++) {
+            mrp_res_attribute_t *elem = &res->priv->attrs[j];
+            const char *attr_name = elem->name;
+
+            if (!mrp_msg_append(msg, RESPROTO_ATTRIBUTE_NAME, MRP_MSG_FIELD_STRING,
+                    attr_name))
+                goto error;
+
+            switch (elem->type) {
+                case 's':
+                    if (!mrp_msg_append(msg, RESPROTO_ATTRIBUTE_VALUE,
+                            MRP_MSG_FIELD_STRING, elem->string))
+                        goto error;
+                    break;
+                case 'i':
+                    if (!mrp_msg_append(msg, RESPROTO_ATTRIBUTE_VALUE,
+                            MRP_MSG_FIELD_SINT32, elem->integer))
+                        goto error;
+                    break;
+                case 'u':
+                    if (!mrp_msg_append(msg, RESPROTO_ATTRIBUTE_VALUE,
+                            MRP_MSG_FIELD_UINT32, elem->unsignd))
+                        goto error;
+                    break;
+                case 'f':
+                    if (!mrp_msg_append(msg, RESPROTO_ATTRIBUTE_VALUE,
+                            MRP_MSG_FIELD_DOUBLE, elem->floating))
+                        goto error;
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        if (!mrp_msg_append(msg, RESPROTO_SECTION_END, MRP_MSG_FIELD_UINT8, 0))
+            goto error;
+    }
+
+    if (!mrp_transport_send(cx->priv->transp, msg))
+        goto error;
+
+    mrp_msg_unref(msg);
+    return 0;
+
+error:
+    mrp_msg_unref(msg);
+    return -1;
+}
+
+
+int get_application_classes_request(mrp_res_context_t *cx)
+{
+    mrp_msg_t *msg = NULL;
+
+    if (!cx->priv->connected)
+        goto error;
+
+    msg = mrp_msg_create(RESPROTO_SEQUENCE_NO, MRP_MSG_FIELD_UINT32, 0,
+            RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16, RESPROTO_QUERY_CLASSES,
+            RESPROTO_MESSAGE_END);
+
+    if (!msg)
+        goto error;
+
+    if (!mrp_transport_send(cx->priv->transp, msg))
+        goto error;
+
+    mrp_msg_unref(msg);
+    return 0;
+
+error:
+    mrp_msg_unref(msg);
+    return -1;
+}
+
+
+int get_available_resources_request(mrp_res_context_t *cx)
+{
+    mrp_msg_t *msg = NULL;
+
+    if (!cx->priv->connected)
+        goto error;
+
+    msg = mrp_msg_create(RESPROTO_SEQUENCE_NO, MRP_MSG_FIELD_UINT32, 0,
+            RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16,
+                    RESPROTO_QUERY_RESOURCES,
+            RESPROTO_MESSAGE_END);
+
+    if (!msg)
+        goto error;
+
+    if (!mrp_transport_send(cx->priv->transp, msg))
+        goto error;
+
+    mrp_msg_unref(msg);
+    return 0;
+
+error:
+    mrp_msg_unref(msg);
+    return -1;
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/message.h b/src/plugins/resource-native/libmurphy-resource/message.h
new file mode 100644 (file)
index 0000000..5b37212
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_API_MESSAGE_H__
+#define __MURPHY_RESOURCE_API_MESSAGE_H__
+
+#include <murphy/resource/protocol.h>
+
+#include "resource-private.h"
+#include "string_array.h"
+
+/* parsing of the message */
+
+bool fetch_resource_set_state(mrp_msg_t *msg, void **pcursor,
+                                     mrp_resproto_state_t *pstate);
+
+bool fetch_resource_set_mask(mrp_msg_t *msg, void **pcursor,
+                                    int mask_type, uint32_t *pmask);
+
+bool fetch_resource_set_id(mrp_msg_t *msg, void **pcursor, uint32_t *pid);
+
+bool fetch_mrp_str_array(mrp_msg_t *msg, void **pcursor,
+                   uint16_t expected_tag, mrp_res_string_array_t **parr);
+
+bool fetch_seqno(mrp_msg_t *msg, void **pcursor, uint32_t *pseqno);
+
+bool fetch_request(mrp_msg_t *msg, void **pcursor, uint16_t *preqtype);
+
+bool fetch_status(mrp_msg_t *msg, void **pcursor, int *pstatus);
+
+bool fetch_attribute_array(mrp_msg_t *msg, void **pcursor,
+                                 size_t dim, mrp_res_attribute_t *arr,
+                                 int *n_arr);
+
+bool fetch_resource_name(mrp_msg_t *msg, void **pcursor,
+                                const char **pname);
+
+/* handling of the message responses */
+
+mrp_res_resource_set_t *resource_query_response(mrp_res_context_t *cx,
+        mrp_msg_t *msg, void **pcursor);
+
+mrp_res_string_array_t *class_query_response(mrp_msg_t *msg, void **pcursor);
+
+bool create_resource_set_response(mrp_msg_t *msg,
+        mrp_res_resource_set_t *rset, void **pcursor);
+
+mrp_res_resource_set_t *acquire_resource_set_response(mrp_msg_t *msg,
+            mrp_res_context_t *cx, void **pcursor);
+
+/* requests to the server */
+
+int acquire_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset);
+
+int release_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset);
+
+int create_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset);
+
+int get_application_classes_request(mrp_res_context_t *cx);
+
+int get_available_resources_request(mrp_res_context_t *cx);
+
+#endif
diff --git a/src/plugins/resource-native/libmurphy-resource/murphy-resource.pc.in b/src/plugins/resource-native/libmurphy-resource/murphy-resource.pc.in
new file mode 100644 (file)
index 0000000..cc17582
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-resource
+Description: Murphy policy framework, resource library.
+Requires: murphy-common = @PACKAGE_VERSION@
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-common -lmurphy-resource
+Cflags: -I${includedir}
diff --git a/src/plugins/resource-native/libmurphy-resource/resource-api.h b/src/plugins/resource-native/libmurphy-resource/resource-api.h
new file mode 100644 (file)
index 0000000..f4dae1e
--- /dev/null
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_API_H__
+#define __MURPHY_RESOURCE_API_H__
+
+#include <stdarg.h>
+
+/*
+ * Enable the json-c/JSON-Glib symbol clash hackaround in transport.h.
+ * This is currently the only known place which triggers the symbol
+ * clash. It happens when compiling ico-uxf-homescreen which includes
+ * this indirectly and also uses JSON-Glib internally for manipulating
+ * JSON objects...
+ */
+
+#define __JSON_GLIB_DANGER__
+
+#include <murphy/common.h>
+
+MRP_CDECL_BEGIN
+
+typedef struct mrp_res_context_private_s mrp_res_context_private_t;
+typedef struct mrp_res_resource_private_s mrp_res_resource_private_t;
+typedef struct mrp_res_resource_set_private_s mrp_res_resource_set_private_t;
+
+typedef enum {
+    MRP_RES_CONNECTED,
+    MRP_RES_DISCONNECTED,
+} mrp_res_connection_state_t;
+
+typedef enum {
+    MRP_RES_RESOURCE_LOST,
+    MRP_RES_RESOURCE_PENDING,
+    MRP_RES_RESOURCE_ACQUIRED,
+    MRP_RES_RESOURCE_AVAILABLE,
+} mrp_res_resource_state_t;
+
+typedef enum {
+    MRP_RES_ERROR_NONE,
+    MRP_RES_ERROR_CONNECTION_LOST,
+    MRP_RES_ERROR_INTERNAL,
+    MRP_RES_ERROR_MALFORMED,
+} mrp_res_error_t;
+
+typedef struct {
+    mrp_res_connection_state_t state;
+    const char *zone;
+    mrp_res_context_private_t *priv;
+} mrp_res_context_t;
+
+typedef enum {
+    mrp_int32   = 'i',
+    mrp_uint32  = 'u',
+    mrp_double  = 'f',
+    mrp_string  = 's',
+    mrp_invalid = '\0'
+} mrp_res_attribute_type_t;
+
+typedef struct {
+    const char *name;
+    mrp_res_attribute_type_t type;
+    union {
+        const char *string;
+        int32_t integer;
+        uint32_t unsignd;
+        double floating;
+    };
+} mrp_res_attribute_t;
+
+typedef struct {
+    const char *name;
+    mrp_res_resource_state_t state;
+    mrp_res_resource_private_t *priv;
+} mrp_res_resource_t;
+
+typedef struct {
+    const char *application_class;
+    mrp_res_resource_state_t state;
+    mrp_res_resource_set_private_t *priv;
+} mrp_res_resource_set_t;
+
+typedef struct {
+    int num_strings;
+    const char **strings;
+} mrp_res_string_array_t;
+
+/**
+ * Prototype for murphy state callback. You have to be in
+ * connected state before you can do any operation with
+ * resources.
+ *
+ * @param cx murphy connection context.
+ * @param err error message.
+ * @param userdata data you gave when starting to connect.
+ */
+typedef void (*mrp_res_state_callback_t) (mrp_res_context_t *cx,
+        mrp_res_error_t err, void *userdata);
+
+/**
+ * Prototype for resource update callback. All changes related to
+ * your acquired resource set is handled through this function.
+ * It is up to you to decide what change in the set is important
+ * for you. This is an update to the set created by you and you
+ * can find the differences by comparison.
+ *
+ * @param cx murphy connection context.
+ * @param set updated resource set for you to handle.
+ * @param userdata data you gave when starting to acquire resources.
+ */
+typedef void (*mrp_res_resource_callback_t) (mrp_res_context_t *cx,
+        const mrp_res_resource_set_t *rs, void *userdata);
+
+/**
+ * Connect to murphy. You have to wait for the callback
+ * to check that state is connected.
+ *
+ * @param ml pointer to murphy mainloop.
+ * @param cb connection state callback from Murphy resource engine.
+ * @param userdata pointer to possible data you want to access in
+ * state callback.
+ *
+ * @return pointer to the newly created resource context.
+ */
+mrp_res_context_t *mrp_res_create(mrp_mainloop_t *ml,
+        mrp_res_state_callback_t cb, void *userdata);
+
+/**
+ * Disconnect from murphy.
+ *
+ * @param cx Murphy connection context to destroy.
+ */
+void mrp_res_destroy(mrp_res_context_t *cx);
+
+/**
+ * List possible application classes that you can assign yourself
+ * when asking for resources. This info is cached to the client
+ * library when creating the connection so it will be synchronous.
+ */
+const mrp_res_string_array_t * mrp_res_list_application_classes(
+        mrp_res_context_t *cx);
+
+/**
+ * List all possible resources that you can try to acquire. This info
+ * is cached to the client library when creating connection so it
+ * will be synchronous. This is a "master" resource set you can't
+ * modify or use as your own resource set. It is only meant for
+ * introspecting the possible resources.
+ */
+const mrp_res_resource_set_t * mrp_res_list_resources(mrp_res_context_t *cx);
+
+/**
+ * Create new empty resource set. This is a resource set allocated
+ * for you so you have to remember to release it.
+ *
+ * @param cx murphy connection context.
+ * @param app_class application class for the resource set.
+ * @param cb resource update callback.
+ * @param userdata data you want to access in resource callback.
+ *
+ * @return pointer to a new empty resource set.
+ */
+mrp_res_resource_set_t * mrp_res_create_resource_set(mrp_res_context_t *cx,
+        const char *app_class, mrp_res_resource_callback_t cb, void *userdata);
+
+/**
+ * Set automatic release mode to the resource set. This means that if an
+ * application loses the resource set, it doesn't automatically get it back
+ * when the resource becomes available again. By default the automatic
+ * release mode is off.
+ *
+ * @param status automatic release status: TRUE means on, FALSE means off
+ * @param rs resource set that is being updated.
+ *
+ * @return true if successful, false otherwise.
+ */
+bool mrp_res_set_autorelease(bool status,
+        mrp_res_resource_set_t *rs);
+
+/**
+ * Delete resource set created with mrp_res_create_resource_set
+ * or mrp_res_copy_resource_set.
+ *
+ * @param set pointer to existing resource set created by the user.
+ */
+void mrp_res_delete_resource_set(mrp_res_resource_set_t *rs);
+
+/**
+ * Make a copy of the resource set. This is a helper function to
+ * be used for example when you receive updated resource set in
+ * resource callback.
+ *
+ * @param original resource set to be copied.
+ *
+ * @return pointer to a copy of the resource set.
+ */
+mrp_res_resource_set_t *mrp_res_copy_resource_set(const mrp_res_resource_set_t *orig);
+
+/**
+ * You might have assigned the same update callback for
+ * several resource sets and you have to identify the
+ * updated set. You can compare your locale copy to
+ * find out if the update concerns that particular set.
+ *
+ * @param a set to be used in comparison
+ * @param b set to be used in comparison
+ *
+ * @return true when matching, false otherwise.
+ */
+bool mrp_res_equal_resource_set(const mrp_res_resource_set_t *a,
+        const mrp_res_resource_set_t *b);
+
+/**
+ * Acquisition and release:
+ *
+ * These two functions serve two purposes. First,
+ * they start the attempt of acquisition or release of
+ * a set of resources. Second, both of them - if
+ * successful - start the delivery of Resource
+ * callbacks to the calling application regarding
+ * the affected resource set.
+ *
+ * What the second point means is that, in case an
+ * application wants to know the state of resources,
+ * but not actually acquire them, it is valid to
+ * "release" a set containing these resources
+ * without acquiring them first.
+ */
+
+/**
+ * Acquire resources. Errors in the return value will
+ * indicate only connection problems or malformed
+ * resource structs. If you will be granted the resources
+ * you asked for you will get an update for your resource
+ * set in the resource callback.
+ *
+ * @param rs resource set you want to acquire.
+ *
+ * @return murphy error code.
+ */
+int mrp_res_acquire_resource_set(const mrp_res_resource_set_t *rs);
+
+/**
+ * Release a resource set. Releasing a set of resources
+ * will not stop delivery of Resource callbacks for that
+ * set, updates for its status will still be delivered.
+ *
+ * This function can be called even with not yet acquired
+ * sets in order to start delivery of Resource callbacks
+ * for them, which can be useful for applications wishing
+ * to survey the state of specific set of resources without
+ * actually affecting it.
+ *
+ * @param rs resource set you want to release.
+ *
+ * @return murphy error code.
+ */
+int mrp_res_release_resource_set(mrp_res_resource_set_t *rs);
+
+
+/**
+ * Get a resource set unique server-side id. The id information is
+ * normally available only after mrp_res_acquire_resource_set or
+ * mrp_res_release_resource_set function callback has been called.
+ *
+ * The id is the resource set internal id, available on the resource
+ * manager side. The client can use this information to associate other
+ * properties with the resource set. The resource manager can then use
+ * this extra information to process system events.
+ *
+ * An example would be to set an audio stream property to contain the
+ * resource set id. The resource manager can use the data associated
+ * with audio streams to find out which streams belong to which resource
+ * set in the audio domain controller.
+ *
+ * @param rs resource set whose id is queried.
+ *
+ * @return resource set id.
+ **/
+int mrp_res_get_resource_set_id(mrp_res_resource_set_t *rs);
+
+
+/**
+ * Create new resource by name and init all other fields.
+ * Created resource will be automatically added to
+ * the resource set provided as argument.
+ *
+ * @param set resource the resource will be added to.
+ * @param name name of the resource you want to create.
+ * @param mandatory is the resource mandatory or not
+ * @param shared can the resource be shared or not
+ *
+ * @return pointer to new resource if succesful null otherwise.
+ */
+mrp_res_resource_t *mrp_res_create_resource(mrp_res_resource_set_t *rs,
+        const char *name, bool mandatory,
+        bool shared);
+
+/**
+ * Get the names of all resources in this resource set.
+ *
+ * @param rs resource set where the resource are.
+ *
+ * @return string array that needs to be freed with mrp_res_free_string_array
+ */
+mrp_res_string_array_t * mrp_res_list_resource_names(
+        const mrp_res_resource_set_t *rs);
+
+/**
+ * Delete resource by name from resource set.
+ *
+ * @param rs resource set where you want to get the resource.
+ * @param name name of the resource you want to get.
+ * @param pointer to resource pointer to be assigned.
+ *
+ * @return 0 if resource found.
+ */
+mrp_res_resource_t * mrp_res_get_resource_by_name(
+        const mrp_res_resource_set_t *rs, const char *name);
+
+/**
+ * Delete a resource from a resource set.
+ *
+ * @param res resource to be deleted.
+ *
+ */
+void mrp_res_delete_resource(mrp_res_resource_t *res);
+
+/**
+ * Delete resource by name from resource set.
+ *
+ * @param rs resource set where you want to remove the resource.
+ * @param name name of the resource you want to remove.
+ *
+ * @return true if resource found and removed.
+ */
+bool mrp_res_delete_resource_by_name(mrp_res_resource_set_t *rs,
+        const char *name);
+
+/**
+ * Get the names of all attributes in this resource.
+ *
+ * @param res resource where the attributes are taken.
+ *
+ * @return string array that needs to be freed with mrp_res_free_string_array
+ */
+mrp_res_string_array_t * mrp_res_list_attribute_names(
+        const mrp_res_resource_t *res);
+
+/**
+ * Get the particular resource attribute by name from the resource.
+ *
+ * @param res resource where the attributes are taken.
+ * @param name of the attribute that is fetched.
+ *
+ * @return attribute pointer to the fetched attribute.
+ */
+mrp_res_attribute_t * mrp_res_get_attribute_by_name(mrp_res_resource_t *res,
+        const char *name);
+
+/**
+ * Set new string attribute value to resource.
+ *
+ * @param attr attríbute pointer returned by mrp_res_get_attribute_by_name.
+ * @value value to be set, copied by the library.
+ *
+ * @return murphy error code.
+ */
+int mrp_res_set_attribute_string(mrp_res_attribute_t *attr,
+        const char *value);
+
+
+/**
+ * Set new unsigned integer attribute value to resource.
+ *
+ * @param attr attríbute pointer returned by mrp_res_get_attribute_by_name.
+ * @value value to be set.
+ *
+ * @return murphy error code.
+ */
+int mrp_res_set_attribute_uint(mrp_res_attribute_t *attr,
+        uint32_t value);
+
+
+/**
+ * Set new integer attribute value to resource.
+ *
+ * @param attr attríbute pointer returned by mrp_res_get_attribute_by_name.
+ * @value value to be set.
+ *
+ * @return murphy error code.
+ */
+int mrp_res_set_attribute_int(mrp_res_attribute_t *attr,
+        int32_t value);
+
+
+/**
+ * Set new unsigned integer attribute value to resource.
+ *
+ * @param attr attríbute pointer returned by mrp_res_get_attribute_by_name.
+ * @value value to be set.
+ *
+ * @return murphy error code.
+ */
+int mrp_res_set_attribute_double(mrp_res_attribute_t *attr,
+        double value);
+
+
+/**
+ * Free a string array.
+ *
+ * @param arr string array to be freed.
+ */
+void mrp_res_free_string_array(mrp_res_string_array_t *arr);
+
+
+/**
+ * Prototype for an external logger.
+ *
+ * @param level log level.
+ * @param file source file (__FILE__) he log message originated from.
+ * @param line source line (__LINE__) the log message originated from.
+ * @param func function (__func__) the log message originated from.
+ *
+ * @return none.
+ */
+typedef void (*mrp_res_logger_t) (mrp_log_level_t level, const char *file,
+                                  int line, const char *func,
+                                  const char *format, va_list args);
+
+/**
+ * Set an external logger for the resource library. All log messages
+ * produced by the library will be handed to this function. If you
+ * want to suppress all logs by the library, set the logger to NULL.
+ *
+ * @param logger the logger function to use.
+ *
+ * @return pointer to the previously active logger function.
+ */
+
+mrp_res_logger_t mrp_res_set_logger(mrp_res_logger_t logger);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_RESOURCE_API_H__ */
diff --git a/src/plugins/resource-native/libmurphy-resource/resource-fuzz.c b/src/plugins/resource-native/libmurphy-resource/resource-fuzz.c
new file mode 100644 (file)
index 0000000..22ab756
--- /dev/null
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <murphy/plugins/resource-native/libmurphy-resource/resource-api.h>
+
+#define DEFAULT_SEED 101
+
+static unsigned int seed;
+
+typedef struct {
+    mrp_res_resource_set_t *rset;
+    bool acquired;
+    mrp_list_hook_t hook;
+} rset_item_t;
+
+typedef struct {
+    /* resource context */
+    mrp_res_context_t *cx;
+
+    /* iteration handling */
+    mrp_mainloop_t *ml;
+    mrp_deferred_t *d;
+    unsigned int iterations_left;
+
+    /* resource set list */
+    mrp_list_hook_t rsets;
+    int n_items;
+} fuzz_data_t;
+
+enum operation_e {
+    OP_CREATE = 0,
+    OP_DELETE,
+    OP_ACQUIRE,
+    OP_RELEASE,
+    OP_MAX
+} operations;
+
+
+static void next_seed() {
+    char tmp_array[9];
+    char buf[7];
+    int offset;
+    int len;
+    int i;
+    unsigned int tmp = seed * seed;
+
+    /* Von Neumann's algorithm */
+
+    snprintf(tmp_array, 8, "%u", tmp);
+
+    len = strlen(tmp_array);
+    offset = 8 - len;
+
+    /* move to the end of the buffer*/
+    memmove(tmp_array+offset, tmp_array, len);
+
+    for (i = 0; i < offset; i++) {
+        tmp_array[i] = ' '; /* space before the num */
+    }
+
+    buf[6] = '\0';
+
+    /* move the last three bytes */
+
+    buf[5] = tmp_array[7];
+    buf[4] = tmp_array[6];
+    buf[3] = tmp_array[5];
+
+    /* the first three bytes */
+
+    buf[2] = tmp_array[2];
+    buf[1] = tmp_array[1];
+    buf[0] = tmp_array[0];
+
+    seed = strtoul(buf, NULL, 10);
+
+    /* printf("new seed: %u\n", seed); */
+}
+
+static int get_index(int max)
+{
+    next_seed();
+    return seed % max;
+}
+
+static void shuffle_strings(char **strings, int n) {
+    char **p; /* non-shuffled strings */
+    char *swap;
+    int i, rem, idx;
+
+    /* in place shuffle */
+
+    for (i = 0, rem = n; i < n; i++, rem--) {
+        p = strings+i;
+
+        idx = get_index(rem);
+
+        /* swap the elements */
+        swap = p[idx];
+        p[idx] = strings[i];
+        strings[i] = swap;
+    }
+}
+
+static void resource_callback(mrp_res_context_t *cx,
+                  const mrp_res_resource_set_t *rs,
+                  void *userdata)
+{
+    MRP_UNUSED(cx);
+    MRP_UNUSED(rs);
+    MRP_UNUSED(userdata);
+
+    return;
+}
+
+static void acquire_rset(fuzz_data_t *data)
+{
+    int i;
+    mrp_list_hook_t *ip, *in;
+    rset_item_t *item = NULL;
+
+    if (data->n_items == 0)
+        goto error;
+
+    i = get_index(data->n_items);
+
+    mrp_list_foreach(&data->rsets, ip, in) {
+        item = mrp_list_entry(ip, rset_item_t, hook);
+
+        if (i-- == 0) {
+            if (!item->acquired) {
+                mrp_res_acquire_resource_set(item->rset);
+                item->acquired = TRUE;
+            }
+            return;
+        }
+    }
+
+error:
+    return;
+}
+
+static void release_rset(fuzz_data_t *data)
+{
+    int i;
+    mrp_list_hook_t *ip, *in;
+    rset_item_t *item = NULL;
+
+    if (data->n_items == 0)
+        goto error;
+
+    i = get_index(data->n_items);
+
+    mrp_list_foreach(&data->rsets, ip, in) {
+        item = mrp_list_entry(ip, rset_item_t, hook);
+
+        if (i-- == 0) {
+            if (item->acquired) {
+                mrp_res_release_resource_set(item->rset);
+                item->acquired = FALSE;
+            }
+            return;
+        }
+    }
+
+error:
+    return;
+}
+
+static void delete_rset(fuzz_data_t *data)
+{
+    int i;
+    mrp_list_hook_t *ip, *in;
+    rset_item_t *item = NULL;
+
+    if (data->n_items == 0)
+        goto error;
+
+    i = get_index(data->n_items);
+
+    mrp_list_foreach(&data->rsets, ip, in) {
+        item = mrp_list_entry(ip, rset_item_t, hook);
+
+        if (i-- == 0) {
+            mrp_list_delete(ip);
+            mrp_res_delete_resource_set(item->rset);
+            mrp_free(item);
+            data->n_items--;
+            return;
+        }
+    }
+
+error:
+    return;
+}
+
+static void create_resource(mrp_res_resource_set_t *rset, char *resource)
+{
+    mrp_res_resource_t *res;
+    bool attrs[][2] = { {TRUE, TRUE}, {TRUE, FALSE}, {FALSE, TRUE}, {FALSE, FALSE} };
+
+    bool *attr = attrs[get_index(4)];
+
+    res = mrp_res_create_resource(rset, resource, attr[0], attr[1]);
+
+    /* TODO: set some attributes */
+
+    MRP_UNUSED(res);
+
+    return;
+}
+
+static void create_rset(fuzz_data_t *data)
+{
+    mrp_res_resource_set_t *rset;
+    rset_item_t *item;
+
+    char *app_classes[] = { "player", "game", "navigator" };
+    char *app_class = app_classes[get_index(3)];
+
+    char *resources[] = { "audio_playback", "audio_recording" };
+    int n_resources = get_index(1) + 1;
+    int i;
+
+    item = (rset_item_t *) mrp_allocz(sizeof(rset_item_t));
+
+    if (!item)
+        goto error;
+
+    mrp_list_init(&item->hook);
+
+    rset = mrp_res_create_resource_set(data->cx,
+            app_class, resource_callback, data);
+
+    if (!rset)
+        goto error;
+
+    /* create resources */
+
+    shuffle_strings(resources, 2);
+
+    for (i = 0; i < n_resources; i++) {
+        create_resource(rset, resources[i]);
+    }
+
+    /* put the resource set to the rset list */
+
+    item->rset = rset;
+    item->acquired = FALSE;
+
+    mrp_list_append(&data->rsets, &item->hook);
+
+    data->n_items++;
+
+    return;
+
+error:
+    return;
+}
+
+static void fuzz_iteration(mrp_deferred_t *d, void *user_data)
+{
+    enum operation_e op = (enum operation_e) get_index(OP_MAX);
+
+    fuzz_data_t *data =  (fuzz_data_t *) user_data;
+
+    data->iterations_left--;
+
+    if (data->iterations_left == 0)
+        mrp_disable_deferred(d);
+
+#if 1
+    printf("iterations left: %d, operation: %d\n", data->iterations_left,
+           op);
+#endif
+
+    switch (op) {
+        case OP_CREATE:
+            create_rset(data);
+            break;
+        case OP_DELETE:
+            delete_rset(data);
+            break;
+        case OP_ACQUIRE:
+            acquire_rset(data);
+            break;
+        case OP_RELEASE:
+            release_rset(data);
+            break;
+        default:
+            break;
+    }
+}
+
+
+static void state_callback(mrp_res_context_t *context,
+               mrp_res_error_t err,
+               void *userdata)
+{
+    fuzz_data_t *data = (fuzz_data_t *) userdata;
+
+    if (err != MRP_RES_ERROR_NONE) {
+        printf("error message received from Murphy\n");
+        goto error;
+    }
+
+    switch (context->state) {
+        case MRP_RES_CONNECTED:
+            data->d = mrp_add_deferred(data->ml, fuzz_iteration, data);
+            if (!data->d) {
+                printf("Error creating iteration loop\n");
+                goto error;
+            }
+            break;
+        case MRP_RES_DISCONNECTED:
+            if (data->d)
+                mrp_del_deferred(data->d);
+            goto error;
+    }
+    return;
+
+error:
+    /* TODO (for memory analysis reasons) */
+    /* exit(1); */
+    return;
+}
+
+static void usage()
+{
+    printf("Usage:\n");
+    printf("\tresource-api-fuzz <iterations> [seed]\n");
+}
+
+int main(int argc, char **argv)
+{
+    mrp_mainloop_t *ml;
+    fuzz_data_t data;
+
+    memset(&data, 0, sizeof(fuzz_data_t));
+
+    if (argc < 2) {
+        usage();
+        exit(1);
+    }
+
+    if (argc >= 3)
+        seed = strtoul(argv[2], NULL, 10);
+    else
+        seed = DEFAULT_SEED;
+
+    if ((ml = mrp_mainloop_create()) == NULL)
+        exit(1);
+
+    data.cx = mrp_res_create(ml, state_callback, &data);
+    data.ml = ml;
+    data.iterations_left = strtoul(argv[1], NULL, 10);
+    mrp_list_init(&data.rsets);
+
+    /* start looping */
+    mrp_mainloop_run(ml);
+
+    mrp_res_destroy(data.cx);
+    mrp_mainloop_destroy(ml);
+
+    data.cx = NULL;
+
+    return 0;
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/resource-log.c b/src/plugins/resource-native/libmurphy-resource/resource-log.c
new file mode 100644 (file)
index 0000000..d5433f9
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012, 2013 Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdarg.h>
+#include <murphy/common/log.h>
+
+#include "resource-api.h"
+#include "resource-private.h"
+
+static void default_logger(mrp_log_level_t level, const char *file,
+                           int line, const char *func,
+                           const char *format, va_list args)
+{
+    va_list ap;
+
+    va_copy(ap, args);
+    mrp_log_msgv(level, file, line, func, format, ap);
+    va_end(ap);
+}
+
+
+static mrp_res_logger_t __res_logger = default_logger;
+
+mrp_res_logger_t mrp_res_set_logger(mrp_res_logger_t logger)
+{
+    mrp_res_logger_t old = __res_logger;
+
+    __res_logger = logger;
+
+    return old;
+}
+
+
+void mrp_res_log_msg(mrp_log_level_t level, const char *file,
+                     int line, const char *func, const char *format, ...)
+{
+    va_list ap;
+
+    if (__res_logger != NULL) {
+        va_start(ap, format);
+        __res_logger(level, file, line, func, format, ap);
+        va_end(ap);
+    }
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/resource-private.h b/src/plugins/resource-native/libmurphy-resource/resource-private.h
new file mode 100644 (file)
index 0000000..18ec44b
--- /dev/null
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_API_PRIVATE_H__
+#define __MURPHY_RESOURCE_API_PRIVATE_H__
+
+#include <stdarg.h>
+
+#include <murphy/common/log.h>
+
+#include "resource-api.h"
+
+MRP_CDECL_BEGIN
+
+typedef enum {
+    MRP_RES_PENDING_OPERATION_NONE = 0,
+    MRP_RES_PENDING_OPERATION_ACQUIRE,
+    MRP_RES_PENDING_OPERATION_RELEASE,
+} pending_operation_t;
+
+typedef struct {
+    const char *name;
+    mrp_res_attribute_type_t type; /* s:char *, i:int32_t, u:uint32_t, f:double */
+    union {
+        const char *string;
+        int32_t integer;
+        uint32_t unsignd;
+        double floating;
+    };
+} attribute_t;
+
+typedef struct {
+    uint32_t dim;
+    mrp_res_attribute_t elems[0];
+} attribute_array_t;
+
+typedef struct {
+    const char *name;
+    int num_attrs;
+    mrp_res_attribute_t *attrs;
+} resource_def_t;
+
+typedef struct {
+    uint32_t dim;
+    resource_def_t defs[0];
+} resource_def_array_t;
+
+struct mrp_res_resource_private_s {
+    mrp_res_resource_t *pub; /* composition */
+    mrp_res_resource_set_t *set; /* owning set */
+
+    bool mandatory;
+    bool shared;
+    int num_attributes;
+    mrp_res_attribute_t *attrs;
+    uint32_t server_id;
+};
+
+struct mrp_res_resource_set_private_s {
+    mrp_res_resource_set_t *pub; /* composition */
+    mrp_res_context_t *cx; /* the context of this resource set */
+    uint32_t id; /* id given by the server */
+    uint32_t internal_id; /* id for checking identity */
+    uint32_t internal_ref_count;
+    uint32_t seqno;
+
+    bool autorelease;
+
+    mrp_res_resource_callback_t cb;
+    void *user_data;
+
+    uint32_t num_resources;
+    mrp_res_resource_t **resources;
+
+    pending_operation_t waiting_for;
+
+    mrp_list_hook_t hook;
+};
+
+struct mrp_res_context_private_s {
+    int connection_id;
+
+    /* mapping of server-side resource set numbers to library resource sets */
+    mrp_htbl_t *rset_mapping;
+
+    /* mapping of library resource sets to client resource sets */
+    mrp_htbl_t *internal_rset_mapping;
+
+    mrp_res_state_callback_t cb;
+    void *user_data;
+
+    mrp_mainloop_t *ml;
+    mrp_sockaddr_t saddr;
+    mrp_transport_t *transp;
+    bool connected;
+
+    mrp_res_string_array_t *master_classes;
+    mrp_res_resource_set_t *master_resource_set;
+
+    /* sometimes we need to know which query was answered */
+    uint32_t next_seqno;
+
+    /* running number for identifying resource sets */
+    uint32_t next_internal_id;
+
+    mrp_list_hook_t pending_sets;
+};
+
+uint32_t p_to_u(const void *p);
+void *u_to_p(uint32_t u);
+
+/*
+ * logging macros
+ */
+
+#define __LOCATION__ __FILE__,__LINE__,__FUNCTION__
+
+#define mrp_res_info(format, args...) \
+    mrp_res_log_msg(MRP_LOG_INFO, __LOCATION__, format, ## args)
+
+#define mrp_res_warning(format, args...) \
+    mrp_res_log_msg(MRP_LOG_WARNING, __LOCATION__, format, ## args)
+
+#define mrp_res_error(format, args...) \
+    mrp_res_log_msg(MRP_LOG_ERROR, __LOCATION__, format, ## args)
+
+void mrp_res_log_msg(mrp_log_level_t level, const char *file, int line,
+                     const char *func, const char *format, ...);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_RESOURCE_API_PRIVATE_H__ */
diff --git a/src/plugins/resource-native/libmurphy-resource/resource.c b/src/plugins/resource-native/libmurphy-resource/resource.c
new file mode 100644 (file)
index 0000000..57a18b0
--- /dev/null
@@ -0,0 +1,552 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/resource/protocol.h>
+#include "resource-api.h"
+#include "resource-private.h"
+
+#include "string_array.h"
+#include "message.h"
+#include "rset.h"
+#include "attribute.h"
+
+
+void *u_to_p(uint32_t u)
+{
+#ifdef __SIZEOF_POINTER__
+#if __SIZEOF_POINTER__ == 8
+    uint64_t o = u;
+#else
+    uint32_t o = u;
+#endif
+#else
+    uint32_t o = o;
+#endif
+    return (void *) o;
+}
+
+
+uint32_t p_to_u(const void *p)
+{
+#ifdef __SIZEOF_POINTER__
+#if __SIZEOF_POINTER__ == 8
+    uint32_t o = 0;
+    uint64_t big = (uint64_t) p;
+    o = big & 0xffffffff;
+#else
+    uint32_t o = (uint32_t) p;
+#endif
+#else
+    uint32_t o = p;
+#endif
+    return o;
+}
+
+
+int int_comp(const void *key1, const void *key2)
+{
+    return key1 != key2;
+}
+
+
+uint32_t int_hash(const void *key)
+{
+    return p_to_u(key);
+}
+
+
+static void resource_event(mrp_msg_t *msg,
+        mrp_res_context_t *cx,
+        int32_t seqno,
+        void **pcursor)
+{
+    uint32_t rset_id;
+    uint32_t grant, advice;
+    mrp_resproto_state_t state;
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+    uint32_t resid;
+    const char *resnam;
+    mrp_res_attribute_t attrs[ATTRIBUTE_MAX + 1];
+    int n_attrs;
+    uint32_t mask, all = 0x0, mandatory = 0x0;
+    uint32_t i;
+    mrp_res_resource_set_t *rset;
+
+    mrp_res_info("Resource event (request no %u):", seqno);
+
+    if (!fetch_resource_set_id(msg, pcursor, &rset_id) ||
+        !fetch_resource_set_state(msg, pcursor, &state) ||
+        !fetch_resource_set_mask(msg, pcursor, 0, &grant) ||
+        !fetch_resource_set_mask(msg, pcursor, 1, &advice)) {
+        mrp_res_error("failed to fetch data from message");
+        goto ignore;
+    }
+
+    /* Update our "master copy" of the resource set. */
+
+    rset = mrp_htbl_lookup(cx->priv->rset_mapping, u_to_p(rset_id));
+
+    if (!rset) {
+        mrp_res_info("resource event outside the resource set lifecycle");
+        goto ignore;
+    }
+
+    while (mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size)) {
+
+        mrp_res_resource_t *res = NULL;
+
+        if ((tag != RESPROTO_RESOURCE_ID || type != MRP_MSG_FIELD_UINT32) ||
+                !fetch_resource_name(msg, pcursor, &resnam)) {
+            mrp_res_error("failed to read resource from message");
+            goto ignore;
+        }
+
+        res = get_resource_by_name(rset, resnam);
+
+        if (!res) {
+            mrp_res_error("resource doesn't exist in resource set");
+            goto ignore;
+        }
+
+        resid = value.u32;
+
+        mrp_res_info("data for '%s': %d", res->name, resid);
+
+        if (!fetch_attribute_array(msg, pcursor, ATTRIBUTE_MAX + 1, attrs,
+                &n_attrs)) {
+            mrp_res_error("failed to read attributes from message");
+            goto ignore;
+        }
+
+        /* copy the attributes */
+        for (i = 0; (int) i < n_attrs; i++) {
+            mrp_res_attribute_t *src = &attrs[i];
+            mrp_res_attribute_t *dst = mrp_res_get_attribute_by_name(res, src->name);
+
+            if (!dst) {
+                mrp_log_error("unknown attribute '%s'!", src->name);
+                continue;
+            }
+
+            if (src->type != dst->type) {
+                mrp_log_error("attribute types don't match for '%s'!", src->name);
+            }
+
+            switch (src->type) {
+                case mrp_int32:
+                    mrp_res_set_attribute_int(dst, src->integer);
+                    break;
+                case mrp_uint32:
+                    mrp_res_set_attribute_uint(dst, src->unsignd);
+                    break;
+                case mrp_double:
+                    mrp_res_set_attribute_double(dst, src->floating);
+                    break;
+                case mrp_string:
+                    mrp_res_set_attribute_string(dst, src->string);
+                    break;
+                default: /* mrp_invalid */
+                    break;
+            }
+        }
+    }
+
+    /* go through all resources and see if they have been modified */
+
+    for (i = 0; i < rset->priv->num_resources; i++)
+    {
+        mrp_res_resource_t *res = rset->priv->resources[i];
+
+        mask  = (1UL << res->priv->server_id);
+        all  |= mask;
+
+        if (res->priv->mandatory)
+            mandatory |= mask;
+
+        if (grant & mask) {
+            res->state = MRP_RES_RESOURCE_ACQUIRED;
+        }
+        else {
+            res->state = MRP_RES_RESOURCE_LOST;
+        }
+    }
+
+    mrp_res_info("advice = 0x%08x, grant = 0x%08x, mandatory = 0x%08x, all = 0x%08x",
+            advice, grant, mandatory, all);
+
+    if (grant) {
+        rset->state = MRP_RES_RESOURCE_ACQUIRED;
+    }
+    else if (advice == mandatory) {
+        rset->state = MRP_RES_RESOURCE_AVAILABLE;
+    }
+    else {
+        rset->state = MRP_RES_RESOURCE_LOST;
+    }
+
+    /* Check the resource set state. If the set is under construction
+     * (we are waiting for "acquire" or "release" message), do not do the
+     * callback before that. Otherwise, if this is a real event, call the
+     * callback right away. */
+
+#if 0
+    print_resource_set(rset);
+#endif
+    if (!rset->priv->seqno) {
+        if (rset->priv->cb) {
+            increase_ref(cx, rset);
+            rset->priv->cb(cx, rset, rset->priv->user_data);
+            decrease_ref(cx, rset);
+        }
+    }
+
+    return;
+
+ ignore:
+    mrp_res_info("ignoring resource event");
+}
+
+
+static void recvfrom_msg(mrp_transport_t *transp, mrp_msg_t *msg,
+                         mrp_sockaddr_t *addr, socklen_t addrlen,
+                         void *user_data)
+{
+    mrp_res_context_t *cx = user_data;
+    void *cursor = NULL;
+    uint32_t seqno;
+    uint16_t req;
+    mrp_res_error_t err = MRP_RES_ERROR_INTERNAL;
+
+    MRP_UNUSED(transp);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    if (!fetch_seqno(msg, &cursor, &seqno) ||
+                !fetch_request(msg, &cursor, &req))
+        goto error;
+
+    mrp_res_info("received message %d for %p", req, cx);
+
+    err = MRP_RES_ERROR_MALFORMED;
+
+    switch (req) {
+        case RESPROTO_QUERY_RESOURCES:
+
+            mrp_res_info("received QUERY_RESOURCES response");
+
+            cx->priv->master_resource_set =
+                    resource_query_response(cx, msg, &cursor);
+            if (!cx->priv->master_resource_set)
+                goto error;
+            break;
+        case RESPROTO_QUERY_CLASSES:
+
+            mrp_res_info("received QUERY_CLASSES response");
+
+            cx->priv->master_classes = class_query_response(msg, &cursor);
+            if (!cx->priv->master_classes)
+                goto error;
+            break;
+        case RESPROTO_CREATE_RESOURCE_SET:
+        {
+            mrp_res_resource_set_private_t *priv = NULL;
+            mrp_res_resource_set_t *rset = NULL;
+            mrp_list_hook_t *p, *n;
+
+            mrp_res_info("received CREATE_RESOURCE_SET response");
+
+            /* get the correct resource set from the pending_sets list */
+
+            mrp_list_foreach(&cx->priv->pending_sets, p, n) {
+                priv = mrp_list_entry(p, typeof(*priv), hook);
+
+                if (priv->seqno == seqno) {
+                    rset = priv->pub;
+                    break;
+                }
+            }
+
+            if (!rset) {
+                /* the corresponding set wasn't found */
+                goto error;
+            }
+
+            mrp_list_delete(&rset->priv->hook);
+
+            if (!create_resource_set_response(msg, rset, &cursor))
+                goto error;
+
+            mrp_htbl_insert(cx->priv->rset_mapping,
+                    u_to_p(rset->priv->id), rset);
+
+            /* TODO: if the operation was "acquire", do that. Otherwise
+             * release. */
+
+            if (rset->priv->waiting_for == MRP_RES_PENDING_OPERATION_ACQUIRE) {
+                rset->priv->waiting_for = MRP_RES_PENDING_OPERATION_NONE;
+                if (acquire_resource_set_request(cx, rset) < 0) {
+                    goto error;
+                }
+            }
+            else if (rset->priv->waiting_for == MRP_RES_PENDING_OPERATION_RELEASE) {
+                rset->priv->waiting_for = MRP_RES_PENDING_OPERATION_NONE;
+                if (release_resource_set_request(cx, rset) < 0) {
+                    goto error;
+                }
+            }
+            else {
+                goto error;
+            }
+            break;
+        }
+        case RESPROTO_ACQUIRE_RESOURCE_SET:
+        {
+            mrp_res_resource_set_t *rset;
+
+            mrp_res_info("received ACQUIRE_RESOURCE_SET response");
+
+            rset = acquire_resource_set_response(msg, cx, &cursor);
+
+            if (!rset) {
+                goto error;
+            }
+
+            rset->priv->seqno = 0;
+
+            break;
+        }
+        case RESPROTO_RELEASE_RESOURCE_SET:
+        {
+            mrp_res_resource_set_t *rset;
+            mrp_res_info("received RELEASE_RESOURCE_SET response");
+
+            rset = acquire_resource_set_response(msg, cx, &cursor);
+
+            if (!rset) {
+                goto error;
+            }
+
+            /* TODO: make new releases fail until seqno == 0 */
+            rset->priv->seqno = 0;
+
+            break;
+        }
+        case RESPROTO_RESOURCES_EVENT:
+            mrp_res_info("received RESOURCES_EVENT response");
+
+            resource_event(msg, cx, seqno, &cursor);
+            break;
+        case RESPROTO_DESTROY_RESOURCE_SET:
+            mrp_res_info("received DESTROY_RESOURCE_SET response");
+            /* TODO? */
+            break;
+        default:
+            break;
+    }
+
+    if (cx->state == MRP_RES_DISCONNECTED &&
+            cx->priv->master_classes &&
+            cx->priv->master_resource_set) {
+        cx->state = MRP_RES_CONNECTED;
+        cx->priv->cb(cx, MRP_RES_ERROR_NONE, cx->priv->user_data);
+    }
+
+    return;
+
+error:
+    mrp_res_error("error processing a message from the server");
+    cx->priv->cb(cx, err, cx->priv->user_data);
+}
+
+
+static void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    return recvfrom_msg(t, msg, NULL, 0, user_data);
+}
+
+
+void closed_evt(mrp_transport_t *transp, int error, void *user_data)
+{
+    mrp_res_context_t *cx = user_data;
+    MRP_UNUSED(transp);
+    MRP_UNUSED(error);
+
+    mrp_res_error("connection closed for %p", cx);
+    cx->priv->connected = FALSE;
+
+    if (cx->state == MRP_RES_CONNECTED) {
+        cx->state = MRP_RES_DISCONNECTED;
+        cx->priv->cb(cx, MRP_RES_ERROR_CONNECTION_LOST, cx->priv->user_data);
+    }
+}
+
+
+static void destroy_context(mrp_res_context_t *cx)
+{
+    if (!cx)
+        return;
+
+    if (cx->priv) {
+
+        if (cx->priv->transp)
+            mrp_transport_destroy(cx->priv->transp);
+
+        delete_resource_set(cx->priv->master_resource_set);
+
+        /* FIXME: is this the way we want to free all resources and
+         * resource sets? */
+        if (cx->priv->rset_mapping)
+            mrp_htbl_destroy(cx->priv->rset_mapping, false);
+
+        if (cx->priv->internal_rset_mapping)
+            mrp_htbl_destroy(cx->priv->internal_rset_mapping, true);
+
+        mrp_res_free_string_array(cx->priv->master_classes);
+
+        mrp_free(cx->priv);
+    }
+    mrp_free(cx);
+}
+
+
+static void htbl_free_rset_mapping(void *key, void *object)
+{
+#if 0
+    mrp_res_info("> htbl_free_rset_mapping(%d, %p)", p_to_u(key), object);
+#else
+    MRP_UNUSED(key);
+#endif
+
+    mrp_res_resource_set_t *rset = object;
+    free_resource_set(rset);
+}
+
+/* public API */
+
+mrp_res_context_t *mrp_res_create(mrp_mainloop_t *ml,
+                       mrp_res_state_callback_t cb,
+                       void *userdata)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = recv_msg },
+        { .recvmsgfrom = recvfrom_msg },
+        .closed        = closed_evt,
+        .connection    = NULL
+    };
+
+    int alen;
+    const char *type;
+    mrp_htbl_config_t conf;
+    mrp_res_context_t *cx = mrp_allocz(sizeof(mrp_res_context_t));
+
+    if (!cx)
+        goto error;
+
+    cx->priv = mrp_allocz(sizeof(struct mrp_res_context_private_s));
+
+    if (!cx->priv)
+        goto error;
+
+    cx->priv->next_seqno = 1;
+    cx->priv->next_internal_id = 1;
+    cx->priv->ml = ml;
+    cx->priv->connection_id = 0;
+    cx->priv->cb = cb;
+    cx->priv->user_data = userdata;
+
+    conf.comp = int_comp;
+    conf.hash = int_hash;
+    conf.free = htbl_free_rset_mapping;
+    conf.nbucket = 0;
+    conf.nentry = 5;
+
+    /* When the resource set is "created" on the server side, we get
+     * back an id. The id is then mapped to the actual resource set on
+     * the client side, so that the event can be addressed to the
+     * correct resource set. */
+    cx->priv->rset_mapping = mrp_htbl_create(&conf);
+
+    if (!cx->priv->rset_mapping)
+        goto error;
+
+    /* When a resource set is acquired, we are keeping a "master copy" on the
+     * server side. The client can free and copy this resource set as much as
+     * it wants. The internal id is a method for understanding which resource
+     * set maps to which. */
+    cx->priv->internal_rset_mapping = mrp_htbl_create(&conf);
+
+    if (!cx->priv->internal_rset_mapping)
+        goto error;
+
+    /* connect to Murphy */
+
+    alen = mrp_transport_resolve(NULL, mrp_resource_get_default_address(),
+            &cx->priv->saddr, sizeof(cx->priv->saddr), &type);
+
+    cx->priv->transp = mrp_transport_create(cx->priv->ml, type,
+                                          &evt, cx, 0);
+
+    if (!cx->priv->transp)
+        goto error;
+
+    if (!mrp_transport_connect(cx->priv->transp, &cx->priv->saddr, alen))
+        goto error;
+
+    cx->priv->connected = TRUE;
+    cx->state = MRP_RES_DISCONNECTED;
+
+    if (get_application_classes_request(cx) < 0 || get_available_resources_request(cx) < 0) {
+        goto error;
+    }
+
+    /* TODO: this needs to be gotten from an environment variable */
+    cx->zone = "driver";
+
+    mrp_list_init(&cx->priv->pending_sets);
+
+    return cx;
+
+error:
+
+    mrp_res_error("error connecting to server");
+    destroy_context(cx);
+
+    return NULL;
+}
+
+
+void mrp_res_destroy(mrp_res_context_t *cx)
+{
+    destroy_context(cx);
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/rset.c b/src/plugins/resource-native/libmurphy-resource/rset.c
new file mode 100644 (file)
index 0000000..1c788b8
--- /dev/null
@@ -0,0 +1,888 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include "rset.h"
+#include "attribute.h"
+#include "message.h"
+
+
+static char *state_to_str(mrp_res_resource_state_t st)
+{
+    char *state = "unknown";
+    switch (st) {
+        case MRP_RES_RESOURCE_ACQUIRED:
+            state = "acquired";
+            break;
+        case MRP_RES_RESOURCE_LOST:
+            state = "lost";
+            break;
+        case MRP_RES_RESOURCE_AVAILABLE:
+            state = "available";
+            break;
+        case MRP_RES_RESOURCE_PENDING:
+            state = "pending";
+            break;
+    }
+    return state;
+}
+
+
+void print_resource(mrp_res_resource_t *res)
+{
+    mrp_res_info("   resource '%s' -> '%s' : %smandatory, %sshared",
+            res->name, state_to_str(res->state),
+            res->priv->mandatory ? " " : "not ",
+            res->priv->shared ? "" : "not ");
+}
+
+#if 0
+void print_resource_set(mrp_res_resource_set_t *rset)
+{
+    uint32_t i;
+    mrp_res_resource_t *res;
+
+    mrp_res_info("Resource set %i/%i (%s) -> '%s':",
+            rset->priv->id, rset->priv->internal_id,
+            rset->application_class, state_to_str(rset->state));
+
+    for (i = 0; i < rset->priv->num_resources; i++) {
+        res = rset->priv->resources[i];
+        print_resource(res);
+    }
+}
+#endif
+
+void increase_ref(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset)
+{
+    MRP_UNUSED(cx);
+
+    if (!rset)
+        return;
+
+    rset->priv->internal_ref_count++;
+}
+
+
+static int destroy_resource_set_request(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset)
+{
+    mrp_msg_t *msg = NULL;
+
+    if (!cx->priv->connected)
+        goto error;
+
+    rset->priv->seqno = cx->priv->next_seqno;
+
+    msg = mrp_msg_create(
+            RESPROTO_SEQUENCE_NO, MRP_MSG_FIELD_UINT32, cx->priv->next_seqno++,
+            RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16,
+                    RESPROTO_DESTROY_RESOURCE_SET,
+            RESPROTO_RESOURCE_SET_ID, MRP_MSG_FIELD_UINT32, rset->priv->id,
+            RESPROTO_MESSAGE_END);
+
+    if (!msg)
+        goto error;
+
+    if (!mrp_transport_send(cx->priv->transp, msg))
+        goto error;
+
+    mrp_msg_unref(msg);
+    return 0;
+
+error:
+    mrp_msg_unref(msg);
+    return -1;
+}
+
+
+void decrease_ref(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset)
+{
+    if (!rset)
+        return;
+
+    rset->priv->internal_ref_count--;
+
+    if (rset->priv->internal_ref_count == 0) {
+        mrp_log_info("delete the server resource set now");
+        destroy_resource_set_request(cx, rset);
+
+        /* if a rset is deleted, remove it from the pending sets */
+        mrp_list_delete(&rset->priv->hook);
+
+        mrp_htbl_remove(cx->priv->rset_mapping,
+                u_to_p(rset->priv->id), FALSE);
+        mrp_htbl_remove(cx->priv->internal_rset_mapping,
+                u_to_p(rset->priv->internal_id), TRUE);
+    }
+}
+
+
+mrp_res_resource_t *get_resource_by_name(mrp_res_resource_set_t *rset,
+        const char *name)
+{
+    uint32_t i;
+
+    if (!rset || !name)
+        return NULL;
+
+    for (i = 0; i < rset->priv->num_resources; i++) {
+        mrp_res_resource_t *res = rset->priv->resources[i];
+        if (strcmp(res->name, name) == 0) {
+            return res;
+        }
+    }
+
+    return NULL;
+}
+
+
+static void free_resource(mrp_res_resource_t *res)
+{
+    if (!res)
+        return;
+
+    mrp_free((void *) res->name);
+
+    if (res->priv) {
+        mrp_attribute_array_free(res->priv->attrs,
+                res->priv->num_attributes);
+    }
+
+    mrp_free(res->priv);
+    mrp_free(res);
+}
+
+
+void free_resource_set(mrp_res_resource_set_t *rset)
+{
+    uint32_t i;
+
+    if (!rset)
+        return;
+
+    mrp_free((void *) rset->application_class);
+
+    if (!rset->priv)
+        goto end;
+
+    for (i = 0; i < rset->priv->num_resources; i++) {
+        free_resource(rset->priv->resources[i]);
+    }
+    mrp_free(rset->priv->resources);
+    mrp_free(rset->priv);
+
+end:
+    mrp_free(rset);
+}
+
+
+
+void delete_resource_set(mrp_res_resource_set_t *rs)
+{
+    mrp_res_context_t *cx = NULL;
+
+    if (!rs)
+        return;
+
+    if (rs->priv && rs->priv->cx) {
+        cx = rs->priv->cx;
+
+        /* check if the resource set being deleted is a library resource set */
+        mrp_res_resource_set_t *internal_rset = mrp_htbl_lookup(
+                cx->priv->internal_rset_mapping, u_to_p(rs->priv->internal_id));
+
+        if (internal_rset && internal_rset != rs) {
+            decrease_ref(cx, internal_rset);
+        }
+    }
+
+    free_resource_set(rs);
+}
+
+
+
+static mrp_res_resource_t *resource_copy(const mrp_res_resource_t *original,
+        mrp_res_resource_set_t *new_rset)
+{
+    mrp_res_resource_t *copy;
+
+    copy = mrp_allocz(sizeof(mrp_res_resource_t));
+
+    if (!copy)
+        goto error;
+
+    memcpy(copy, original, sizeof(mrp_res_resource_t));
+
+    copy->name = mrp_strdup(original->name);
+
+    if (!copy->name)
+        goto error;
+
+    copy->priv = mrp_allocz(sizeof(mrp_res_resource_private_t));
+
+    if (!copy->priv)
+        goto error;
+
+    memcpy(copy->priv, original->priv, sizeof(mrp_res_resource_private_t));
+
+    copy->priv->pub = copy;
+    copy->priv->set = new_rset;
+
+    copy->priv->attrs = mrp_attribute_array_dup(original->priv->num_attributes,
+            original->priv->attrs);
+
+    if (!copy->priv->attrs)
+        goto error;
+
+    return copy;
+
+error:
+    mrp_res_error("failed to copy resource");
+
+    if (copy) {
+        mrp_free((void *) copy->name);
+        if (copy->priv) {
+            mrp_attribute_array_free(copy->priv->attrs,
+                    original->priv->num_attributes);
+            mrp_free(copy->priv);
+        }
+        mrp_free(copy);
+    }
+
+    return NULL;
+}
+
+
+
+mrp_res_resource_set_t *resource_set_copy(
+        const mrp_res_resource_set_t *original)
+{
+    mrp_res_resource_set_t *copy = NULL;
+    uint32_t i;
+
+    copy = mrp_allocz(sizeof(mrp_res_resource_set_t));
+
+    if (!copy)
+        goto error;
+
+    copy->state = original->state;
+    copy->application_class = mrp_strdup(original->application_class);
+
+    if (!copy->application_class)
+        goto error;
+
+    copy->priv = mrp_allocz(sizeof(mrp_res_resource_set_private_t));
+
+    if (!copy->priv)
+        goto error;
+
+    memcpy(copy->priv, original->priv, sizeof(mrp_res_resource_set_private_t));
+
+    copy->priv->pub = copy;
+    copy->priv->resources = mrp_allocz_array(mrp_res_resource_t *,
+            original->priv->num_resources);
+
+    if (copy->priv->resources == NULL && copy->priv->num_resources)
+        goto error;
+
+    for (i = 0; i < copy->priv->num_resources; i++) {
+        copy->priv->resources[i] = resource_copy(original->priv->resources[i],
+                copy);
+        if (!copy->priv->resources[i]) {
+            copy->priv->num_resources = --i;
+            goto error;
+        }
+    }
+
+    memset(&copy->priv->hook, 0, sizeof(mrp_list_hook_t));
+    mrp_list_init(&copy->priv->hook);
+
+    return copy;
+
+error:
+    free_resource_set(copy);
+    return NULL;
+}
+
+
+static mrp_res_resource_set_t *create_resource_set(
+        mrp_res_context_t *cx,
+        const char *klass,
+        mrp_res_resource_callback_t cb,
+        void *userdata)
+{
+    mrp_res_resource_set_t *rs;
+    mrp_res_resource_set_t *internal;
+
+    if (cx->priv->master_resource_set == NULL)
+        return NULL;
+
+    rs = mrp_allocz(sizeof(mrp_res_resource_set_t));
+
+    if (!rs)
+        goto error;
+
+    rs->priv = mrp_allocz(sizeof(mrp_res_resource_set_private_t));
+    if (!rs->priv)
+        goto error;
+
+    rs->application_class = mrp_strdup(klass);
+
+    rs->priv->pub = rs;
+    rs->priv->cx = cx;
+    rs->priv->id = 0;
+    rs->priv->internal_id = cx->priv->next_internal_id++;
+    rs->priv->seqno = 0;
+    rs->priv->cb = cb;
+    rs->priv->user_data = userdata;
+    rs->state = MRP_RES_RESOURCE_PENDING;
+    rs->priv->autorelease = FALSE;
+
+    rs->priv->resources = mrp_allocz_array(mrp_res_resource_t *,
+            cx->priv->master_resource_set->priv->num_resources);
+
+    rs->priv->waiting_for = MRP_RES_PENDING_OPERATION_NONE;
+
+    mrp_list_init(&rs->priv->hook);
+
+    /* ok, create an library-side resource set that we can compare this one to */
+
+    internal = resource_set_copy(rs);
+    if (!internal)
+        goto error;
+
+    increase_ref(cx, internal);
+
+    mrp_htbl_insert(cx->priv->internal_rset_mapping,
+            u_to_p(internal->priv->internal_id), internal);
+
+    return rs;
+
+error:
+    mrp_log_error("error creating resource set");
+    delete_resource_set(rs);
+    return NULL;
+}
+
+
+static int update_library_resource_set(mrp_res_context_t *cx,
+        const mrp_res_resource_set_t *original,
+        mrp_res_resource_set_t *rset)
+{
+    char *application_class = NULL;
+    mrp_res_resource_t **resources = NULL;
+    uint32_t i, num_resources = 0;
+
+    if (!cx || !original)
+        return -1;
+
+    /* Update the rset with the values in the original resource set. There
+     * is only one "library-side" resource set corresponding 1-1 to the server
+     * resource set. The original is the "client-side" resource set, which there
+     * can be many. */
+
+     application_class = mrp_strdup(original->application_class);
+     if (!application_class) {
+        mrp_log_error("error with memory allocation");
+        goto error;
+    }
+
+     resources = mrp_allocz_array(mrp_res_resource_t *,
+            original->priv->num_resources);
+     if (!resources) {
+        mrp_log_error("error allocating %d resources", original->priv->num_resources);
+        goto error;
+    }
+
+    for (i = 0; i < original->priv->num_resources; i++) {
+        resources[i] = resource_copy(original->priv->resources[i], rset);
+        if (!resources[i]) {
+            mrp_log_error("error copying resources to library resource set");
+            goto error;
+        }
+        num_resources++;
+    }
+
+    mrp_free((void *) rset->application_class);
+    for (i = 0; i < rset->priv->num_resources; i++) {
+        free_resource(rset->priv->resources[i]);
+    }
+    mrp_free(rset->priv->resources);
+
+    rset->application_class = application_class;
+    rset->priv->resources = resources;
+    rset->priv->num_resources = num_resources;
+    rset->priv->autorelease = original->priv->autorelease;
+
+    return 0;
+
+error:
+    mrp_log_error("error updating library resource set");
+    mrp_free(application_class);
+    for (i = 0; i < num_resources; i++) {
+        free_resource(resources[i]);
+    }
+    mrp_free(resources);
+
+    return -1;
+}
+
+/* public API */
+
+const mrp_res_string_array_t * mrp_res_list_application_classes(
+        mrp_res_context_t *cx)
+{
+    if (!cx)
+        return NULL;
+
+    return cx->priv->master_classes;
+}
+
+
+mrp_res_resource_t *mrp_res_create_resource(
+                    mrp_res_resource_set_t *set,
+                    const char *name,
+                    bool mandatory,
+                    bool shared)
+{
+    mrp_res_resource_t *res = NULL, *proto = NULL;
+    uint32_t i = 0;
+    bool found = false;
+    uint32_t server_id = 0;
+    mrp_res_context_t *cx = NULL;
+
+    if (set == NULL)
+        return NULL;
+
+    cx = set->priv->cx;
+
+    if (cx == NULL || name == NULL)
+        return NULL;
+
+    for (i = 0; i < cx->priv->master_resource_set->priv->num_resources; i++) {
+        proto = cx->priv->master_resource_set->priv->resources[i];
+        if (strcmp(proto->name, name) == 0) {
+            found = true;
+            server_id = proto->priv->server_id;
+            break;
+        }
+    }
+
+    if (!found)
+        goto error;
+
+    res = mrp_allocz(sizeof(mrp_res_resource_t));
+
+    if (!res)
+        goto error;
+
+    res->name = mrp_strdup(name);
+
+    res->state = MRP_RES_RESOURCE_PENDING;
+
+    res->priv = mrp_allocz(sizeof(mrp_res_resource_private_t));
+
+    if (!res->priv)
+        goto error;
+
+    res->priv->server_id = server_id;
+    res->priv->mandatory = mandatory;
+    res->priv->shared = shared;
+    res->priv->pub = res;
+    res->priv->set = set;
+
+    /* copy the attributes with the default values */
+    res->priv->attrs = mrp_attribute_array_dup(proto->priv->num_attributes,
+                proto->priv->attrs);
+
+    res->priv->num_attributes = proto->priv->num_attributes;
+
+    /* add resource to resource set */
+    set->priv->resources[set->priv->num_resources++] = res;
+
+    return res;
+
+error:
+    mrp_res_error("mrp_res_create_resource error");
+    free_resource(res);
+
+    return NULL;
+}
+
+
+mrp_res_resource_set_t *mrp_res_copy_resource_set(
+        const mrp_res_resource_set_t *original)
+{
+    mrp_res_resource_set_t *copy, *internal;
+    mrp_res_context_t *cx = NULL;
+
+    copy = resource_set_copy(original);
+
+    if (!copy)
+        goto error;
+
+    cx = original->priv->cx;
+
+    /* increase the reference count of the library resource set */
+
+    internal = mrp_htbl_lookup(cx->priv->internal_rset_mapping,
+            u_to_p(original->priv->internal_id));
+
+    if (!internal)
+        goto error;
+
+    increase_ref(cx, internal);
+
+    return copy;
+
+error:
+    mrp_log_error("error copying a resource set");
+    free_resource_set(copy);
+    return NULL;
+}
+
+const mrp_res_resource_set_t * mrp_res_list_resources(
+        mrp_res_context_t *cx)
+{
+    if (cx == NULL || cx->priv == NULL)
+        return NULL;
+
+    return cx->priv->master_resource_set;
+}
+
+
+int mrp_res_release_resource_set(mrp_res_resource_set_t *original)
+{
+    mrp_res_resource_set_t *internal_set = NULL;
+    mrp_res_context_t *cx = original->priv->cx;
+
+    if (!cx || !cx->priv->connected)
+        goto error;
+
+    if (!original->priv->internal_id)
+        goto error;
+
+    internal_set = mrp_htbl_lookup(cx->priv->internal_rset_mapping,
+            u_to_p(original->priv->internal_id));
+
+    if (!internal_set)
+        goto error;
+
+    update_library_resource_set(cx, original, internal_set);
+
+    if (internal_set->priv->id) {
+        return release_resource_set_request(cx, internal_set);
+    }
+    else {
+        mrp_list_hook_t *p, *n;
+        mrp_res_resource_set_private_t *pending_rset;
+        bool found = FALSE;
+
+        /* Create the resource set if it doesn't already exist on the
+         * server. The releasing is continued when the set is created.
+         */
+
+        /* only append if not already present in the list */
+
+        mrp_list_foreach(&cx->priv->pending_sets, p, n) {
+            pending_rset = mrp_list_entry(p, mrp_res_resource_set_private_t, hook);
+            if (pending_rset == internal_set->priv) {
+                found = TRUE;
+                break;
+            }
+        }
+
+        if (!found) {
+            mrp_list_append(&cx->priv->pending_sets, &internal_set->priv->hook);
+        }
+
+        internal_set->priv->waiting_for = MRP_RES_PENDING_OPERATION_RELEASE;
+
+        if (create_resource_set_request(cx, internal_set) < 0) {
+            mrp_res_error("creating resource set failed");
+            mrp_list_delete(&internal_set->priv->hook);
+            goto error;
+        }
+
+        return 0;
+    }
+
+error:
+    mrp_res_error("mrp_release_resources error");
+
+    return -1;
+}
+
+
+bool mrp_res_equal_resource_set(const mrp_res_resource_set_t *a,
+                const mrp_res_resource_set_t *b)
+{
+    if (!a || !b)
+        return false;
+
+    /* Compare the internal IDs to figure out if the both sets are result
+     * of the same "create" call. */
+
+    return a->priv->internal_id == b->priv->internal_id;
+}
+
+
+mrp_res_resource_set_t *mrp_res_create_resource_set(mrp_res_context_t *cx,
+                        const char *app_class,
+                        mrp_res_resource_callback_t cb,
+                        void *userdata)
+{
+    if (cx == NULL)
+        return NULL;
+
+    return create_resource_set(cx, app_class, cb, userdata);
+}
+
+
+void mrp_res_delete_resource_set(mrp_res_resource_set_t *set)
+{
+    delete_resource_set(set);
+}
+
+
+void mrp_res_delete_resource(mrp_res_resource_t *res)
+{
+    if (res->priv->set) {
+        if (!mrp_res_delete_resource_by_name(res->priv->set, res->name)) {
+            /* hmm, strange */
+            free_resource(res);
+        }
+    }
+    else
+        free_resource(res);
+}
+
+
+bool mrp_res_delete_resource_by_name(mrp_res_resource_set_t *rs, const char *name)
+{
+    uint32_t i;
+    mrp_res_resource_t *res = NULL;
+
+    /* assumption: only one resource of given name in the resource set */
+    for (i = 0; i < rs->priv->num_resources; i++) {
+        if (strcmp(rs->priv->resources[i]->name, name) == 0) {
+            /* found at i */
+            res = rs->priv->resources[i];
+            break;
+        }
+    }
+
+    if (i == rs->priv->num_resources) {
+        /* not found */
+        return false;
+    }
+
+    memmove(rs->priv->resources+i, rs->priv->resources+i+1,
+            (rs->priv->num_resources-i) * sizeof(mrp_res_resource_t *));
+
+    rs->priv->num_resources--;
+    rs->priv->resources[rs->priv->num_resources] = NULL;
+
+    free_resource(res);
+
+    return true;
+}
+
+
+mrp_res_string_array_t * mrp_res_list_resource_names(
+                const mrp_res_resource_set_t *rs)
+{
+    uint32_t i;
+    mrp_res_string_array_t *ret;
+
+    if (!rs)
+        return NULL;
+
+    ret = mrp_allocz(sizeof(mrp_res_string_array_t));
+
+    if (!ret)
+        return NULL;
+
+    ret->num_strings = rs->priv->num_resources;
+    ret->strings = mrp_allocz_array(const char *, rs->priv->num_resources);
+
+    if (!ret->strings) {
+        mrp_free(ret);
+        return NULL;
+    }
+
+    for (i = 0; i < rs->priv->num_resources; i++) {
+        ret->strings[i] = mrp_strdup(rs->priv->resources[i]->name);
+        if (!ret->strings[i]) {
+            ret->num_strings = i;
+            mrp_res_free_string_array(ret);
+            return NULL;
+        }
+    }
+
+    return ret;
+}
+
+
+mrp_res_resource_t * mrp_res_get_resource_by_name(
+                 const mrp_res_resource_set_t *rs,
+                 const char *name)
+{
+    uint32_t i;
+
+    if (!rs)
+        return NULL;
+
+    for (i = 0; i < rs->priv->num_resources; i++) {
+        if (strcmp(name, rs->priv->resources[i]->name) == 0) {
+            return rs->priv->resources[i];
+        }
+    }
+
+    return NULL;
+}
+
+bool mrp_res_set_autorelease(bool status,
+        mrp_res_resource_set_t *rs)
+{
+    if (!rs || !rs->priv->cx)
+        return FALSE;
+
+     /* the resource library doesn't allow updating already  used sets */
+    if (rs->state != MRP_RES_RESOURCE_PENDING)
+        return FALSE;
+
+    rs->priv->autorelease = status;
+
+    return TRUE;
+}
+
+
+int mrp_res_acquire_resource_set(
+                const mrp_res_resource_set_t *original)
+{
+    mrp_res_resource_set_t *rset;
+    mrp_res_context_t *cx = original->priv->cx;
+
+    if (!cx->priv->connected) {
+        mrp_res_error("not connected to server");
+        goto error;
+    }
+
+    rset = mrp_htbl_lookup(cx->priv->internal_rset_mapping,
+            u_to_p(original->priv->internal_id));
+
+    if (!rset) {
+        mrp_res_error("trying to acquire non-existent resource set");
+        goto error;
+    }
+
+    update_library_resource_set(cx, original, rset);
+
+#if 0
+    print_resource_set(rset);
+#endif
+    if (rset->priv->id) {
+        /* the set has been already created on server */
+
+        if (rset->state == MRP_RES_RESOURCE_ACQUIRED) {
+            /* already requested, updating is not supported yet */
+            mrp_res_error("trying to re-acquire already acquired set");
+
+            /* TODO: when supported by backend
+             * type = RESPROTO_UPDATE_RESOURCE_SET
+             */
+            goto error;
+        }
+        else {
+            /* re-acquire a lost or released set */
+            return acquire_resource_set_request(cx, rset);
+        }
+    }
+    else {
+        mrp_list_hook_t *p, *n;
+        mrp_res_resource_set_private_t *pending_rset;
+        bool found = FALSE;
+
+        /* Create the resource set. The acquisition is continued
+         * when the set is created. */
+
+        /* only append if not already present in the list */
+
+        mrp_list_foreach(&cx->priv->pending_sets, p, n) {
+            pending_rset = mrp_list_entry(p, mrp_res_resource_set_private_t, hook);
+            if (pending_rset == rset->priv) {
+                found = TRUE;
+                break;
+            }
+        }
+
+        if (!found) {
+            mrp_list_append(&cx->priv->pending_sets, &rset->priv->hook);
+        }
+
+        rset->priv->waiting_for = MRP_RES_PENDING_OPERATION_ACQUIRE;
+
+        if (create_resource_set_request(cx, rset) < 0) {
+            mrp_res_error("creating resource set failed");
+            mrp_list_delete(&rset->priv->hook);
+            goto error;
+        }
+    }
+
+    return 0;
+
+error:
+    mrp_log_error("error acquiring a resource set");
+    return -1;
+}
+
+
+int mrp_res_get_resource_set_id(mrp_res_resource_set_t *rs)
+{
+    mrp_res_resource_set_t *internal_set;
+    mrp_res_context_t *cx = NULL;
+
+    if (!rs || !rs->priv || !rs->priv->cx)
+        return 0;
+
+    cx = rs->priv->cx;
+
+    internal_set = mrp_htbl_lookup(cx->priv->internal_rset_mapping,
+            u_to_p(rs->priv->internal_id));
+
+    if (!internal_set || !internal_set->priv)
+        return 0;
+
+    return internal_set->priv->id;
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/rset.h b/src/plugins/resource-native/libmurphy-resource/rset.h
new file mode 100644 (file)
index 0000000..3bee589
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_API_RSET_H__
+#define __MURPHY_RESOURCE_API_RSET_H__
+
+#include <errno.h>
+
+#include <murphy/resource/protocol.h>
+#include "resource-api.h"
+#include "resource-private.h"
+
+#define RESOURCE_MAX   32
+
+void print_resource(mrp_res_resource_t *res);
+
+void print_resource_set(mrp_res_resource_set_t *rset);
+
+
+void increase_ref(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset);
+
+void decrease_ref(mrp_res_context_t *cx,
+        mrp_res_resource_set_t *rset);
+
+
+void free_resource_set(mrp_res_resource_set_t *rset);
+
+void delete_resource_set(mrp_res_resource_set_t *rs);
+
+mrp_res_resource_set_t *resource_set_copy(
+        const mrp_res_resource_set_t *original);
+
+mrp_res_resource_t *get_resource_by_name(mrp_res_resource_set_t *rset,
+        const char *name);
+
+#endif
diff --git a/src/plugins/resource-native/libmurphy-resource/string_array.c b/src/plugins/resource-native/libmurphy-resource/string_array.c
new file mode 100644 (file)
index 0000000..53f89e7
--- /dev/null
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "string_array.h"
+#include <errno.h>
+
+
+mrp_res_string_array_t *mrp_str_array_dup(uint32_t dim, const char **arr)
+{
+    uint32_t i;
+    mrp_res_string_array_t *dup;
+
+    if (dim >= ARRAY_MAX || !arr)
+        return NULL;
+
+    if (!dim && arr) {
+        for (dim = 0;  arr[dim];  dim++)
+            ;
+    }
+
+    if (!(dup = mrp_allocz(sizeof(mrp_res_string_array_t)))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    dup->num_strings = dim;
+    dup->strings = mrp_allocz_array(const char *, dim);
+
+    if (!dup->strings) {
+        mrp_free(dup);
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    for (i = 0;   i < dim;   i++) {
+        if (arr[i]) {
+            if (!(dup->strings[i] = mrp_strdup(arr[i]))) {
+                for (; i > 0; i--) {
+                    mrp_free((void *)dup->strings[i-1]);
+                }
+                mrp_free(dup->strings);
+                mrp_free(dup);
+                errno = ENOMEM;
+                return NULL;
+            }
+        }
+    }
+
+    return dup;
+}
+
+/* public API */
+
+void mrp_res_free_string_array(mrp_res_string_array_t *arr)
+{
+    int i;
+
+    if (!arr)
+        return;
+
+    for (i = 0; i < arr->num_strings; i++)
+        mrp_free((void *) arr->strings[i]);
+
+    mrp_free(arr->strings);
+    mrp_free(arr);
+}
diff --git a/src/plugins/resource-native/libmurphy-resource/string_array.h b/src/plugins/resource-native/libmurphy-resource/string_array.h
new file mode 100644 (file)
index 0000000..4becad4
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_API_STRING_ARRAY_H__
+#define __MURPHY_RESOURCE_API_STRING_ARRAY_H__
+
+#include "resource-api.h"
+#include "resource-private.h"
+
+#define ARRAY_MAX      1024
+
+mrp_res_string_array_t *mrp_str_array_dup(uint32_t dim, const char **arr);
+
+#endif
diff --git a/src/plugins/resource-native/plugin-resource-native.c b/src/plugins/resource-native/plugin-resource-native.c
new file mode 100644 (file)
index 0000000..13afd55
--- /dev/null
@@ -0,0 +1,1225 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/common/msg.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/debug.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/console.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#include <murphy-db/mql.h>
+#include <murphy-db/mqi.h>
+
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/config-api.h>
+#include <murphy/resource/manager-api.h>
+#include <murphy/resource/protocol.h>
+
+#include <murphy/resource/resource-set.h>
+
+#define ATTRIBUTE_MAX MRP_ATTRIBUTE_MAX
+
+
+
+enum {
+    RESOURCE_ERROR  = -1,
+    ATTRIBUTE_ERROR = -1,
+    RESOURCE_OK     = 0,
+    ATTRIBUTE_OK    = 0,
+    ATTRIBUTE_LAST,
+    RESOURCE_LAST,
+};
+
+
+enum {
+    ARG_ADDRESS,
+};
+
+
+typedef struct {
+    mrp_plugin_t      *plugin;
+    mrp_event_bus_t   *plugin_bus;
+    mrp_event_watch_t *w;
+    mrp_sockaddr_t     saddr;
+    socklen_t          alen;
+    const char        *atyp;
+    mrp_transport_t   *listen;
+    mrp_list_hook_t    clients;
+} resource_data_t;
+
+typedef struct {
+    mrp_list_hook_t        list;
+    resource_data_t       *data;
+    uint32_t               id;
+    mrp_resource_client_t *rscli;
+    mrp_transport_t       *transp;
+} client_t;
+
+
+static void print_zones_cb(mrp_console_t *, void *, int, char **argv);
+static void print_classes_cb(mrp_console_t *, void *, int, char **argv);
+static void print_sets_cb(mrp_console_t *, void *, int, char **argv);
+static void print_owners_cb(mrp_console_t *, void *, int, char **argv);
+static void print_resources_cb(mrp_console_t *, void *, int, char **argv);
+
+static void resource_event_handler(uint32_t, mrp_resource_set_t *, void *);
+
+
+MRP_CONSOLE_GROUP(resource_group, "resource", NULL, NULL, {
+        MRP_TOKENIZED_CMD("zones"  , print_zones_cb, FALSE,
+                          "zones", "prints zones",
+                          "prints the available zones. The data sources "
+                          "for the printout are the internal data structures "
+                          "of the resource library."),
+        MRP_TOKENIZED_CMD("classes"  , print_classes_cb, FALSE,
+                          "classes", "prints application classes",
+                          "prints the available application classes. The "
+                          "data sources for the printout are the internal "
+                          "data structures of the resource library."),
+        MRP_TOKENIZED_CMD("sets", print_sets_cb, FALSE,
+                          "sets", "prints resource sets",
+                          "prints the current resource sets for each "
+                          "application class. The data sources for the "
+                          "printout are the internal data structures of the "
+                          "resource library"),
+        MRP_TOKENIZED_CMD("owners" , print_owners_cb , FALSE,
+                          "owners", "prints resource owners",
+                          "prints for each zone the owner application class "
+                          "of each resource. The data sources for the "
+                          "printout are the internal data structures of the "
+                          "resource library"),
+        MRP_TOKENIZED_CMD("resources" , print_resources_cb , FALSE,
+                          "resources", "prints resources",
+                          "prints all resource definitions and along with "
+                          "all their attributes. The data sources for the "
+                          "printout are the internal data structures of the "
+                          "resource library"),
+
+});
+
+
+static void print_zones_cb(mrp_console_t *c, void *user_data,
+                           int argc, char **argv)
+{
+    const char **zone_names;
+    int i;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    printf("Zones:\n");
+
+    if ((zone_names = mrp_zone_get_all_names(0, NULL))) {
+
+        for (i = 0;  zone_names[i];  i++)
+            printf("   %s\n", zone_names[i]);
+
+
+        mrp_free(zone_names);
+    }
+}
+
+
+static void print_classes_cb(mrp_console_t *c, void *user_data,
+                             int argc, char **argv)
+{
+    char buf[8192];
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    mrp_application_class_print(buf, sizeof(buf), false);
+
+    printf("%s", buf);
+}
+
+
+static void print_sets_cb(mrp_console_t *c, void *user_data,
+                          int argc, char **argv)
+{
+    static int size = 8192;
+    char       buf[size];
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    if (mrp_application_class_print(buf, sizeof(buf), true) >= size)
+        size *= 2;
+
+    printf("%s", buf);
+}
+
+
+static void print_owners_cb(mrp_console_t *c, void *user_data,
+                            int argc, char **argv)
+{
+    char buf[2048];
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    mrp_resource_owner_print(buf, sizeof(buf));
+
+    printf("%s", buf);
+}
+
+
+static void print_resources_cb(mrp_console_t *c, void *user_data,
+                               int argc, char **argv)
+{
+    const char **names;
+    mrp_attr_t  *attrs, *a;
+    mrp_attr_t   buf[ATTRIBUTE_MAX];
+    uint32_t     resid;
+
+    MRP_UNUSED(c);
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    if (!(names = mrp_resource_definition_get_all_names(0, NULL))) {
+        printf("Failed to read resource definitions.\n");
+        return;
+    }
+
+    printf("Resource definitions:\n");
+    for (resid = 0; names[resid]; resid++) {
+        attrs = mrp_resource_definition_read_all_attributes(resid,
+                                                            ATTRIBUTE_MAX, buf);
+        printf("    Resource '%s'\n", names[resid]);
+        for (a = attrs; a->name; a++) {
+            printf("        attribute %s: ", a->name);
+            switch (a->type) {
+            case mqi_string:
+                printf("'%s'\n", a->value.string);
+                break;
+            case mqi_integer:
+                printf("%d\n", a->value.integer);
+                break;
+            case mqi_unsignd:
+                printf("%u\n", a->value.unsignd);
+                break;
+            case mqi_floating:
+                printf("%f\n", a->value.floating);
+                break;
+            default:
+                printf("<unsupported type>\n");
+                break;
+            }
+        }
+    }
+
+    mrp_free(names);
+}
+
+
+#if 0
+static int set_default_configuration(void)
+{
+    typedef struct {
+        const char     *name;
+        bool            share;
+        mrp_attr_def_t *attrs;
+    } resdef_t;
+
+    static const char *zones[] = {
+        "driver",
+        "front-passenger",
+        "rear-left-passenger",
+        "rear-right-passenger",
+        NULL
+    };
+
+    static const char *classes[] = {
+        "implicit",
+        "player",
+        "game",
+        "phone",
+        "navigator",
+        NULL
+    };
+
+    static mrp_attr_def_t audio_attrs[] = {
+        { "role", MRP_RESOURCE_RW, mqi_string , .value.string="music" },
+        {  NULL ,        0       , mqi_unknown, .value.string=NULL    }
+    };
+
+    static resdef_t  resources[] = {
+        { "audio_playback" , true , audio_attrs  },
+        { "audio_recording", true , NULL         },
+        { "video_playback" , false, NULL         },
+        { "video_recording", false, NULL         },
+        {      NULL        , false, NULL         }
+    };
+
+    const char *name;
+    resdef_t *rdef;
+    uint32_t i;
+
+    mrp_zone_definition_create(NULL);
+
+    for (i = 0;  (name = zones[i]);  i++)
+        mrp_zone_create(name, NULL);
+
+    for (i = 0;  (name = classes[i]); i++)
+        mrp_application_class_create(name, i);
+
+    for (i = 0;  (rdef = resources + i)->name;  i++) {
+        mrp_resource_definition_create(rdef->name, rdef->share, rdef->attrs,
+                                       NULL, NULL);
+    }
+
+    return 0;
+}
+#endif
+
+static void reply_with_array(client_t *client, mrp_msg_t *msg,
+                             uint16_t tag, const char **arr)
+{
+    resource_data_t *data   = client->data;
+    mrp_plugin_t    *plugin = data->plugin;
+    uint16_t         dim;
+    bool             s;
+
+    for (dim = 0;  arr[dim];  dim++)
+        ;
+
+    s  = mrp_msg_append(msg, MRP_MSG_TAG_SINT16(RESPROTO_REQUEST_STATUS, 0));
+    s &= mrp_msg_append(msg, MRP_MSG_TAG_STRING_ARRAY(tag, dim, arr));
+
+    if (!s) {
+        mrp_log_error("%s: failed to build reply", plugin->instance);
+        return;
+    }
+
+    if (!mrp_transport_send(client->transp, msg))
+        mrp_log_error("%s: failed to send reply", plugin->instance);
+}
+
+static void reply_with_status(client_t *client, mrp_msg_t *msg, int16_t err)
+{
+    if (!mrp_msg_append(msg,MRP_MSG_TAG_SINT16(RESPROTO_REQUEST_STATUS,err)) ||
+        !mrp_transport_send(client->transp, msg))
+    {
+        resource_data_t *data   = client->data;
+        mrp_plugin_t    *plugin = data->plugin;
+
+        mrp_log_error("%s: failed to create or send reply", plugin->instance);
+    }
+}
+
+
+static bool write_attributes(mrp_msg_t *msg, mrp_attr_t *attrs)
+{
+#define PUSH(m, tag, typ, val)    \
+    mrp_msg_append(m, MRP_MSG_TAG_##typ(RESPROTO_##tag, val))
+
+    mrp_attr_t *a;
+    bool ok;
+
+    if (attrs) {
+        for (a = attrs;  a->name;  a++) {
+            if (!PUSH(msg, ATTRIBUTE_NAME, STRING, a->name))
+                return false;;
+
+            switch (a->type) {
+            case mqi_string:
+                ok = PUSH(msg, ATTRIBUTE_VALUE, STRING, a->value.string);
+                break;
+            case mqi_integer:
+                ok = PUSH(msg, ATTRIBUTE_VALUE, SINT32, a->value.integer);
+                break;
+            case mqi_unsignd:
+                ok = PUSH(msg, ATTRIBUTE_VALUE, UINT32, a->value.unsignd);
+                break;
+            case mqi_floating:
+                ok = PUSH(msg, ATTRIBUTE_VALUE, DOUBLE, a->value.floating);
+                break;
+            default:
+                ok = false;
+                break;
+            }
+
+            if (!ok)
+                return false;
+        }
+    }
+
+    if (!PUSH(msg, SECTION_END, UINT8, 0))
+        return false;
+
+    return true;
+
+#undef PUSH
+}
+
+
+static void query_resources_request(client_t *client, mrp_msg_t *req)
+{
+#define PUSH(m, tag, typ, val)    \
+    mrp_msg_append(m, MRP_MSG_TAG_##typ(RESPROTO_##tag, val))
+
+
+    resource_data_t  *data   = client->data;
+    mrp_plugin_t     *plugin = data->plugin;
+    const char      **names;
+    mrp_attr_t       *attrs;
+    mrp_attr_t        buf[ATTRIBUTE_MAX];
+    uint32_t          resid;
+
+    if (!(names = mrp_resource_definition_get_all_names(0, NULL)))
+        reply_with_status(client, req, ENOMEM);
+    else {
+        if (!PUSH(req, REQUEST_STATUS, SINT16, 0))
+            goto failed;
+        else {
+            for (resid = 0;   names[resid];   resid++) {
+                attrs = mrp_resource_definition_read_all_attributes(
+                                                    resid, ATTRIBUTE_MAX, buf);
+
+                if (!PUSH(req, RESOURCE_NAME, STRING, names[resid]) ||
+                    !write_attributes(req, attrs))
+                    goto failed;
+            }
+
+            if (!mrp_transport_send(client->transp, req))
+                mrp_log_error("%s: failed to send reply", plugin->instance);
+
+            mrp_free(names);
+        }
+    }
+
+    return;
+
+ failed:
+    mrp_log_error("%s: can't build recource query reply message",
+                  plugin->instance);
+    mrp_free(names);
+
+
+#undef PUSH
+}
+
+static void query_classes_request(client_t *client, mrp_msg_t *req)
+{
+    const char **names = mrp_application_class_get_all_names(0, NULL);
+
+    if (!names)
+        reply_with_status(client, req, ENOMEM);
+    else {
+        reply_with_array(client, req, RESPROTO_CLASS_NAME, names);
+        mrp_free(names);
+    }
+}
+
+static void query_zones_request(client_t *client, mrp_msg_t *req)
+{
+    const char **names = mrp_zone_get_all_names(0, NULL);
+
+    if (!names)
+        reply_with_status(client, req, ENOMEM);
+    else {
+        reply_with_array(client, req, RESPROTO_ZONE_NAME, names);
+        mrp_free(names);
+    }
+}
+
+static int read_attribute(mrp_msg_t *req, mrp_attr_t *attr, void **pcurs)
+{
+    uint16_t tag;
+    uint16_t type;
+    size_t size;
+    mrp_msg_value_t value;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size))
+        return ATTRIBUTE_ERROR;
+
+    if (tag == RESPROTO_SECTION_END)
+        return ATTRIBUTE_LAST;
+
+    if (tag != RESPROTO_ATTRIBUTE_NAME || type != MRP_MSG_FIELD_STRING)
+        return ATTRIBUTE_ERROR;
+
+    attr->name = value.str;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_ATTRIBUTE_VALUE)
+        return ATTRIBUTE_ERROR;
+
+    switch (type) {
+    case MRP_MSG_FIELD_STRING:
+        attr->type = mqi_string;
+        attr->value.string = value.str;
+        break;
+    case MRP_MSG_FIELD_SINT32:
+        attr->type = mqi_integer;
+        attr->value.integer = value.s32;
+        break;
+    case MRP_MSG_FIELD_UINT32:
+        attr->type = mqi_unsignd;
+        attr->value.unsignd = value.u32;
+        break;
+    case MRP_MSG_FIELD_DOUBLE:
+        attr->type = mqi_floating;
+        attr->value.floating = value.dbl;
+        break;
+    default:
+        return ATTRIBUTE_ERROR;
+    }
+
+    {
+        char str[256];
+
+        switch (attr->type) {
+        case mqi_string:
+            snprintf(str, sizeof(str), "'%s'", attr->value.string);
+            break;
+        case mqi_integer:
+            snprintf(str, sizeof(str), "%d", attr->value.integer);
+            break;
+        case mqi_unsignd:
+            snprintf(str, sizeof(str), "%u", attr->value.unsignd);
+            break;
+        case mqi_floating:
+            snprintf(str, sizeof(str), "%.2lf", attr->value.floating);
+            break;
+        default:
+            snprintf(str, sizeof(str), "< ??? >");
+            break;
+        }
+
+        mrp_log_info("      attribute %s:%s", attr->name, str);
+    }
+
+    return ATTRIBUTE_OK;
+}
+
+
+static int read_resource(mrp_resource_set_t *rset, mrp_msg_t *req,void **pcurs)
+{
+    uint16_t        tag;
+    uint16_t        type;
+    size_t          size;
+    mrp_msg_value_t value;
+    const char     *name;
+    bool            mand;
+    bool            shared;
+    mrp_attr_t      attrs[ATTRIBUTE_MAX + 1];
+    uint32_t        i;
+    int             arst;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size))
+        return RESOURCE_LAST;
+
+    if (tag != RESPROTO_RESOURCE_NAME || type != MRP_MSG_FIELD_STRING)
+        return RESOURCE_ERROR;
+
+    name = value.str;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_FLAGS || type != MRP_MSG_FIELD_UINT32)
+        return RESOURCE_ERROR;
+
+    mand   = (value.u32 & RESPROTO_RESFLAG_MANDATORY) ? true : false;
+    shared = (value.u32 & RESPROTO_RESFLAG_SHARED)    ? true : false;
+
+    mrp_log_info("   resource: name:'%s' %s %s", name,
+                 mand?"mandatory":"optional ", shared?"shared":"exclusive");
+
+    for (i = 0, arst = 0;    i < ATTRIBUTE_MAX;    i++) {
+        if ((arst = read_attribute(req, attrs + i, pcurs)))
+            break;
+    }
+
+    memset(attrs + i, 0, sizeof(mrp_attr_t));
+
+    if (arst > 0) {
+        if (mrp_resource_set_add_resource(rset, name, shared, attrs, mand) < 0)
+            arst = RESOURCE_ERROR;
+        else
+            arst = 0;
+    }
+
+    return arst;
+}
+
+
+static void create_resource_set_request(client_t *client, mrp_msg_t *req,
+                                        uint32_t seqno, void **pcurs)
+{
+    static uint16_t reqtyp = RESPROTO_CREATE_RESOURCE_SET;
+
+    resource_data_t        *data   = client->data;
+    mrp_plugin_t           *plugin = data->plugin;
+    mrp_resource_set_t     *rset   = 0;
+    mrp_msg_t              *rpl;
+    uint32_t                flags;
+    uint32_t                priority;
+    const char             *class;
+    const char             *zone;
+    uint16_t                tag;
+    uint16_t                type;
+    size_t                  size;
+    mrp_msg_value_t         value;
+    uint32_t                rsid;
+    int                     arst;
+    int32_t                 status;
+    bool                    auto_release;
+    bool                    auto_acquire;
+    bool                    dont_wait;
+    mrp_resource_event_cb_t event_cb;
+
+    MRP_ASSERT(client, "invalid argument");
+    MRP_ASSERT(client->rscli, "confused with data structures");
+
+    rsid = MRP_RESOURCE_ID_INVALID;
+    status = EINVAL;
+
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_FLAGS || type != MRP_MSG_FIELD_UINT32)
+        goto reply;
+
+    flags = value.u32;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_PRIORITY || type != MRP_MSG_FIELD_UINT32)
+        goto reply;
+
+    priority = value.u32;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_CLASS_NAME || type != MRP_MSG_FIELD_STRING)
+        goto reply;
+
+    class = value.str;
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_ZONE_NAME || type != MRP_MSG_FIELD_STRING)
+        goto reply;
+
+    zone = value.str;
+
+    mrp_log_info("resource-set flags:%u priority:%u class:'%s' zone:'%s'",
+                 flags, priority, class, zone);
+
+    auto_release = (flags & RESPROTO_RSETFLAG_AUTORELEASE);
+    auto_acquire = (flags & RESPROTO_RSETFLAG_AUTOACQUIRE);
+    dont_wait    = (flags & RESPROTO_RSETFLAG_DONTWAIT);
+
+    if (flags & RESPROTO_RSETFLAG_NOEVENTS)
+        event_cb = NULL;
+    else
+        event_cb = resource_event_handler;
+
+    rset = mrp_resource_set_create(client->rscli, auto_release, dont_wait,
+                                   priority, event_cb, client);
+    if (!rset)
+        goto reply;
+
+    rsid = mrp_get_resource_set_id(rset);
+
+    while ((arst = read_resource(rset, req, pcurs)) == 0)
+        ;
+
+    if (arst > 0) {
+        if (auto_acquire)
+            mrp_resource_set_acquire(rset,seqno);
+        if (mrp_application_class_add_resource_set(class,zone,rset,seqno) == 0)
+            status = 0;
+    }
+
+ reply:
+    rpl = mrp_msg_create(MRP_MSG_TAG_UINT32( RESPROTO_SEQUENCE_NO    , seqno ),
+                         MRP_MSG_TAG_UINT16( RESPROTO_REQUEST_TYPE   , reqtyp),
+                         MRP_MSG_TAG_SINT16( RESPROTO_REQUEST_STATUS , status),
+                         MRP_MSG_TAG_UINT32( RESPROTO_RESOURCE_SET_ID, rsid  ),
+                         RESPROTO_MESSAGE_END                                );
+    if (!rpl || !mrp_transport_send(client->transp, rpl)) {
+        mrp_log_error("%s: failed to send reply", plugin->instance);
+        return;
+    }
+
+    mrp_msg_unref(rpl);
+
+    if (status != 0)
+        mrp_resource_set_destroy(rset);
+}
+
+static void destroy_resource_set_request(client_t *client, mrp_msg_t *req,
+                                         void **pcurs)
+{
+    uint16_t            tag;
+    uint16_t            type;
+    size_t              size;
+    mrp_msg_value_t     value;
+    uint32_t            rset_id;
+    mrp_resource_set_t *rset;
+
+    MRP_ASSERT(client, "invalid argument");
+    MRP_ASSERT(client->rscli, "confused with data structures");
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_SET_ID || type != MRP_MSG_FIELD_UINT32)
+    {
+        reply_with_status(client, req, EINVAL);
+        return;
+    }
+
+    rset_id = value.u32;
+
+    if (!(rset = mrp_resource_client_find_set(client->rscli, rset_id))) {
+        reply_with_status(client, req, ENOENT);
+        return;
+    }
+
+    reply_with_status(client, req, 0);
+
+    mrp_resource_set_destroy(rset);
+}
+
+
+static void acquire_resource_set_request(client_t *client, mrp_msg_t *req,
+                                         uint32_t seqno, bool acquire,
+                                         void **pcurs)
+{
+    uint16_t            tag;
+    uint16_t            type;
+    size_t              size;
+    mrp_msg_value_t     value;
+    uint32_t            rset_id;
+    mrp_resource_set_t *rset;
+
+    MRP_ASSERT(client, "invalid argument");
+    MRP_ASSERT(client->rscli, "confused with data structures");
+
+    if (!mrp_msg_iterate(req, pcurs, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_SET_ID || type != MRP_MSG_FIELD_UINT32)
+    {
+        reply_with_status(client, req, EINVAL);
+        return;
+    }
+
+    rset_id = value.u32;
+
+    if (!(rset = mrp_resource_client_find_set(client->rscli, rset_id))) {
+        reply_with_status(client, req, ENOENT);
+        return;
+    }
+
+    reply_with_status(client, req, 0);
+
+    if (acquire)
+        mrp_resource_set_acquire(rset, seqno);
+    else
+        mrp_resource_set_release(rset, seqno);
+}
+
+static void connection_evt(mrp_transport_t *listen, void *user_data)
+{
+    static uint32_t  id;
+
+    resource_data_t *data   = (resource_data_t *)user_data;
+    mrp_plugin_t    *plugin = data->plugin;
+    int              flags  = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_NONBLOCK;
+    client_t        *client = mrp_allocz(sizeof(client_t));
+    char             name[256];
+
+    if (!client) {
+        mrp_log_error("%s: Memory alloc error. Can't accept new connection",
+                      plugin->instance);
+        return;
+    }
+
+    client->data = data;
+
+    snprintf(name, sizeof(name), "client%u", (client->id = ++id));
+    client->rscli = mrp_resource_client_create(name, client);
+
+    if (!(client->transp = mrp_transport_accept(listen, client, flags))) {
+        mrp_log_error("%s: failed to accept new connection", plugin->instance);
+        mrp_resource_client_destroy(client->rscli);
+        mrp_free(client);
+        return;
+    }
+
+    mrp_list_append(&data->clients, &client->list);
+
+    mrp_log_info("%s: %s connected", plugin->instance, name);
+}
+
+static void closed_evt(mrp_transport_t *transp, int error, void *user_data)
+{
+    client_t        *client = (client_t *)user_data;
+    resource_data_t *data   = client->data;
+    mrp_plugin_t    *plugin = data->plugin;
+
+    MRP_UNUSED(transp);
+
+    if (error)
+        mrp_log_error("%s: connection error %d (%s)",
+                      plugin->instance, error, strerror(error));
+    else
+        mrp_log_info("%s: peer closed connection", plugin->instance);
+
+    mrp_resource_client_destroy(client->rscli);
+
+    mrp_list_delete(&client->list);
+    mrp_free(client);
+
+    mrp_transport_disconnect(transp);
+    mrp_transport_destroy(transp);
+}
+
+
+
+static void recvfrom_msg(mrp_transport_t *transp, mrp_msg_t *msg,
+                         mrp_sockaddr_t *addr, socklen_t addrlen,
+                         void *user_data)
+{
+    client_t               *client = (client_t *)user_data;
+    resource_data_t        *data   = client->data;
+    mrp_plugin_t           *plugin = data->plugin;
+    void                   *cursor = NULL;
+    uint32_t                seqno;
+    mrp_resproto_request_t  reqtyp;
+    uint16_t                tag;
+    uint16_t                type;
+    size_t                  size;
+    mrp_msg_value_t         value;
+
+
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    MRP_ASSERT(client->transp == transp, "confused with data structures");
+
+    mrp_log_info("%s: received a message", plugin->instance);
+    mrp_msg_dump(msg, stdout);
+
+
+    if (mrp_msg_iterate(msg, &cursor, &tag, &type, &value, &size) &&
+        tag == RESPROTO_SEQUENCE_NO && type == MRP_MSG_FIELD_UINT32)
+        seqno = value.u32;
+    else {
+        mrp_log_warning("%s: malformed message. Bad or missing "
+                        "sequence number", plugin->instance);
+        return;
+    }
+
+    if (mrp_msg_iterate(msg, &cursor, &tag, &type, &value, &size) &&
+        tag == RESPROTO_REQUEST_TYPE && type == MRP_MSG_FIELD_UINT16)
+        reqtyp = value.u16;
+    else {
+        mrp_log_warning("%s: malformed message. Bad or missing "
+                        "request type", plugin->instance);
+        return;
+    }
+
+    switch (reqtyp) {
+
+    case RESPROTO_QUERY_RESOURCES:
+        query_resources_request(client, msg);
+        break;
+
+    case RESPROTO_QUERY_CLASSES:
+        query_classes_request(client, msg);
+        break;
+
+    case RESPROTO_QUERY_ZONES:
+        query_zones_request(client, msg);
+        break;
+
+    case RESPROTO_CREATE_RESOURCE_SET:
+        create_resource_set_request(client, msg, seqno, &cursor);
+        break;
+
+    case RESPROTO_DESTROY_RESOURCE_SET:
+        destroy_resource_set_request(client, msg, &cursor);
+        break;
+
+    case RESPROTO_ACQUIRE_RESOURCE_SET:
+        acquire_resource_set_request(client, msg, seqno, true, &cursor);
+        break;
+
+    case RESPROTO_RELEASE_RESOURCE_SET:
+        acquire_resource_set_request(client, msg, seqno, false, &cursor);
+        break;
+
+    default:
+        mrp_log_warning("%s: unsupported request type %d",
+                        plugin->instance, reqtyp);
+        break;
+    }
+}
+
+static void recv_msg(mrp_transport_t *transp, mrp_msg_t *msg, void *user_data)
+{
+    return recvfrom_msg(transp, msg, NULL, 0, user_data);
+}
+
+
+static void resource_event_handler(uint32_t reqid, mrp_resource_set_t *rset,
+                                   void *userdata)
+{
+#define FIELD(tag, typ, val)      \
+    RESPROTO_##tag, MRP_MSG_FIELD_##typ, val
+#define PUSH(m, tag, typ, val)    \
+    mrp_msg_append(m, MRP_MSG_TAG_##typ(RESPROTO_##tag, val))
+
+    client_t           *client = (client_t *)userdata;
+    resource_data_t    *data;
+    mrp_plugin_t       *plugin;
+    uint16_t            reqtyp;
+    uint16_t            state;
+    mrp_resource_mask_t grant;
+    mrp_resource_mask_t advice;
+    mrp_resource_mask_t mask;
+    mrp_resource_mask_t all;
+    mrp_msg_t          *msg;
+    mrp_resource_t     *res;
+    uint32_t            id;
+    const char         *name;
+    void               *curs;
+    mrp_attr_t          attrs[ATTRIBUTE_MAX + 1];
+
+    MRP_ASSERT(rset && client, "invalid argument");
+
+    data   = client->data;
+    plugin = data->plugin;
+
+    reqtyp = RESPROTO_RESOURCES_EVENT;
+    id     = mrp_get_resource_set_id(rset);
+    grant  = mrp_get_resource_set_grant(rset);
+    advice = mrp_get_resource_set_advice(rset);
+
+    if (mrp_get_resource_set_state(rset) == mrp_resource_acquire)
+        state = RESPROTO_ACQUIRE;
+    else
+        state = RESPROTO_RELEASE;
+
+    msg = mrp_msg_create(FIELD( SEQUENCE_NO    , UINT32, reqid  ),
+                         FIELD( REQUEST_TYPE   , UINT16, reqtyp ),
+                         FIELD( RESOURCE_SET_ID, UINT32, id     ),
+                         FIELD( RESOURCE_STATE , UINT16, state  ),
+                         FIELD( RESOURCE_GRANT , UINT32, grant  ),
+                         FIELD( RESOURCE_ADVICE, UINT32, advice ),
+                         RESPROTO_MESSAGE_END                   );
+
+    if (!msg)
+        goto failed;
+
+    all = grant | advice;
+    curs = NULL;
+
+    while ((res = mrp_resource_set_iterate_resources(rset, &curs))) {
+        mask = mrp_resource_get_mask(res);
+
+        if (!(all & mask))
+            continue;
+
+        id = mrp_resource_get_id(res);
+        name = mrp_resource_get_name(res);
+
+         if (!PUSH(msg, RESOURCE_ID  , UINT32, id  ) ||
+             !PUSH(msg, RESOURCE_NAME, STRING, name)  )
+             goto failed;
+
+         if (!mrp_resource_read_all_attributes(res, ATTRIBUTE_MAX + 1, attrs))
+             goto failed;
+
+         if (!write_attributes(msg, attrs))
+                 goto failed;
+    }
+
+    if (!mrp_transport_send(client->transp, msg))
+        goto failed;
+
+    mrp_msg_unref(msg);
+
+    return;
+
+    failed:
+         mrp_log_error("%s: failed to build/send message for resource event",
+                       plugin->instance);
+         mrp_msg_unref(msg);
+
+#undef PUSH
+#undef FIELD
+}
+
+
+
+static int initiate_transport(mrp_plugin_t *plugin)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg = recv_msg },
+        { .recvmsgfrom = recvfrom_msg },
+        .closed = NULL,
+        .connection = NULL
+    };
+
+    mrp_context_t    *ctx   = plugin->ctx;
+    mrp_plugin_arg_t *args  = plugin->args;
+    resource_data_t  *data  = (resource_data_t *)plugin->data;
+    const char       *addr  = args[ARG_ADDRESS].str;
+    int               flags = MRP_TRANSPORT_REUSEADDR;
+    bool              stream;
+
+    if (addr == NULL)
+        addr = mrp_resource_get_default_address();
+
+    data->alen = mrp_transport_resolve(NULL, addr, &data->saddr,
+                                       sizeof(data->saddr), &data->atyp);
+
+    if (data->alen <= 0) {
+        mrp_log_error("%s: failed to resolve transport arddress '%s'",
+                      plugin->instance, addr);
+        return -1;
+    }
+
+
+    if (strncmp(addr, "tcp", 3) && strncmp(addr, "unxs", 4))
+        stream = false;
+    else {
+        stream = true;
+        evt.connection = connection_evt;
+        evt.closed = closed_evt;
+    }
+
+    data->listen = mrp_transport_create(ctx->ml, data->atyp, &evt, data,flags);
+
+    if (!data->listen) {
+        mrp_log_error("%s: can't create listening transport",plugin->instance);
+        return -1;
+    }
+
+    if (!mrp_transport_bind(data->listen, &data->saddr, data->alen)) {
+        mrp_log_error("%s: can't bind to address %s", plugin->instance, addr);
+        return -1;
+    }
+
+    if (stream && !mrp_transport_listen(data->listen, 0)) {
+        mrp_log_error("%s: can't listen for connections", plugin->instance);
+        return -1;
+    }
+
+    mrp_log_info("%s: listening for connections on %s", plugin->instance,addr);
+
+    return 0;
+}
+
+
+static void initiate_lua_configuration(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    mrp_resource_configuration_init();
+}
+
+static void event_cb(mrp_event_watch_t *w, uint32_t id, int format,
+                     void *event_data, void *user_data)
+{
+    mrp_plugin_t     *plugin     = (mrp_plugin_t *)user_data;
+#if 0
+    mrp_plugin_arg_t *args     = plugin->args;
+#endif
+    resource_data_t  *data     = (resource_data_t *)plugin->data;
+    const char       *event    = mrp_event_name(id);
+    uint16_t          tag_inst = MRP_PLUGIN_TAG_INSTANCE;
+    uint16_t          tag_name = MRP_PLUGIN_TAG_PLUGIN;
+    const char       *inst;
+    const char       *name;
+    int               success;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(format);
+
+    mrp_log_info("%s: got event 0x%x (%s):", plugin->instance, id, event);
+
+    if (data && event) {
+        if (!strcmp(event, MRP_PLUGIN_EVENT_STARTED)) {
+            success = mrp_msg_get(event_data,
+                                  MRP_MSG_TAG_STRING(tag_inst, &inst),
+                                  MRP_MSG_TAG_STRING(tag_name, &name),
+                                  MRP_MSG_END);
+            if (success) {
+                if (!strcmp(inst, plugin->instance)) {
+#if 0
+                    set_default_configuration();
+                    mrp_log_info("%s: built-in default configuration "
+                                 "is in use", plugin->instance);
+#endif
+
+                    initiate_lua_configuration(plugin);
+                    initiate_transport(plugin);
+                }
+            }
+        } /* if PLUGIN_STARTED */
+    }
+}
+
+
+static int subscribe_events(mrp_plugin_t *plugin)
+{
+    resource_data_t  *data = (resource_data_t *)plugin->data;
+    mrp_mainloop_t   *ml   = plugin->ctx->ml;
+    mrp_event_bus_t  *bus  = mrp_event_bus_get(ml, MRP_PLUGIN_BUS);
+    mrp_event_mask_t  events;
+
+    if (bus == NULL)
+        return FALSE;
+
+    data->plugin_bus = bus;
+
+    mrp_mask_init(&events);
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_LOADED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_STARTED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_FAILED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_STOPPING));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_STOPPED));
+    mrp_mask_set(&events, mrp_event_id(MRP_PLUGIN_EVENT_UNLOADED));
+
+    data->w = mrp_event_add_watch_mask(bus, &events, event_cb, plugin);
+
+    return (data->w != NULL);
+}
+
+
+static void unsubscribe_events(mrp_plugin_t *plugin)
+{
+    resource_data_t *data = (resource_data_t *)plugin->data;
+
+    if (data->w) {
+        mrp_event_del_watch(data->w);
+        data->w = NULL;
+    }
+}
+
+
+static void register_events(mrp_plugin_t *plugin)
+{
+    MRP_UNUSED(plugin);
+
+    /* register the events that are sent on the resource state changes */
+
+    mrp_event_register(MURPHY_RESOURCE_EVENT_CREATED);
+    mrp_event_register(MURPHY_RESOURCE_EVENT_ACQUIRE);
+    mrp_event_register(MURPHY_RESOURCE_EVENT_RELEASE);
+    mrp_event_register(MURPHY_RESOURCE_EVENT_DESTROYED);
+}
+
+
+static int resource_init(mrp_plugin_t *plugin)
+{
+#if 0
+    mrp_plugin_arg_t *args = plugin->args;
+#endif
+    resource_data_t  *data;
+
+    mrp_log_info("%s() called for resource instance '%s'...", __FUNCTION__,
+                 plugin->instance);
+
+    if (!(data = mrp_allocz(sizeof(*data)))) {
+        mrp_log_error("Failed to allocate private data for resource plugin "
+                      "instance %s.", plugin->instance);
+        return FALSE;
+    }
+
+    data->plugin = plugin;
+    mrp_list_init(&data->clients);
+
+    plugin->data = data;
+
+    register_events(plugin);
+    subscribe_events(plugin);
+    initiate_lua_configuration(plugin);
+
+    return TRUE;
+}
+
+
+static void resource_exit(mrp_plugin_t *plugin)
+{
+    mrp_log_info("%s() called for test instance '%s'...", __FUNCTION__,
+                 plugin->instance);
+
+    unsubscribe_events(plugin);
+}
+
+
+#define RESOURCE_DESCRIPTION "Plugin to implement resource message protocol"
+#define RESOURCE_HELP        "Maybe later ..."
+#define RESOURCE_VERSION     MRP_VERSION_INT(0, 0, 1)
+#define RESOURCE_AUTHORS     "Janos Kovacs <jankovac503@gmail.com>"
+
+#define DEF_CONFIG_FILE      "/etc/murphy/resource.conf"
+#define DEF_ADDRESS          NULL
+
+static mrp_plugin_arg_t args[] = {
+    MRP_PLUGIN_ARGIDX( ARG_ADDRESS, STRING, "address", DEF_ADDRESS ),
+};
+
+
+MURPHY_REGISTER_PLUGIN("resource",
+                       RESOURCE_VERSION,
+                       RESOURCE_DESCRIPTION,
+                       RESOURCE_AUTHORS,
+                       RESOURCE_HELP,
+                       MRP_SINGLETON,
+                       resource_init,
+                       resource_exit,
+                       args, MRP_ARRAY_SIZE(args),
+#if 0
+                       exports, MRP_ARRAY_SIZE(exports),
+                       imports, MRP_ARRAY_SIZE(imports),
+#else
+                       NULL, 0,
+                       NULL, 0,
+#endif
+                       &resource_group);
diff --git a/src/plugins/resource-native/resource-client.c b/src/plugins/resource-native/resource-client.c
new file mode 100644 (file)
index 0000000..ead5c88
--- /dev/null
@@ -0,0 +1,1744 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/time.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <strings.h>
+#include <signal.h>
+#include <ctype.h>
+#include <libgen.h>
+#include <errno.h>
+
+#include <murphy/common.h>
+#include <murphy/resource/protocol.h>
+
+#define ARRAY_MAX      1024
+#define RESOURCE_MAX   32
+#define ATTRIBUTE_MAX  32
+
+#define INVALID_ID      (~(uint32_t)0)
+#define INVALID_INDEX   (~(uint32_t)0)
+#define INVALID_SEQNO   (~(uint32_t)0)
+#define INVALID_REQUEST (~(uint16_t)0)
+
+#define GRANT           0
+#define ADVICE          1
+
+
+typedef struct {
+    uint32_t        dim;
+    const char     *elems[0];
+} string_array_t;
+
+typedef struct {
+    const char     *name;
+    char            type;       /* s:char *, i:int32_t, u:uint32_t, f:double */
+    union {
+        const char *string;
+        int32_t     integer;
+        uint32_t    unsignd;
+        double      floating;
+    } v;
+} attribute_t;
+
+typedef struct {
+    uint32_t       dim;
+    attribute_t    elems[0];
+} attribute_array_t;
+
+typedef struct {
+    const char        *name;
+    attribute_array_t *attrs;
+} resource_def_t;
+
+typedef struct {
+    uint32_t          dim;
+    resource_def_t    defs[0];
+} resource_def_array_t;
+
+typedef struct {
+    const char           *name;
+    mrp_mainloop_t       *ml;
+    mrp_transport_t      *transp;
+    mrp_sockaddr_t        saddr;
+    socklen_t             alen;
+    const char           *atype;
+    uint32_t              seqno;
+    bool                  prompt;
+    bool                  msgdump;
+    char *                class;
+    char *                zone;
+    char *                rsetd;
+    uint32_t              rsetf;
+    uint32_t              priority;
+    resource_def_array_t *resources;
+    string_array_t       *class_names;
+    string_array_t       *zone_names;
+    uint32_t              rset_id;
+} client_t;
+
+typedef struct {
+    uint32_t              seqno;
+    uint64_t              time;
+} reqstamp_t;
+
+#define HASH_BITS         8
+#define HASH_MAX          (((uint32_t)1) << HASH_BITS)
+#define HASH_MASK         (HASH_MAX - 1)
+#define HASH_FUNC(s)      ((uint32_t)(s) & HASH_MASK)
+
+static reqstamp_t         reqstamps[HASH_MAX];
+static uint64_t           totaltime;
+static uint32_t           reqcount;
+
+static void print_prompt(client_t *, bool);
+
+
+static uint64_t reqstamp_current_time(void)
+{
+    struct timeval tv;
+
+    if (gettimeofday(&tv, NULL) < 0)
+        return 0ULL;
+
+    return ((uint64_t)tv.tv_sec * 1000000ULL) + (uint64_t)tv.tv_usec;
+}
+
+static void reqstamp_start(uint32_t seqno)
+{
+    reqstamp_t *rs  = reqstamps + HASH_FUNC(seqno);
+    uint64_t    now = reqstamp_current_time();
+
+    if (!rs->seqno && !rs->time && now) {
+        rs->seqno = seqno;
+        rs->time  = now;
+    }
+}
+
+static void reqstamp_intermediate(uint32_t seqno)
+{
+    reqstamp_t *rs   = reqstamps + HASH_FUNC(seqno);
+    uint64_t    now  = reqstamp_current_time();
+
+    if (rs->seqno == seqno && rs->time && now > rs->time) {
+        printf("request %u was responded in %.2lf msec\n",
+               seqno, (double)(now - rs->time) / 1000.0);
+    }
+}
+
+static void reqstamp_end(uint32_t seqno)
+{
+    reqstamp_t *rs   = reqstamps + HASH_FUNC(seqno);
+    uint64_t    now  = reqstamp_current_time();
+    uint64_t    diff = 0;
+
+    if (rs->seqno == seqno && rs->time) {
+        if (now > rs->time) {
+            diff = now - rs->time;
+        }
+
+        printf("request %u was processed in %.2lf msec\n",
+               seqno, (double)diff / 1000.0);
+
+        rs->seqno = 0;
+        rs->time = 0ULL;
+
+        totaltime += diff;
+        reqcount++;
+    }
+}
+
+
+static void str_array_free(string_array_t *arr)
+{
+    uint32_t i;
+
+    if (arr) {
+        for (i = 0;  i < arr->dim;  i++)
+            mrp_free((void *)arr->elems[i]);
+
+        mrp_free(arr);
+    }
+}
+
+static string_array_t *str_array_dup(uint32_t dim, const char **arr)
+{
+    size_t size;
+    uint32_t i;
+    string_array_t *dup;
+
+    MRP_ASSERT(dim < ARRAY_MAX && arr, "invalid argument");
+
+    if (!dim && arr) {
+        for (dim = 0;  arr[dim];  dim++)
+            ;
+    }
+
+    size = sizeof(string_array_t) + (sizeof(const char *) * (dim + 1));
+
+    if (!(dup = mrp_allocz(size))) {
+        errno = ENOMEM;
+        return NULL;
+    }
+
+    dup->dim = dim;
+
+    for (i = 0;   i < dim;   i++) {
+        if (arr[i]) {
+            if (!(dup->elems[i] = mrp_strdup(arr[i]))) {
+                errno = ENOMEM;
+                /* probably no use for freing anything */
+                return NULL;
+            }
+        }
+    }
+
+    return dup;
+}
+
+
+static int str_array_print(string_array_t *arr, const char *hdr,
+                           const char *sep, const char *trail,
+                           char *buf, int len)
+{
+    uint32_t i;
+    int cnt;
+
+    char *p, *e;
+
+    if (!sep)
+        sep = " ";
+
+    e = (p = buf) + len;
+    cnt = 0;
+
+    if (hdr && p < e)
+        p += snprintf(p, e-p, "%s", hdr);
+
+    if (arr) {
+        for (i = 0;  i < arr->dim && p < e;  i++) {
+            p += snprintf(p, e-p, "%s'%s'", sep, arr->elems[i]);
+            cnt++;
+        }
+    }
+
+    if (!cnt && p < e)
+        p += snprintf(p, e-p, "%s<none>", sep);
+
+    if (trail && hdr && p < e)
+        p += snprintf(p, e-p, "%s", trail);
+
+    return p - buf;
+}
+
+#if 0
+static uint32_t str_array_index(string_array_t *arr, const char *member)
+{
+    uint32_t i;
+
+    if (arr && member) {
+        for (i = 0;  i < arr->dim;  i++) {
+            if (!strcmp(member, arr->elems[i]))
+                return i;
+        }
+    }
+
+    return INVALID_INDEX;
+}
+#endif
+
+static void attribute_array_free(attribute_array_t *arr)
+{
+    uint32_t i;
+    attribute_t *attr;
+
+    if (arr) {
+        for (i = 0;   i < arr->dim;   i++) {
+            attr = arr->elems + i;
+
+            mrp_free((void *)attr->name);
+
+            if (attr->type == 's')
+                mrp_free((void *)attr->v.string);
+        }
+        mrp_free(arr);
+    }
+}
+
+static attribute_array_t *attribute_array_dup(uint32_t dim, attribute_t *arr)
+{
+    size_t size;
+    uint32_t i;
+    attribute_t *sattr, *dattr;
+    attribute_array_t *dup;
+    int err = ENOMEM;
+
+    MRP_ASSERT(dim < ARRAY_MAX && arr, "invalid argument");
+
+    if (!dim && arr) {
+        for (dim = 0;  arr[dim].name;  dim++)
+            ;
+    }
+
+    size = sizeof(attribute_array_t) + (sizeof(attribute_t) * (dim + 1));
+
+    if (!(dup = mrp_allocz(size))) {
+        goto failed;
+    }
+
+    dup->dim = dim;
+
+    for (i = 0;    i < dim;    i++) {
+        sattr = arr + i;
+        dattr = dup->elems + i;
+
+        if (!(dattr->name = mrp_strdup(sattr->name))) {
+            goto failed;
+        }
+
+        switch ((dattr->type = sattr->type)) {
+        case 's':
+            if (!(dattr->v.string = mrp_strdup(sattr->v.string))) {
+                goto failed;
+            }
+            break;
+        case 'i':
+            dattr->v.integer = sattr->v.integer;
+            break;
+        case 'u':
+            dattr->v.unsignd = sattr->v.unsignd;
+            break;
+        case 'f':
+            dattr->v.floating = sattr->v.floating;
+            break;
+        default:
+            errno = EINVAL;
+            goto failed;
+        }
+    }
+
+    return dup;
+
+ failed:
+    attribute_array_free(dup);
+    errno = err;
+    return NULL;
+}
+
+
+static int attribute_array_print(attribute_array_t *arr, const char *hdr,
+                                 const char *sep, const char *trail,
+                                 char *buf, int len)
+{
+    attribute_t *attr;
+    uint32_t i;
+    int cnt;
+
+    char *p, *e;
+
+    if (!sep)
+        sep = " ";
+
+    e = (p = buf) + len;
+    cnt = 0;
+
+    if (hdr && p < e)
+        p += snprintf(p, e-p, "%s", hdr);
+
+    if (arr) {
+        for (i = 0;  i < arr->dim && p < e;  i++) {
+            attr = arr->elems + i;
+
+            p += snprintf(p, e-p, "%s%s:%c:", sep, attr->name, attr->type);
+
+            if (p < e) {
+                switch (attr->type) {
+                case 's': p += snprintf(p,e-p, "'%s'", attr->v.string);  break;
+                case 'i': p += snprintf(p,e-p, "%d",   attr->v.integer); break;
+                case 'u': p += snprintf(p,e-p, "%u",   attr->v.unsignd); break;
+                case 'f': p += snprintf(p,e-p, "%.2lf",attr->v.floating);break;
+                default:  p += snprintf(p,e-p, "<unknown>");             break;
+                }
+            }
+
+            cnt++;
+        }
+    }
+
+    if (!cnt && hdr && p < e)
+        p += snprintf(p, e-p, "%s<none>", sep);
+
+    if (trail && hdr && p < e)
+        p += snprintf(p, e-p, "%s", trail);
+
+    return p - buf;
+}
+
+#if 0
+static uint32_t attribute_array_index(attribute_array_t *arr,
+                                      const char *member)
+{
+    uint32_t i;
+
+    if (arr && member) {
+        for (i = 0;  i < arr->dim;  i++) {
+            if (!strcmp(member, arr->elems[i].name))
+                return i;
+        }
+    }
+
+    return INVALID_INDEX;
+}
+#endif
+
+static void resource_def_array_free(resource_def_array_t *arr)
+{
+    uint32_t i;
+    resource_def_t *def;
+
+    if (arr) {
+        for (i = 0;   i < arr->dim;   i++) {
+            def = arr->defs + i;
+
+            mrp_free((void *)def->name);
+            attribute_array_free(def->attrs);
+        }
+
+        mrp_free(arr);
+    }
+}
+
+
+static int resource_def_array_print(resource_def_array_t *arr,
+                                    const char *rhdr,
+                                    const char *rsep,
+                                    const char *rtrail,
+                                    const char *ahdr,
+                                    const char *asep,
+                                    const char *atrail,
+                                    char *buf, int len)
+{
+    resource_def_t *def;
+    uint32_t i;
+    int cnt;
+
+    char *p, *e;
+
+    if (!rsep)
+        rsep = " ";
+
+    e = (p = buf) + len;
+    cnt = 0;
+
+    if (rhdr && p < e)
+        p += snprintf(p, e-p, "%s", rhdr);
+
+    if (arr) {
+        for (i = 0;  i < arr->dim && p < e;  i++) {
+            def = arr->defs + i;
+
+            p += snprintf(p, e-p, "%s%s", rsep, def->name);
+
+            if (p < e)
+                p += attribute_array_print(def->attrs,ahdr,asep,atrail,p,e-p);
+
+            cnt++;
+        }
+    }
+
+    if (!cnt && rhdr && p < e)
+        p += snprintf(p, e-p, "%s<none>", rsep);
+
+    if (rtrail && p < e)
+        p += snprintf(p, e-p, "%s", rtrail);
+
+    return p - buf;
+}
+
+
+
+
+static bool fetch_seqno(mrp_msg_t *msg, void **pcursor, uint32_t *pseqno)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_SEQUENCE_NO || type != MRP_MSG_FIELD_UINT32)
+    {
+        *pseqno = INVALID_SEQNO;
+        return false;
+    }
+
+    *pseqno = value.u32;
+    return true;
+}
+
+
+static bool fetch_request(mrp_msg_t *msg, void **pcursor, uint16_t *preqtype)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_REQUEST_TYPE || type != MRP_MSG_FIELD_UINT16)
+    {
+        *preqtype = INVALID_REQUEST;
+        return false;
+    }
+
+    *preqtype = value.u16;
+    return true;
+}
+
+static bool fetch_status(mrp_msg_t *msg, void **pcursor, int *pstatus)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_REQUEST_STATUS || type != MRP_MSG_FIELD_SINT16)
+    {
+        *pstatus = EIO;
+        return false;
+    }
+
+    *pstatus = value.s16;
+    return true;
+}
+
+static bool fetch_resource_set_id(mrp_msg_t *msg, void **pcursor,uint32_t *pid)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_SET_ID || type != MRP_MSG_FIELD_UINT32)
+    {
+        *pid = INVALID_ID;
+        return false;
+    }
+
+    *pid = value.u32;
+    return true;
+}
+
+static bool fetch_resource_set_state(mrp_msg_t *msg, void **pcursor,
+                                     mrp_resproto_state_t *pstate)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_STATE || type != MRP_MSG_FIELD_UINT16)
+    {
+        *pstate = 0;
+        return false;
+    }
+
+    *pstate = value.u16;
+    return true;
+}
+
+
+static bool fetch_resource_set_mask(mrp_msg_t *msg, void **pcursor,
+                                    int mask_type, mrp_resproto_state_t *pmask)
+{
+    uint16_t expected_tag;
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    switch (mask_type) {
+    case GRANT:    expected_tag = RESPROTO_RESOURCE_GRANT;     break;
+    case ADVICE:   expected_tag = RESPROTO_RESOURCE_ADVICE;    break;
+    default:       /* don't know what to fetch */              return false;
+    }
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != expected_tag || type != MRP_MSG_FIELD_UINT32)
+    {
+        *pmask = 0;
+        return false;
+    }
+
+    *pmask = value.u32;
+    return true;
+}
+
+static bool fetch_resource_name(mrp_msg_t *msg, void **pcursor,
+                                const char **pname)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != RESPROTO_RESOURCE_NAME || type != MRP_MSG_FIELD_STRING)
+    {
+        *pname = "<unknown>";
+        return false;
+    }
+
+    *pname = value.str;
+    return true;
+}
+
+
+static bool fetch_str_array(mrp_msg_t *msg, void **pcursor,
+                            uint16_t expected_tag, string_array_t **parr)
+{
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+
+    if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+        tag != expected_tag || type != MRP_MSG_FIELD_ARRAY_OF(STRING))
+    {
+        *parr = str_array_dup(0, NULL);
+        return false;
+    }
+
+    if (!(*parr = str_array_dup(size, (const char **)value.astr)))
+        return false;
+
+    return true;
+}
+
+static bool fetch_attribute_array(mrp_msg_t *msg, void **pcursor,
+                                 size_t dim, attribute_t *arr)
+{
+    attribute_t *attr;
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+    size_t i;
+
+    i = 0;
+
+    while (mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size)) {
+        if (tag == RESPROTO_SECTION_END && type == MRP_MSG_FIELD_UINT8)
+            break;
+
+        if (tag  != RESPROTO_ATTRIBUTE_NAME ||
+            type != MRP_MSG_FIELD_STRING    ||
+            i    >= dim - 1)
+        {
+            return false;
+        }
+
+        attr = arr + i++;
+        attr->name = value.str;
+
+        if (!mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size) ||
+            tag != RESPROTO_ATTRIBUTE_VALUE)
+        {
+            return false;
+        }
+
+        switch (type) {
+        case MRP_MSG_FIELD_STRING:
+            attr->type = 's';
+            attr->v.string = value.str;
+            break;
+        case MRP_MSG_FIELD_SINT32:
+            attr->type = 'i';
+            attr->v.integer = value.s32;
+            break;
+        case MRP_MSG_FIELD_UINT32:
+            attr->type = 'u';
+            attr->v.unsignd = value.u32;
+            break;
+        case MRP_MSG_FIELD_DOUBLE:
+            attr->type = 'f';
+            attr->v.floating = value.dbl;
+            break;
+        default:
+            return false;
+        }
+    }
+
+    memset(arr + i, 0, sizeof(attribute_t));
+
+    return true;
+}
+
+
+static void resource_query_response(client_t *client, uint32_t seqno,
+                                    mrp_msg_t *msg, void **pcursor)
+{
+    int             status;
+    uint32_t        dim, i;
+    resource_def_t  rdef[RESOURCE_MAX];
+    attribute_t     attrs[ATTRIBUTE_MAX + 1];
+    resource_def_t *src, *dst;
+    resource_def_array_t *arr;
+    size_t          size;
+    char            buf[4096];
+
+    MRP_UNUSED(seqno);
+
+    if (!fetch_status(msg, pcursor, &status))
+        goto failed;
+
+    if (status != 0)
+        printf("Resource query failed (%u): %s\n", status, strerror(status));
+    else {
+        dim = 0;
+
+        while (fetch_resource_name(msg, pcursor, &rdef[dim].name)) {
+            if (!fetch_attribute_array(msg, pcursor, ATTRIBUTE_MAX+1, attrs))
+                goto failed;
+
+            if (!(rdef[dim].attrs = attribute_array_dup(0, attrs))) {
+                mrp_log_error("failed to duplicate attributes");
+                return;
+            }
+
+            dim++;
+        }
+
+        size = sizeof(resource_def_array_t) + sizeof(resource_def_t) * (dim+1);
+
+
+        resource_def_array_free(client->resources);
+
+        client->resources = arr = mrp_allocz(size);
+
+        arr->dim = dim;
+
+        for (i = 0;  i < dim;  i++) {
+            src = rdef + i;
+            dst = arr->defs + i;
+
+            dst->name  = mrp_strdup(src->name);
+            dst->attrs = src->attrs;
+        }
+
+        resource_def_array_print(client->resources,
+                                 "Resource definitions:", "\n   ", "\n",
+                                 NULL, "\n      ", NULL,
+                                 buf, sizeof(buf));
+        printf("\n%s", buf);
+
+        client->prompt = true;
+        print_prompt(client, true);
+    }
+
+    return;
+
+ failed:
+    mrp_log_error("malformed reply to recource query");
+}
+
+static void class_query_response(client_t *client, uint32_t seqno,
+                                 mrp_msg_t *msg, void **pcursor)
+{
+    int status;
+    string_array_t *arr;
+    char buf[4096];
+
+    MRP_UNUSED(seqno);
+
+    if (!fetch_status(msg, pcursor, &status) || (status == 0 &&
+        !fetch_str_array(msg, pcursor, RESPROTO_CLASS_NAME, &arr)))
+    {
+        mrp_log_error("ignoring malformed response to class query");
+        return;
+    }
+
+    if (status) {
+        mrp_log_error("class query failed with error code %u", status);
+        return;
+    }
+
+    str_array_free(client->class_names);
+    client->class_names = arr;
+
+    str_array_print(arr, "Application class names:", "\n   ", "\n",
+                    buf, sizeof(buf));
+
+    printf("\n%s", buf);
+
+    client->prompt = true;
+    print_prompt(client, true);
+}
+
+static void zone_query_response(client_t *client, uint32_t seqno,
+                                mrp_msg_t *msg, void **pcursor)
+{
+    int status;
+    string_array_t *arr;
+    char buf[4096];
+
+    MRP_UNUSED(seqno);
+
+    if (!fetch_status(msg, pcursor, &status) || (status == 0 &&
+        !fetch_str_array(msg, pcursor, RESPROTO_ZONE_NAME, &arr)))
+    {
+        mrp_log_error("ignoring malformed response to zone query");
+        return;
+    }
+
+    if (status) {
+        mrp_log_error("zone query failed with error code %u", status);
+        return;
+    }
+
+    str_array_free(client->zone_names);
+    client->zone_names = arr;
+
+    str_array_print(arr, "Zone names:", "\n   ", "\n",
+                    buf, sizeof(buf));
+
+    printf("\n%s", buf);
+
+    client->prompt = true;
+    print_prompt(client, true);
+}
+
+static void create_resource_set_response(client_t *client, uint32_t seqno,
+                                         mrp_msg_t *msg, void **pcursor)
+{
+    int status;
+    uint32_t rset_id;
+
+    MRP_UNUSED(seqno);
+
+    if (!fetch_status(msg, pcursor, &status) || (status == 0 &&
+        !fetch_resource_set_id(msg, pcursor, &rset_id)))
+    {
+        mrp_log_error("ignoring malformed response to resource set creation");
+        return;
+    }
+
+    if (status) {
+        mrp_log_error("creation of resource set failed. error code %u",status);
+        return;
+    }
+
+    client->rset_id = rset_id;
+
+    printf("\nresource set %u created\n", rset_id);
+
+    client->prompt = true;
+    print_prompt(client, true);
+}
+
+static void acquire_resource_set_response(client_t *client, uint32_t seqno,
+                                          bool acquire, mrp_msg_t *msg,
+                                          void **pcursor)
+{
+    const char *op = acquire ? "acquisition" : "release";
+    int status;
+    uint32_t rset_id;
+
+    if (!fetch_resource_set_id(msg, pcursor, &rset_id) ||
+        !fetch_status(msg, pcursor, &status))
+    {
+        mrp_log_error("ignoring malformed response to resource set %s", op);
+        return;
+    }
+
+    if (status) {
+        printf("\n%s of resource set %u failed. request no %u "
+               "error code %u", op, rset_id, seqno, status);
+    }
+    else {
+        printf("\nSuccessful %s of resource set %u. request no %u\n",
+               op, rset_id, seqno);
+    }
+
+    client->prompt = true;
+
+    if (status)
+        print_prompt(client, true);
+}
+
+
+static void resource_event(client_t *client, uint32_t seqno, mrp_msg_t *msg,
+                           void **pcursor)
+{
+    uint32_t rset;
+    uint32_t grant, advice;
+    mrp_resproto_state_t state;
+    const char *str_state;
+    uint16_t tag;
+    uint16_t type;
+    mrp_msg_value_t value;
+    size_t size;
+    uint32_t resid;
+    const char *resnam;
+    attribute_t attrs[ATTRIBUTE_MAX + 1];
+    attribute_array_t *list;
+    char buf[4096];
+    uint32_t mask;
+    int cnt;
+
+    printf("\nResource event (request no %u):\n", seqno);
+
+    if (!fetch_resource_set_id(msg, pcursor, &rset) ||
+        !fetch_resource_set_state(msg, pcursor, &state) ||
+        !fetch_resource_set_mask(msg, pcursor, GRANT, &grant) ||
+        !fetch_resource_set_mask(msg, pcursor, ADVICE, &advice))
+        goto malformed;
+
+    switch (state) {
+    case RESPROTO_RELEASE:   str_state = "release";    break;
+    case RESPROTO_ACQUIRE:   str_state = "acquire";    break;
+    default:                 str_state = "<unknown>";  break;
+    }
+
+    printf("   resource-set ID  : %u\n"  , rset);
+    printf("   state            : %s\n"  , str_state);
+    printf("   grant mask       : 0x%x\n", grant);
+    printf("   advice mask      : 0x%x\n", advice);
+    printf("   resources        :");
+
+    cnt = 0;
+
+    while (mrp_msg_iterate(msg, pcursor, &tag, &type, &value, &size)) {
+        if ((tag != RESPROTO_RESOURCE_ID || type != MRP_MSG_FIELD_UINT32) ||
+            !fetch_resource_name(msg, pcursor, &resnam))
+            goto malformed;
+
+        resid = value.u32;
+        mask  = (1UL <<  resid);
+
+        if (!cnt++)
+            printf("\n");
+
+        printf("      %02u name       : %s\n", resid, resnam);
+        printf("         mask       : 0x%x\n", mask);
+        printf("         grant      : %s\n", (grant & mask)  ? "yes" : "no");
+        printf("         advice     : %savailable\n",
+               (advice & mask)  ? "" : "not ");
+
+        if (!fetch_attribute_array(msg, pcursor, ATTRIBUTE_MAX + 1, attrs))
+            goto malformed;
+
+        if (!(list = attribute_array_dup(0, attrs))) {
+            mrp_log_error("failed to duplicate attribute list");
+            exit(ENOMEM);
+        }
+
+        attribute_array_print(list, "         attributes :", " ", "\n",
+                              buf, sizeof(buf));
+        printf("%s", buf);
+
+        attribute_array_free(list);
+    }
+
+    if (!cnt)
+        printf(" <none>\n");
+
+    print_prompt(client, true);
+
+    return;
+
+ malformed:
+    mrp_log_error("ignoring malformed resource event");
+}
+
+
+static void recvfrom_msg(mrp_transport_t *transp, mrp_msg_t *msg,
+                         mrp_sockaddr_t *addr, socklen_t addrlen,
+                         void *user_data)
+{
+    client_t *client = (client_t *)user_data;
+    void     *cursor = NULL;
+    uint32_t  seqno;
+    uint16_t  request;
+
+    MRP_UNUSED(transp);
+    MRP_UNUSED(addr);
+    MRP_UNUSED(addrlen);
+
+    if (client->msgdump) {
+        mrp_log_info("received a message");
+        mrp_msg_dump(msg, stdout);
+    }
+
+    if (!fetch_seqno   (msg, &cursor, &seqno  ) ||
+        !fetch_request (msg, &cursor, &request)   )
+    {
+        mrp_log_error("ignoring malformed message");
+        return;
+    }
+
+
+    switch (request) {
+    case RESPROTO_QUERY_RESOURCES:
+        reqstamp_end(seqno);
+        resource_query_response(client, seqno, msg, &cursor);
+        break;
+    case RESPROTO_QUERY_CLASSES:
+        reqstamp_end(seqno);
+        class_query_response(client, seqno, msg, &cursor);
+        break;
+    case RESPROTO_QUERY_ZONES:
+        reqstamp_end(seqno);
+        zone_query_response(client, seqno, msg, &cursor);
+        break;
+    case RESPROTO_CREATE_RESOURCE_SET:
+        reqstamp_end(seqno);
+        create_resource_set_response(client, seqno, msg, &cursor);
+        break;
+    case RESPROTO_ACQUIRE_RESOURCE_SET:
+        reqstamp_intermediate(seqno);
+        acquire_resource_set_response(client, seqno, true, msg, &cursor);
+        break;
+    case RESPROTO_RELEASE_RESOURCE_SET:
+        reqstamp_intermediate(seqno);
+        acquire_resource_set_response(client, seqno, false, msg, &cursor);
+        break;
+    case RESPROTO_RESOURCES_EVENT:
+        reqstamp_end(seqno);
+        resource_event(client, seqno, msg, &cursor);
+        break;
+    default:
+        mrp_log_error("ignoring unsupported request type %u", request);
+        break;
+    }
+}
+
+static void recv_msg(mrp_transport_t *t, mrp_msg_t *msg, void *user_data)
+{
+    return recvfrom_msg(t, msg, NULL, 0, user_data);
+}
+
+
+void closed_evt(mrp_transport_t *transp, int error, void *user_data)
+{
+    MRP_UNUSED(transp);
+    MRP_UNUSED(user_data);
+
+    if (error) {
+        mrp_log_error("Connection closed with error %d (%s)", error,
+                      strerror(error));
+        exit(EIO);
+    }
+    else {
+        mrp_log_info("Peer has closed the connection");
+        exit(0);
+    }
+}
+
+
+static void init_transport(client_t *client, char *addr)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvmsg     = recv_msg },
+        { .recvmsgfrom = recvfrom_msg },
+        .closed        = closed_evt,
+        .connection    = NULL
+    };
+
+    client->alen = mrp_transport_resolve(NULL, addr, &client->saddr,
+                                         sizeof(client->saddr),&client->atype);
+    if (client->alen <= 0) {
+        mrp_log_error("Can't resolve transport address '%s'", addr);
+        exit(EINVAL);
+    }
+
+    client->transp = mrp_transport_create(client->ml, client->atype,
+                                          &evt, client, 0);
+
+    if (!client->transp) {
+        mrp_log_error("Failed to create transport");
+        exit(EIO);
+    }
+
+    if (!mrp_transport_connect(client->transp, &client->saddr, client->alen)) {
+        mrp_log_error("Failed to connect to '%s'", addr);
+        exit(EIO);
+    }
+}
+
+
+
+static mrp_msg_t *create_request(uint32_t seqno, mrp_resproto_request_t req)
+{
+    uint16_t   type  = req;
+    mrp_msg_t *msg;
+
+    msg = mrp_msg_create(RESPROTO_SEQUENCE_NO , MRP_MSG_FIELD_UINT32, seqno,
+                         RESPROTO_REQUEST_TYPE, MRP_MSG_FIELD_UINT16, type ,
+                         RESPROTO_MESSAGE_END                               );
+
+    if (!msg) {
+        mrp_log_error("Unable to create new message");
+        exit(ENOMEM);
+    }
+
+    reqstamp_start(seqno);
+
+    return msg;
+}
+
+static void send_message(client_t *client, mrp_msg_t *msg)
+{
+    if (!mrp_transport_send(client->transp, msg)) {
+        mrp_log_error("Failed to send message");
+        exit(EIO);
+    }
+
+    mrp_msg_unref(msg);
+}
+
+static void query_resources(client_t *client)
+{
+    mrp_msg_t *req;
+
+    req = create_request(client->seqno++, RESPROTO_QUERY_RESOURCES);
+
+    send_message(client, req);
+}
+
+static void query_classes(client_t *client)
+{
+    mrp_msg_t *req;
+
+    req = create_request(client->seqno++, RESPROTO_QUERY_CLASSES);
+
+    send_message(client, req);
+}
+
+static void query_zones(client_t *client)
+{
+    mrp_msg_t *req;
+
+    req = create_request(client->seqno++, RESPROTO_QUERY_ZONES);
+
+    send_message(client, req);
+}
+
+static char *parse_attribute(mrp_msg_t *msg, char *str, char *sep)
+{
+#define PUSH_ATTRIBUTE_NAME(m, n) \
+    mrp_msg_append(m, MRP_MSG_TAG_STRING(RESPROTO_ATTRIBUTE_NAME, n))
+
+#define PUSH_ATTRIBUTE_VALUE(m, t, v) \
+    mrp_msg_append(m, MRP_MSG_TAG_##t(RESPROTO_ATTRIBUTE_VALUE, v))
+
+
+    char *p, *e, c;
+    char *name;
+    char  type;
+    char *valstr;
+    uint32_t unsignd;
+    int32_t integer;
+    double floating;
+
+
+    *sep = '\0';
+
+    if (!(p = str))
+        return NULL;
+
+    name = p;
+    while ((c = *p++)) {
+        if (c == ':') {
+            *(p-1) = '\0';
+            break;
+        }
+        if (!isalnum(c) && c != '_' && c != '-') {
+            mrp_log_error("invalid attribute name: '%s'", name);
+            return NULL;
+        }
+    }
+
+    if (!c || !(type = *p++) || (*p++ != ':')) {
+        mrp_log_error("invalid or missing resource type");
+        return NULL;
+    }
+
+    if (*p == '\"') {
+        valstr = ++p;
+        while ((c = *p++) != '\"') {
+            if (!c) {
+                mrp_log_error("bad quoted value '%s'", valstr-1);
+                return NULL;
+            }
+        }
+        *(p-1) = '\0';
+        if ((c = *p)) {
+            if (c == '/' || c == ',')
+                p++;
+            else {
+                mrp_log_error("invalid separator '%s'", p);
+                return NULL;
+            }
+        }
+    }
+    else {
+        valstr = p;
+        while ((c = *p++)) {
+            if (c == '/' || c == ',') {
+                *(p-1) = '\0';
+                break;
+            }
+            if (c < 0x20) {
+                mrp_log_error("invalid attribute value '%s'", valstr);
+                return NULL;
+            }
+        }
+    }
+
+    *sep = c;
+
+    if (!PUSH_ATTRIBUTE_NAME(msg, name))
+        goto error;
+
+    if (type == 's') {
+        if (!PUSH_ATTRIBUTE_VALUE(msg, STRING, valstr))
+            goto error;
+    }
+    else if (type == 'i') {
+        integer = strtol(valstr, &e, 10);
+
+        if (*e || e == valstr || !PUSH_ATTRIBUTE_VALUE(msg, SINT32, integer))
+            goto error;
+    }
+    else if (type == 'u') {
+        unsignd = strtoul(valstr, &e, 10);
+
+        if (*e || e == valstr || !PUSH_ATTRIBUTE_VALUE(msg, UINT32, unsignd))
+            goto error;
+    }
+    else if (type == 'f') {
+        floating = strtod(valstr, &e);
+
+        if (*e || e == valstr || !PUSH_ATTRIBUTE_VALUE(msg, DOUBLE, floating))
+            goto error;
+    }
+
+
+    return (p && *p) ? p : NULL;
+
+ error:
+    mrp_log_error("failed to build resource-set creation request");
+    return NULL;
+
+#undef PUSH_ATTRIBUTE_VALUE
+#undef PUSH_ATTRIBUTE_NAME
+}
+
+bool parse_flags(char *str, uint32_t *pflags)
+{
+    typedef struct { char *str; uint32_t flags; } flagdef_t;
+
+    static flagdef_t flagdefs[] = {
+        { "M" ,    RESPROTO_RESFLAG_MANDATORY |            0            },
+        { "O" ,               0               |            0            },
+        { "S" ,    RESPROTO_RESFLAG_MANDATORY | RESPROTO_RESFLAG_SHARED },
+        { "E" ,    RESPROTO_RESFLAG_MANDATORY |            0            },
+        { "MS",    RESPROTO_RESFLAG_MANDATORY | RESPROTO_RESFLAG_SHARED },
+        { "ME",    RESPROTO_RESFLAG_MANDATORY |            0            },
+        { "OS",               0               | RESPROTO_RESFLAG_SHARED },
+        { "OE",               0               |            0            },
+        { "SM",    RESPROTO_RESFLAG_MANDATORY | RESPROTO_RESFLAG_SHARED },
+        { "SO",               0               | RESPROTO_RESFLAG_SHARED },
+        { "EM",    RESPROTO_RESFLAG_MANDATORY |            0            },
+        { "EO",               0               |            0            },
+        { NULL,               0               |            0            }
+    };
+
+    flagdef_t *fd;
+    bool success;
+
+    *pflags = RESPROTO_RESFLAG_MANDATORY;
+
+    if (!str)
+        success = true;
+    else {
+        for (success = false, fd = flagdefs;    fd->str;    fd++) {
+            if (!strcasecmp(str, fd->str)) {
+                success = true;
+                *pflags = fd->flags;
+                break;
+            }
+        }
+    }
+
+    return success;
+}
+
+static char *parse_resource(mrp_msg_t *msg, char *str, char *sep)
+{
+#define PUSH(msg, tag, typ, val) \
+    mrp_msg_append(msg, MRP_MSG_TAG_##typ(RESPROTO_##tag, val))
+
+    uint32_t flags;
+    char *name, *flgstr;
+    char *p;
+    char  c;
+
+    *sep = '\0';
+
+    if (!(p = str))
+        return NULL;
+
+    name = p;
+    flgstr = NULL;
+
+    while ((c = *p++)) {
+        if (c == ':') {
+            *(p-1) = '\0';
+            flgstr = name;
+            name = p;
+        }
+        else if (c == '/' || c == ',') {
+            *(p-1) = '\0';
+            break;
+        }
+        else if (!isalnum(c) && c != '_' && c != '-') {
+            mrp_log_error("invalid resource name: '%s'", name);
+            return NULL;
+        }
+    }
+
+    if (!parse_flags(flgstr, &flags)) {
+        mrp_log_error("invalid flag string '%s'", flgstr ? flgstr : "");
+        return NULL;
+    }
+
+    if (!PUSH(msg, RESOURCE_NAME , STRING, name ) ||
+        !PUSH(msg, RESOURCE_FLAGS, UINT32, flags)   )
+        goto failed;
+
+    if (!c)
+        p--;
+    else {
+        while ((*sep = c) == '/')
+            p = parse_attribute(msg, p, &c);
+    }
+
+    if (!PUSH(msg, SECTION_END, UINT8, 0))
+        goto failed;
+
+    return (p && *p) ? p : NULL;
+
+ failed:
+    mrp_log_error("failed to build resource-set creation request");
+    *sep = '\0';
+    return NULL;
+
+#undef PUSH
+}
+
+static void create_resource_set(client_t   *client,
+                                const char *class,
+                                const char *zone,
+                                const char *def,
+                                uint32_t    flags,
+                                uint32_t    priority)
+{
+#define PUSH(msg, tag, typ, val) \
+    mrp_msg_append(msg, MRP_MSG_TAG_##typ(RESPROTO_##tag, val))
+
+    char  *buf;
+    mrp_msg_t *req;
+    char *p;
+    char c;
+
+    /* 'def' => {m|o}{s|e}:resource_name/attr_name:{s|i|u|f}:["]value["] */
+
+    if (!client || !class || !zone || !def)
+        return;
+
+    req = create_request(client->seqno++, RESPROTO_CREATE_RESOURCE_SET);
+
+    if (!PUSH(req, RESOURCE_FLAGS   , UINT32, flags   ) ||
+        !PUSH(req, RESOURCE_PRIORITY, UINT32, priority) ||
+        !PUSH(req, CLASS_NAME       , STRING, class   ) ||
+        !PUSH(req, ZONE_NAME        , STRING, zone    )   )
+    {
+        mrp_msg_unref(req);
+    }
+    else {
+        p = buf = mrp_strdup(def);
+        c = ',';
+
+        while (c == ',')
+            p = parse_resource(req, p, &c);
+
+        if (client->msgdump)
+            mrp_msg_dump(req, stdout);
+
+        send_message(client, req);
+
+        mrp_free(buf);
+    }
+
+#undef PUSH
+}
+
+static uint32_t acquire_resource_set(client_t *client, bool acquire)
+{
+#define PUSH(msg, tag, typ, val) \
+    mrp_msg_append(msg, MRP_MSG_TAG_##typ(RESPROTO_##tag, val))
+
+    uint16_t   tag;
+    uint32_t   reqno;
+    mrp_msg_t *req;
+
+    if (!client || client->rset_id == INVALID_ID)
+        return 0;
+
+    if (acquire)
+        tag = RESPROTO_ACQUIRE_RESOURCE_SET;
+    else
+        tag = RESPROTO_RELEASE_RESOURCE_SET;
+
+    req = create_request((reqno = client->seqno++), tag);
+
+    if (!PUSH(req, RESOURCE_SET_ID, UINT32, client->rset_id))
+        mrp_msg_unref(req);
+    else {
+        if (client->msgdump)
+            mrp_msg_dump(req, stdout);
+
+        send_message(client, req);
+    }
+
+    return reqno;
+
+#undef PUSH
+}
+
+static void print_prompt(client_t *client, bool startwith_lf)
+{
+    if (client && client->prompt) {
+        printf("%s%s>", startwith_lf ? "\n":"", client->name);
+        fflush(stdout);
+    }
+}
+
+static void print_command_help(void)
+{
+    printf("\nAvailable commands:\n");
+    printf("   help\t\tprints this help\n");
+    printf("   quit\t\texits\n");
+    printf("   resources\tprints the resource definitions\n");
+    printf("   classes\tprints the application classes\n");
+    printf("   zones\tprints the zones\n");
+    printf("   acquire\tacquires the resource-set specified by command "
+           "line options\n");
+    printf("   release\treleases the resource-set specified by command "
+           "line options\n");
+}
+
+static void parse_line(client_t *client, char *buf, int len)
+{
+    char *p, *e;
+
+    if (len <= 0)
+        print_prompt(client, false);
+    else {
+        for (p = buf;  isblank(*p);  p++) ;
+        for (e = buf+len;  e > buf && isblank(*(e-1));  e--) ;
+
+        *e = '\0';
+
+        if (!strcmp(p, "help")) {
+            print_command_help();
+            print_prompt(client, true);
+        }
+        else if (!strcmp(p, "quit") || !strcmp(p, "exit")) {
+            printf("\n");
+            mrp_mainloop_quit(client->ml, 0);
+        }
+        else if (!strcmp(p, "resources")) {
+            client->prompt = false;
+            printf("   querying resource definitions\n");
+            query_resources(client);
+        }
+        else if (!strcmp(p, "classes")) {
+            client->prompt = false;
+            printf("   querying application classes\n");
+            query_classes(client);
+        }
+        else if (!strcmp(p, "zones")) {
+            client->prompt = false;
+            printf("   querying zones\n");
+            query_zones(client);
+        }
+        else if (!strcmp(p, "acquire")) {
+            if (client->rset_id == INVALID_ID) {
+                printf("   there is no resource set\n");
+                print_prompt(client, true);
+            }
+            else {
+                client->prompt = false;
+                printf("   acquiring resource set %u. request no %u\n",
+                       client->rset_id, acquire_resource_set(client, true));
+            }
+        }
+        else if (!strcmp(p, "release")) {
+            if (client->rset_id == INVALID_ID) {
+                printf("   there is no resource set\n");
+                print_prompt(client, true);
+            }
+            else {
+                client->prompt = false;
+                printf("   releasing resource set %u. request no %u\n",
+                       client->rset_id, acquire_resource_set(client, false));
+            }
+        }
+        else {
+            printf("   unsupported command\n");
+            print_prompt(client, true);
+        }
+    }
+}
+
+static void console_input(mrp_io_watch_t *w, int fd, mrp_io_event_t events,
+                          void *user_data)
+{
+    static char  buf[512];
+    static char *bufend = buf + (sizeof(buf) - 1);
+    static char *writep = buf;
+
+    client_t *client = (client_t *)user_data;
+    int       len;
+    char     *eol;
+
+    MRP_UNUSED(w);
+    MRP_UNUSED(events);
+
+    MRP_ASSERT(client, "invalid argument");
+    MRP_ASSERT(fd == 0, "confused with data structures");
+
+    while ((len = read(fd, writep, bufend-writep)) < 0) {
+        if (errno != EINTR) {
+            mrp_log_error("read error %d: %s", errno, strerror(errno));
+            return;
+        }
+    }
+
+    *(writep += len) = '\0';
+
+    while ((eol = strchr(buf, '\n'))) {
+        *eol++ = '\0';
+
+        parse_line(client, buf, (eol-buf)-1);
+
+        if ((len = writep - eol) <= 0) {
+            writep = buf;
+            break;
+        }
+        else {
+            memmove(buf, eol, len);
+            writep = buf + len;
+        }
+    }
+}
+
+static void sighandler(mrp_sighandler_t *h, int signum, void *user_data)
+{
+    mrp_mainloop_t *ml     = mrp_get_sighandler_mainloop(h);
+    client_t       *client = (client_t *)user_data;
+
+    MRP_UNUSED(h);
+
+    MRP_ASSERT(client, "invalid argument");
+
+    switch (signum) {
+
+    case SIGHUP:
+    case SIGTERM:
+    case SIGINT:
+        if (ml)
+            mrp_mainloop_quit(ml, 0);
+        break;
+
+    default:
+        break;
+    }
+}
+
+static void usage(client_t *client, int exit_code)
+{
+    printf("Usage: "
+           "%s [-h] [-v] [-r] [-a] [-w] [-p pri] [class zone resources]\n"
+           "\nwhere\n"
+           "\t-h\t\tprints this help\n"
+           "\t-v\t\tverbose mode (dumps the transport messages)\n"
+           "\t-a\t\tautoacquire mode\n"
+           "\t-w\t\tdont wait for resources if they were not available\n"
+           "\t-r\t\tautorelease mode\n"
+           "\t-p priority\t\tresource set priority (priority is 0-7)\n"
+           "\tclass\t\tapplication class of the resource set\n"
+           "\tzone\t\tzone wher the resource set lives\n"
+           "\tresources\tcomma separated list of resources. Each resource is\n"
+           "\t\t\tspecified as flags:name[/attribute[/ ... ]]\n"
+           "\t\t\tflags\t\tspecified as {m|o}{s|e} where\n"
+           "\t\t\t\t\t'm' stands for mandatory,\n"
+           "\t\t\t\t\t'o' for optional,\n"
+           "\t\t\t\t\t's' for shared and\n"
+           "\t\t\t\t\t'e' for exclusive.\n"
+           "\t\t\tresource\tis the name of the resource composed of\n"
+           "\t\t\t\t\ta series of letters, digits, '_' and\n"
+           "\t\t\t\t\t'-' characters\n"
+           "\t\t\tattribute\tis defined as attr-name:type:[\"]value[\"]\n"
+           "\t\t\t\t\ttypes can be\n"
+           "\t\t\t\t\t's' - string\n"
+           "\t\t\t\t\t'i' - signed integer\n"
+           "\t\t\t\t\t'u' - unsigned integer\n"
+           "\t\t\t\t\t'f' - floating\n"
+           "\nExample:\n\n%s player driver "
+           "ms:audio_playback/role:s:\"video\",me:video_playback\n"
+           "\n", client->name, client->name);
+
+    exit(exit_code);
+}
+
+static void parse_arguments(client_t *client, int argc, char **argv)
+{
+    unsigned long pri;
+    char *e;
+    int opt;
+
+    while ((opt = getopt(argc, argv, "hvrawp:")) != -1) {
+        switch (opt) {
+        case 'h':
+            usage(client, 0);
+        case 'v':
+            client->msgdump = true;
+            break;
+        case 'a':
+            client->rsetf |= RESPROTO_RSETFLAG_AUTOACQUIRE;
+            break;
+        case 'r':
+            client->rsetf |= RESPROTO_RSETFLAG_AUTORELEASE;
+            break;
+        case 'w':
+            client->rsetf |= RESPROTO_RSETFLAG_DONTWAIT;
+            break;
+        case 'p':
+            pri = strtoul(optarg, &e, 10);
+            if (e == optarg || *e || pri > 7)
+                usage(client, EINVAL);
+            else
+                client->priority = pri;
+            break;
+        default:
+            usage(client, EINVAL);
+        }
+    }
+
+    if (optind + 3 == argc) {
+        client->class = argv[optind + 0];
+        client->zone  = argv[optind + 1];
+        client->rsetd = argv[optind + 2];
+    }
+    else if (optind < argc) {
+        usage(client, EINVAL);
+    }
+}
+
+
+int main(int argc, char **argv)
+{
+    client_t *client = mrp_allocz(sizeof(client_t));
+    char     *addr = RESPROTO_DEFAULT_ADDRESS;
+
+    mrp_log_set_mask(MRP_LOG_UPTO(MRP_LOG_DEBUG));
+    mrp_log_set_target(MRP_LOG_TO_STDOUT);
+
+    client->name    = mrp_strdup(basename(argv[0]));
+    client->ml      = mrp_mainloop_create();
+    client->seqno   = 1;
+    client->prompt  = false;
+    client->rset_id = INVALID_ID;
+
+    if (!client->ml || !client->name)
+        exit(1);
+
+    parse_arguments(client, argc, argv);
+
+    mrp_add_sighandler(client->ml, SIGHUP , sighandler, client);
+    mrp_add_sighandler(client->ml, SIGTERM, sighandler, client);
+    mrp_add_sighandler(client->ml, SIGINT , sighandler, client);
+
+    init_transport(client, addr);
+
+
+    if (!client->class || !client->zone || !client->rsetd)
+        print_prompt(client, false);
+    else {
+        create_resource_set(client, client->class, client->zone,
+                            client->rsetd, client->rsetf, client->priority);
+    }
+
+    mrp_add_io_watch(client->ml, 0, MRP_IO_EVENT_IN, console_input, client);
+
+    mrp_mainloop_run(client->ml);
+
+    if (reqcount > 0)
+        printf("%u requests, avarage request processing time %.2lfmsec\n",
+               reqcount, (double)(totaltime / (uint64_t)reqcount) / 1000.0);
+
+    printf("exiting now ...\n");
+
+    mrp_transport_destroy(client->transp);
+
+    mrp_mainloop_destroy(client->ml);
+    mrp_free((void *)client->name);
+    resource_def_array_free(client->resources);
+    str_array_free(client->class_names);
+    str_array_free(client->zone_names);
+    mrp_free(client);
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/plugins/resource-wrt/Makefile b/src/plugins/resource-wrt/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/plugins/resource-wrt/plugin-resource-wrt.c b/src/plugins/resource-wrt/plugin-resource-wrt.c
new file mode 100644 (file)
index 0000000..36d062f
--- /dev/null
@@ -0,0 +1,1234 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/transport.h>
+#include <murphy/common/wsck-transport.h>
+#include <murphy/common/json.h>
+#include <murphy/core/plugin.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/config-api.h>
+#include <murphy/resource/manager-api.h>
+#include <murphy/resource/protocol.h>
+
+#include "resource-wrt.h"
+#include "config.h"
+
+#ifdef MURPHY_DATADIR                             /* default content dir */
+#    define DEFAULT_HTTPDIR MURPHY_DATADIR"/resource-wrt"
+#else
+#    define DEFAULT_HTTPDIR "/usr/share/murphy/resource-wrt"
+#endif
+
+#define DEFAULT_ADDRESS "wsck:127.0.0.1:4000/murphy"
+#define ATTRIBUTE_MAX   MRP_ATTRIBUTE_MAX
+
+/*
+ * plugin argument indices
+ */
+
+enum {
+    ARG_ADDRESS,                         /* transport address to use */
+    ARG_HTTPDIR,                         /* content directory for HTTP */
+    ARG_SSLCERT,                         /* path to SSL certificate */
+    ARG_SSLPKEY,                         /* path to SSL private key */
+    ARG_SSLCA                            /* path to SSL CA */
+};
+
+
+/*
+ * WRT resource context
+ */
+
+typedef struct {
+    mrp_context_t   *ctx;                /* murphy context */
+    mrp_transport_t *lt;                 /* transport we listen on */
+    const char      *addr;               /* address we listen on */
+    mrp_list_hook_t  clients;            /* connected clients */
+    int              id;                 /* next client id */
+    const char      *httpdir;            /* WRT-resource agent directory */
+    const char      *sslcert;            /* path to SSL certificate */
+    const char      *sslpkey;            /* path to SSL private key */
+    const char      *sslca;              /* path to SSL CA */
+
+} wrt_data_t;
+
+
+typedef struct {
+    int                    id;           /* client id */
+    int                    seq;          /* last request sequence number */
+    mrp_context_t         *ctx;          /* murphy context */
+    mrp_transport_t       *t;            /* client transport */
+    mrp_resource_client_t *rsc;          /* resource client */
+    mrp_list_hook_t        hook;         /* to list of clients */
+    /*
+     * Notes:
+     *    The resource infra sends the first event for a resource set
+     *    out-of-order, before it has acknowledged the creation of the
+     *    set and let the client know the resource set id. We use the
+     *    field below to suppress this event and force emitting an event
+     *    for the resource set right after we have acked the creation request.
+     */
+    mrp_resource_set_t *rset;            /* resource set being created */
+    int                 force_all;       /* flag for */
+} wrt_client_t;
+
+
+typedef struct {
+    const char *name;
+    bool        mand;
+    bool        share;
+    mrp_attr_t  attrs[ATTRIBUTE_MAX + 1];
+    int         nattr;
+} resdef_t;
+
+
+typedef struct {
+    const char *name;
+    uint32_t    flag;
+} flagdef_t;
+
+
+typedef struct {
+    int  err;
+    char msg[256];
+} errbuf_t;
+
+
+static int send_message(wrt_client_t *c, mrp_json_t *msg);
+
+static void ignore_invalid_request(wrt_client_t *c, mrp_json_t *req, ...)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(req);
+
+    mrp_log_error("Ignoring invalid WRT resource request");
+}
+
+
+static void ignore_unknown_request(wrt_client_t *c, mrp_json_t *req,
+                                   const char *type)
+{
+    MRP_UNUSED(c);
+    MRP_UNUSED(req);
+
+    mrp_log_error("Ignoring unknown WRT resource request '%s'", type);
+}
+
+
+static int error(errbuf_t *e, int code, const char *format, ...)
+{
+    va_list ap;
+
+    errno = e->err = code;
+
+    va_start(ap, format);
+    vsnprintf(e->msg, sizeof(e->msg), format, ap);
+    va_end(ap);
+
+    return code;
+}
+
+
+static mrp_json_t *alloc_reply(const char *type, int seq)
+{
+    mrp_json_t *reply;
+
+    reply = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (reply != NULL) {
+        if (mrp_json_add_string (reply, "type", type) &&
+            mrp_json_add_integer(reply, "seq" , seq))
+            return reply;
+        else
+            mrp_json_unref(reply);
+    }
+
+    mrp_log_error("Failed to allocate WRT resource reply.");
+
+    return NULL;
+}
+
+
+static void error_reply(wrt_client_t *c, const char *type, int seq, int code,
+                        const char *fmt, ...)
+{
+    mrp_json_t *reply;
+    char        errmsg[256];
+    va_list     ap;
+
+    reply = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (reply != NULL) {
+        va_start(ap, fmt);
+        vsnprintf(errmsg, sizeof(errmsg), fmt, ap);
+        errmsg[sizeof(errmsg) - 1] = '\0';
+        va_end(ap);
+
+        if (mrp_json_add_string (reply, "type"   , type) &&
+            mrp_json_add_integer(reply, "seq"    , seq ) &&
+            mrp_json_add_integer(reply, "error"  , code) &&
+            mrp_json_add_string (reply, "message", errmsg))
+            send_message(c, reply);
+
+        mrp_json_unref(reply);
+    }
+}
+
+
+static void query_resources(wrt_client_t *c, mrp_json_t *req)
+{
+    const char  *type = RESWRT_QUERY_RESOURCES;
+    mrp_json_t  *reply, *rarr, *r, *ao;
+    const char **resources;
+    int          seq, cnt;
+    mrp_attr_t  *attrs, *a;
+    mrp_attr_t   buf[ATTRIBUTE_MAX];
+    uint32_t     id;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    rarr = r = ao = NULL;
+    resources = mrp_resource_definition_get_all_names(0, NULL);
+
+    if (resources == NULL) {
+        error_reply(c, type, seq, ENOMEM, "failed to query class names");
+        return;
+    }
+
+    reply = alloc_reply(type, seq);
+
+    if (reply == NULL)
+        return;
+
+    rarr = mrp_json_create(MRP_JSON_ARRAY);
+
+    if (rarr == NULL)
+        goto fail;
+
+    for (id = 0; resources[id]; id++) {
+        r = mrp_json_create(MRP_JSON_OBJECT);
+
+        if (r == NULL)
+            goto fail;
+
+        if (!mrp_json_add_string (r, "name", resources[id]))
+            goto fail;
+
+        attrs = mrp_resource_definition_read_all_attributes(id,
+                                                            ATTRIBUTE_MAX, buf);
+
+        ao = mrp_json_create(MRP_JSON_OBJECT);
+
+        if (ao == NULL)
+            goto fail;
+
+        for (a = attrs, cnt = 0; a->name; a++, cnt++) {
+            switch (a->type) {
+            case mqi_string:
+                if (!mrp_json_add_string(ao, a->name, a->value.string))
+                    goto fail;
+
+                break;
+            case mqi_integer:
+            case mqi_unsignd:
+                if (!mrp_json_add_integer(ao, a->name, a->value.integer))
+                    goto fail;
+                break;
+            case mqi_floating:
+                if (!mrp_json_add_double(ao, a->name, a->value.floating))
+                    goto fail;
+                break;
+            default:
+                mrp_log_error("attribute '%s' of resource '%s' "
+                              "has unknown type %d", a->name, resources[id],
+                              a->type);
+                break;
+            }
+        }
+
+        if (cnt > 0)
+            mrp_json_add(r, "attributes", ao);
+        else
+            mrp_json_unref(ao);
+
+        ao = NULL;
+
+        if (!mrp_json_array_append(rarr, r))
+            goto fail;
+        else
+            r = NULL;
+    }
+
+    if (mrp_json_add_integer(reply, "status"   , 0)) {
+        mrp_json_add        (reply, "resources", rarr);
+        send_message(c, reply);
+    }
+
+    mrp_json_unref(reply);
+    mrp_free(resources);
+    return;
+
+ fail:
+    mrp_json_unref(reply);
+    mrp_json_unref(rarr);
+    mrp_json_unref(r);
+    mrp_json_unref(ao);
+    mrp_free(resources);
+}
+
+
+static void query_classes(wrt_client_t *c, mrp_json_t *req)
+{
+    const char  *type = RESWRT_QUERY_CLASSES;
+    mrp_json_t  *reply;
+    const char **classes;
+    size_t       nclass;
+    int          seq;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    classes = mrp_application_class_get_all_names(0, NULL);
+
+    if (classes == NULL) {
+        error_reply(c, type, seq, ENOMEM, "failed to query class names");
+        return;
+    }
+
+    reply = alloc_reply(type, seq);
+
+    if (reply == NULL)
+        return;
+
+    nclass = 0;
+    for (nclass = 0; classes[nclass] != NULL; nclass++)
+        ;
+
+    if (mrp_json_add_integer     (reply, "status" , 0) &&
+        mrp_json_add_string_array(reply, "classes", classes, nclass))
+        send_message(c, reply);
+
+    mrp_json_unref(reply);
+    mrp_free(classes);
+}
+
+
+static void query_zones(wrt_client_t *c, mrp_json_t *req)
+{
+    const char  *type = RESWRT_QUERY_ZONES;
+    mrp_json_t  *reply;
+    const char **zones;
+    size_t       nzone;
+    int          seq;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    zones = mrp_zone_get_all_names(0, NULL);
+
+    if (zones == NULL) {
+        error_reply(c, type, seq, ENOMEM, "failed to query zone names");
+        return;
+    }
+
+    reply = alloc_reply(type, seq);
+
+    if (reply == NULL)
+        mrp_log_error("Failed to allocate WRT resource reply.");
+
+    nzone = 0;
+    for (nzone = 0; zones[nzone] != NULL; nzone++)
+        ;
+
+    if (mrp_json_add_integer     (reply, "status", 0) &&
+        mrp_json_add_string_array(reply, "zones" , zones, nzone))
+        send_message(c, reply);
+
+    mrp_json_unref(reply);
+    mrp_free(zones);
+}
+
+
+static int parse_attributes(mrp_json_t *ja, mrp_attr_t *attrs, size_t max,
+                            errbuf_t *e)
+{
+    mrp_json_iter_t  it;
+    mrp_json_t      *v;
+    const char      *k;
+    mrp_attr_t      *attr;
+    int              nattr, cnt;
+
+    nattr = mrp_json_array_length(ja);
+
+    if (nattr >= (int)max)
+        return -error(e, EOVERFLOW, "too many attributes (%d > %d)", nattr,
+                      max - 1);
+
+    cnt  = 0;
+    attr = attrs;
+    mrp_json_foreach_member(ja, k, v, it) {
+        attr->name = k;
+
+        switch (mrp_json_get_type(v)) {
+        case MRP_JSON_STRING:
+            attr->type = mqi_string;
+            attr->value.string = mrp_json_string_value(v);
+            break;
+        case MRP_JSON_INTEGER:
+            attr->type = mqi_integer;
+            attr->value.integer = mrp_json_integer_value(v);
+            break;
+        case MRP_JSON_DOUBLE:
+            attr->type = mqi_floating;
+            attr->value.floating = mrp_json_double_value(v);
+            break;
+        case MRP_JSON_BOOLEAN:
+            attr->type = mqi_integer;
+            attr->value.integer = mrp_json_boolean_value(v);
+            break;
+        default:
+            return -error(e, EINVAL, "attribute '%s' with invalid type", k);
+        }
+
+        cnt++;
+        attr++;
+    }
+    attr->name = NULL;
+
+    return cnt;
+}
+
+
+static int append_attributes(mrp_json_t *o, mrp_attr_t *attrs, errbuf_t *e)
+{
+    mrp_json_t *a;
+    mrp_attr_t *attr;
+
+    if (attrs->name == NULL)
+        return 0;
+
+    a = mrp_json_create(MRP_JSON_OBJECT);
+
+    if (a == NULL)
+        return error(e, ENOMEM, "failed to create attributes object");
+
+    for (attr = attrs; attr->name != NULL; attr++) {
+        switch (attr->type) {
+        case mqi_string:
+            if (!mrp_json_add_string(a, attr->name, attr->value.string))
+                goto fail;
+            break;
+        case mqi_integer:
+            if (!mrp_json_add_integer(a, attr->name, attr->value.integer))
+                goto fail;
+            break;
+        case mqi_unsignd:
+            if (!mrp_json_add_integer(a, attr->name, attr->value.integer))
+                goto fail;
+            break;
+        case mqi_floating:
+            if (!mrp_json_add_double(a, attr->name, attr->value.floating))
+                goto fail;
+            break;
+        default:
+            goto fail;
+        }
+    }
+
+    mrp_json_add(o, "attributes", a);
+    return 0;
+
+ fail:
+    mrp_json_unref(a);
+
+    return error(e, EINVAL, "failed to append attribtues");
+}
+
+
+static int parse_flags(mrp_json_t *arr, flagdef_t *defs, uint32_t *flagsp,
+                       errbuf_t *e)
+{
+    const char *name;
+    flagdef_t  *d;
+    int         i, cnt;
+    uint32_t    flags;
+
+    flags = 0;
+    cnt   = mrp_json_array_length(arr);
+
+    for (i = 0; i < cnt; i++) {
+        if (mrp_json_array_get_string(arr, i, &name)) {
+            for (d = defs; d->name != NULL; d++)
+                if (!strcmp(d->name, name))
+                    break;
+
+            if (d->name == NULL)
+                return -error(e, EINVAL, "unknown flag '%s'", name);
+
+            flags |= d->flag;
+        }
+        else
+            return -error(e, EINVAL, "flags must be strings");
+    }
+
+    *flagsp = flags;
+    return 0;
+}
+
+
+static int parse_resource_definition(mrp_json_t *jr, resdef_t *d, errbuf_t *e)
+{
+#define OPTIONAL 0x1
+#define SHARED   0x2
+    static flagdef_t res_flags[] = {
+        { "optional", 0x1 },
+        { "shared"  , 0x2 },
+        { NULL, 0 }
+    };
+    mrp_json_t *jf, *ja;
+    uint32_t    flags;
+
+    if (!mrp_json_get_string(jr, "name", &d->name))
+        return -error(e, EINVAL, "missing resource name");
+
+    if (mrp_json_get_array(jr, "flags", &jf)) {
+        if (parse_flags(jf, res_flags, &flags, e) != 0)
+            return e->err;
+    }
+    else {
+        if (errno != ENOENT)
+            return -error(e, EINVAL, "invalid resource flags");
+        else
+            flags = 0;
+    }
+
+    d->mand  = !(flags & OPTIONAL);
+    d->share =   flags & SHARED;
+
+    if (mrp_json_get_object(jr, "attributes", &ja)) {
+        d->nattr = parse_attributes(ja, d->attrs, MRP_ARRAY_SIZE(d->attrs), e);
+
+        if (d->nattr < 0)
+            return -e->err;
+    }
+    else {
+        if (errno != ENOENT)
+            return -error(e, EINVAL, "invalid resource attributes");
+        else {
+            d->attrs[0].name = NULL;
+            d->nattr = 0;
+        }
+    }
+
+    return 0;
+#undef OPTIONAL
+#undef SHARED
+}
+
+
+static int block_resource_set_events(wrt_client_t *c, mrp_resource_set_t *rset)
+{
+    if (c->rset == NULL) {
+        c->rset = rset;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int allow_resource_set_events(wrt_client_t *c, mrp_resource_set_t *rset)
+{
+    if (c->rset == rset) {
+        c->rset = NULL;
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static int resource_set_events_blocked(wrt_client_t *c, mrp_resource_set_t *rs)
+{
+    return c->rset == rs;
+}
+
+
+static void emit_resource_set_event(wrt_client_t *c, uint32_t reqid,
+                                    mrp_resource_set_t *rset, int force_all)
+{
+    const char     *type = RESWRT_EVENT;
+    int             seq  = (int)reqid;
+    mrp_json_t     *msg, *rarr, *r;
+    int             rsid;
+    const char     *state;
+    int             grant, advice, all, mask;
+    errbuf_t        e;
+    mrp_resource_t *res;
+    void           *it;
+    const char     *name;
+    mrp_attr_t      attrs[ATTRIBUTE_MAX + 1];
+
+    mrp_debug("event for resource set %p of client %p", rset, c);
+
+    if (resource_set_events_blocked(c, rset)) {
+        mrp_debug("suppressing event for unacknowledged resource set");
+        return;
+    }
+
+    if (mrp_get_resource_set_state(rset) == mrp_resource_acquire)
+        state = RESWRT_STATE_GRANTED;
+    else
+        state = RESWRT_STATE_RELEASE;
+
+    rsid   = (int)mrp_get_resource_set_id(rset);
+    grant  = (int)mrp_get_resource_set_grant(rset);
+    advice = (int)mrp_get_resource_set_advice(rset);
+
+    msg = alloc_reply(type, seq);
+
+    if (msg == NULL)
+        return;
+
+    rarr = r = NULL;
+
+    if (mrp_json_add_integer(msg, "id"    , rsid ) &&
+        mrp_json_add_string (msg, "state" , state) &&
+        mrp_json_add_integer(msg, "grant" , grant) &&
+        mrp_json_add_integer(msg, "advice", advice)) {
+
+        all = grant | advice;
+        it  = NULL;
+
+        while ((res = mrp_resource_set_iterate_resources(rset, &it)) != NULL) {
+            mask = mrp_resource_get_mask(res);
+
+            if (!(mask & all) && !force_all)
+                continue;
+
+            name = mrp_resource_get_name(res);
+
+            if (!mrp_resource_read_all_attributes(res, ATTRIBUTE_MAX+1, attrs))
+                goto fail;
+
+            if (rarr == NULL) {
+                rarr = mrp_json_create(MRP_JSON_ARRAY);
+                if (rarr == NULL)
+                    goto fail;
+            }
+
+            r = mrp_json_create(MRP_JSON_OBJECT);
+
+            if (r == NULL)
+                goto fail;
+
+            if (!mrp_json_add_string (r, "name", name) ||
+                (force_all && !mrp_json_add_integer(r, "mask", mask)))
+                goto fail;
+
+            if (append_attributes(r, attrs, &e) != 0)
+                goto fail;
+
+            if (!mrp_json_array_append(rarr, r))
+                goto fail;
+            else
+                r = NULL;
+        }
+
+        if (rarr != NULL)
+            mrp_json_add(msg, "resources", rarr);
+
+        send_message(c, msg);
+        mrp_json_unref(msg);
+
+        return;
+    }
+
+ fail:
+    mrp_json_unref(msg);
+    mrp_json_unref(rarr);
+    mrp_json_unref(r);
+}
+
+
+static void event_cb(uint32_t reqid, mrp_resource_set_t *rset, void *user_data)
+{
+    emit_resource_set_event((wrt_client_t *)user_data, reqid, rset, FALSE);
+}
+
+
+static void create_set(wrt_client_t *c, mrp_json_t *req)
+{
+    static flagdef_t set_flags[] = {
+        { "autorelease", TRUE },
+        { NULL, 0 }
+    };
+    const char *type = RESWRT_CREATE_SET;
+    int         seq;
+    mrp_json_t *reply;
+    errbuf_t    e;
+    mrp_json_t *jf, *jra, *jr;
+    uint32_t    flags = 0, priority, rsid;
+    bool        autorelease;
+    bool        dontwait;
+    const char *appclass, *zone;
+    char        attr[1024], *p;
+    resdef_t    r;
+    int         i, j, cnt, n, l;
+    mrp_resource_set_t *rset;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    /* parse resource set flags */
+    if (mrp_json_get_array(req, "flags", &jf)) {
+        if (parse_flags(jf, set_flags, &flags, &e) != 0) {
+            error_reply(c, type, seq, e.err, e.msg);
+            return;
+        }
+    }
+
+    autorelease = (flags != 0);
+    dontwait    = false;
+    mrp_debug("autorelease: %s", autorelease ? "true" : "false");
+
+    /* dig out priority, class, and zone */
+    if (mrp_json_get_integer(req, "priority", &priority))
+        mrp_debug("priority: %u", priority);
+    else {
+        error_reply(c, type, seq, EINVAL, "missing or invalid 'priority'");
+        return;
+    }
+
+    if (mrp_json_get_string(req, "class", &appclass))
+        mrp_debug("class: '%s'", appclass);
+    else {
+        error_reply(c, type, seq, EINVAL, "missing or invalid 'class'");
+        return;
+    }
+
+    if (mrp_json_get_string(req, "zone", &zone))
+        mrp_debug("zone: '%s'", zone);
+    else {
+        error_reply(c, type, seq, EINVAL, "missing or invalid 'zone'");
+        return;
+    }
+
+    /* dig out resources */
+    if (!mrp_json_get_array(req, "resources", &jra) ||
+        (cnt = mrp_json_array_length(jra)) <= 0) {
+        error_reply(c, type, seq, EINVAL, "missing or invalid 'resources'");
+        return;
+    }
+
+    /* create a new resource set */
+    rset = mrp_resource_set_create(c->rsc, autorelease, dontwait,
+                                   priority, event_cb, c);
+
+    if (rset != NULL) {
+        rsid = mrp_get_resource_set_id(rset);
+
+        /* add resources to set */
+        for (i = 0; i < cnt; i++) {
+            if (mrp_json_array_get_object(jra, i, &jr)) {
+                if (parse_resource_definition(jr, &r, &e) != 0) {
+                    ignore_invalid_request(c, req, e.msg);
+                    goto fail;
+                }
+                else {
+                    mrp_debug("resource '%s': %s %s", r.name,
+                              r.mand  ? "mandatory" : "optional",
+                              r.share ? "shared"    : "exclusive");
+
+                    for (j = 0; j < r.nattr; j++) {
+                        p  = attr;
+                        n  = sizeof(attr);
+                        l  = snprintf(p, n, "'%s' = ", r.attrs[j].name);
+                        p += l;
+                        n -= l;
+
+                        switch (r.attrs[j].type) {
+                        case mqi_string:
+                            l = snprintf(p, n, "'%s'", r.attrs[j].value.string);
+                            p += l;
+                            n -= l;
+                            break;
+                        case mqi_integer:
+                            l = snprintf(p, n, "%d", r.attrs[j].value.integer);
+                            p += l;
+                            n -= l;
+                            break;
+                        case mqi_floating:
+                            l = snprintf(p, n, "%f", r.attrs[j].value.floating);
+                            p += l;
+                            n -= l;
+                            break;
+                        default:
+                            l = snprintf(p, n, "<unsupported type>");
+                            p += l;
+                            n -= l;
+                        }
+
+                        if (n > 0)
+                            mrp_debug("    attribute %s", attr);
+                    }
+
+                    if (mrp_resource_set_add_resource(rset, r.name, r.share,
+                                                      r.attrs, r.mand) < 0) {
+                        error_reply(c, type, seq, EINVAL,
+                                    "failed to add resource %s to set", r.name);
+                        goto fail;
+                    }
+                }
+            }
+        }
+
+        block_resource_set_events(c, rset);
+        /* suppress events for this resource set (client does not know id) */
+        c->rset = rset;
+
+        /* add resource set to class/zone */
+        if (mrp_application_class_add_resource_set(appclass, zone, rset, seq)) {
+            error_reply(c, type, seq, EINVAL, "failed to add set to class");
+            goto fail;
+        }
+        else {
+            reply = alloc_reply(type, seq);
+
+            if (reply != NULL) {
+                if (mrp_json_add_integer(reply, "status",0) &&
+                    mrp_json_add_integer(reply, "id", rsid)) {
+                    send_message(c, reply);
+
+                    allow_resource_set_events(c, rset);
+                    emit_resource_set_event(c, seq, rset, TRUE);
+                }
+            }
+
+            mrp_json_unref(reply);
+
+            return;
+        }
+    }
+
+ fail:
+    mrp_resource_set_destroy(rset);
+}
+
+
+static void destroy_set(wrt_client_t *c, mrp_json_t *req)
+{
+    const char         *type = RESWRT_DESTROY_SET;
+    int                 seq;
+    mrp_json_t         *reply;
+    mrp_resource_set_t *rset;
+    uint32_t            rsid;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    /* get resource set id */
+    if (!mrp_json_get_integer(req, "id", &rsid)) {
+        error_reply(c, type, seq, EINVAL, "missing id");
+        return;
+    }
+
+    rset = mrp_resource_client_find_set(c->rsc, rsid);
+
+    if (rset != NULL) {
+        reply = alloc_reply(type, seq);
+
+        if (reply != NULL) {
+            if (mrp_json_add_integer(reply, "status", 0))
+                send_message(c, reply);
+        }
+
+        mrp_resource_set_destroy(rset);
+    }
+    else
+        error_reply(c, type, seq, ENOENT, "resource set %d not found", rsid);
+}
+
+
+static void acquire_set(wrt_client_t *c, mrp_json_t *req)
+{
+    const char         *type = RESWRT_ACQUIRE_SET;
+    int                 seq;
+    mrp_json_t         *reply;
+    mrp_resource_set_t *rset;
+    uint32_t            rsid;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    /* get resource set id */
+    if (!mrp_json_get_integer(req, "id", &rsid)) {
+        error_reply(c, type, seq, EINVAL, "missing id");
+        return;
+    }
+
+    rset = mrp_resource_client_find_set(c->rsc, rsid);
+
+    if (rset != NULL) {
+        reply = alloc_reply(type, seq);
+
+        if (reply != NULL) {
+            if (mrp_json_add_integer(reply, "status", 0))
+                send_message(c, reply);
+        }
+
+        mrp_resource_set_acquire(rset, (uint32_t)seq);
+    }
+    else
+        error_reply(c, type, seq, ENOENT, "resource set %d not found", rsid);
+}
+
+
+static void release_set(wrt_client_t *c, mrp_json_t *req)
+{
+    const char         *type = RESWRT_RELEASE_SET;
+    int                 seq;
+    mrp_json_t         *reply;
+    mrp_resource_set_t *rset;
+    uint32_t            rsid;
+
+    if (!mrp_json_get_integer(req, "seq", &seq)) {
+        ignore_invalid_request(c, req, "missing 'seq' field");
+        return;
+    }
+
+    /* get resource set id */
+    if (!mrp_json_get_integer(req, "id", &rsid)) {
+        error_reply(c, type, seq, EINVAL, "missing id");
+        return;
+    }
+
+    rset = mrp_resource_client_find_set(c->rsc, rsid);
+
+    if (rset != NULL) {
+        reply = alloc_reply(type, seq);
+
+        if (reply != NULL) {
+            if (mrp_json_add_integer(reply, "status", 0))
+                send_message(c, reply);
+        }
+
+        mrp_resource_set_release(rset, (uint32_t)seq);
+    }
+    else
+        error_reply(c, type, seq, ENOENT, "resource set %d not found", rsid);
+}
+
+
+static wrt_client_t *create_client(wrt_data_t *data, mrp_transport_t *lt)
+{
+    wrt_client_t *c;
+    char          name[64];
+
+    c = mrp_allocz(sizeof(*c));
+
+    if (c != NULL) {
+        mrp_list_init(&c->hook);
+
+        c->t = mrp_transport_accept(lt, c, MRP_TRANSPORT_REUSEADDR);
+
+        if (c->t != NULL) {
+            snprintf(name, sizeof(name), "wrt-client%d", data->id++);
+            c->rsc = mrp_resource_client_create(name, c);
+
+            if (c->rsc != NULL) {
+                mrp_list_append(&data->clients, &c->hook);
+
+                return c;
+            }
+
+            mrp_transport_destroy(c->t);
+        }
+
+        mrp_free(c);
+    }
+
+    return NULL;
+}
+
+
+static void destroy_client(wrt_client_t *c)
+{
+    if (c != NULL) {
+        mrp_list_delete(&c->hook);
+
+        mrp_transport_disconnect(c->t);
+        mrp_transport_destroy(c->t);
+        mrp_resource_client_destroy(c->rsc);
+
+        mrp_free(c);
+    }
+}
+
+
+static void connection_evt(mrp_transport_t *lt, void *user_data)
+{
+    wrt_data_t   *data = (wrt_data_t *)user_data;
+    wrt_client_t *c;
+
+    c = create_client(data, lt);
+
+    if (c != NULL)
+        mrp_log_info("Accepted WRT resource client connection.");
+    else
+        mrp_log_error("Failed to accept WRT resource client connection.");
+}
+
+
+static void closed_evt(mrp_transport_t *t, int error, void *user_data)
+{
+    wrt_client_t *c = (wrt_client_t *)user_data;
+
+    MRP_UNUSED(t);
+
+    if (error != 0)
+        mrp_log_error("WRT resource connection closed with error %d (%s).",
+                      error, strerror(error));
+    else
+        mrp_log_info("WRT resource connection closed.");
+
+    destroy_client(c);
+}
+
+
+static int send_message(wrt_client_t *c, mrp_json_t *msg)
+{
+    const char *s;
+
+    s = mrp_json_object_to_string(msg);
+
+    mrp_log_info("sending WRT resource message:");
+    mrp_log_info("  %s", s);
+
+    return mrp_transport_sendcustom(c->t, msg);
+}
+
+
+static void recv_evt(mrp_transport_t *t, void *data, void *user_data)
+{
+    wrt_client_t *c   = (wrt_client_t *)user_data;
+    mrp_json_t   *req = (mrp_json_t *)data;
+    const char   *type;
+    int           seq;
+    const char   *s;
+
+    MRP_UNUSED(t);
+
+    s = mrp_json_object_to_string((mrp_json_t *)data);
+
+    mrp_log_info("recived WRT resource message:");
+    mrp_log_info("  %s", s);
+
+    if (!mrp_json_get_string (req, "type", &type) ||
+        !mrp_json_get_integer(req, "seq" , &seq))
+        ignore_invalid_request(c, req);
+    else {
+        if (seq < c->seq) {
+            mrp_log_info("ignoring out-of-date request");
+            return;
+        }
+        else
+            c->seq = seq;
+
+        if (!strcmp(type, RESWRT_QUERY_RESOURCES))
+            query_resources(c, req);
+        else if (!strcmp(type, RESWRT_QUERY_CLASSES))
+            query_classes(c, req);
+        else if (!strcmp(type, RESWRT_QUERY_ZONES))
+            query_zones(c, req);
+        else if (!strcmp(type, RESWRT_CREATE_SET))
+            create_set(c, req);
+        else if (!strcmp(type, RESWRT_DESTROY_SET))
+            destroy_set(c, req);
+        else if (!strcmp(type, RESWRT_ACQUIRE_SET))
+            acquire_set(c, req);
+        else if (!strcmp(type, RESWRT_RELEASE_SET))
+            release_set(c, req);
+        else
+            ignore_unknown_request(c, req, type);
+    }
+}
+
+
+static int transport_create(wrt_data_t *data)
+{
+    static mrp_transport_evt_t evt = {
+        { .recvcustom     = recv_evt },
+        { .recvcustomfrom = NULL     },
+        .connection       = connection_evt,
+        .closed           = closed_evt,
+    };
+
+    mrp_mainloop_t *ml   = data->ctx->ml;
+    const char     *root = data->httpdir;
+    const char     *cert = data->sslcert;
+    const char     *pkey = data->sslpkey;
+    const char     *ca   = data->sslca;
+
+    mrp_sockaddr_t  addr;
+    socklen_t       len;
+    const char     *type, *opt, *val;
+    int             flags;
+
+    len = mrp_transport_resolve(NULL, data->addr, &addr, sizeof(addr), &type);
+
+    if (len > 0) {
+        flags    = MRP_TRANSPORT_REUSEADDR | MRP_TRANSPORT_MODE_CUSTOM;
+        data->lt = mrp_transport_create(ml, type, &evt, data, flags);
+
+        if (data->lt != NULL) {
+            if (cert || pkey || ca) {
+                mrp_transport_setopt(data->lt, MRP_WSCK_OPT_SSL_CERT, cert);
+                mrp_transport_setopt(data->lt, MRP_WSCK_OPT_SSL_PKEY, pkey);
+                mrp_transport_setopt(data->lt, MRP_WSCK_OPT_SSL_CA  , ca);
+            }
+
+            if (mrp_transport_bind(data->lt, &addr, len) &&
+                mrp_transport_listen(data->lt, 0)) {
+                mrp_log_info("Listening on transport '%s'...", data->addr);
+
+                opt = MRP_WSCK_OPT_SENDMODE;
+                val = MRP_WSCK_SENDMODE_TEXT;
+                mrp_transport_setopt(data->lt, opt, val);
+                mrp_transport_setopt(data->lt, MRP_WSCK_OPT_HTTPDIR, root);
+
+                return TRUE;
+            }
+
+            mrp_transport_destroy(data->lt);
+            data->lt = NULL;
+        }
+    }
+    else
+        mrp_log_error("Failed to resolve transport address '%s'.", data->addr);
+
+    return FALSE;
+}
+
+
+static void transport_destroy(wrt_data_t *data)
+{
+    mrp_transport_destroy(data->lt);
+}
+
+
+static int plugin_init(mrp_plugin_t *plugin)
+{
+    wrt_data_t *data;
+
+    data = mrp_allocz(sizeof(*data));
+
+    if (data != NULL) {
+        mrp_list_init(&data->clients);
+
+        data->id      = 1;
+        data->ctx     = plugin->ctx;
+        data->addr    = plugin->args[ARG_ADDRESS].str;
+        data->httpdir = plugin->args[ARG_HTTPDIR].str;
+        data->sslcert = plugin->args[ARG_SSLCERT].str;
+        data->sslpkey = plugin->args[ARG_SSLPKEY].str;
+        data->sslca   = plugin->args[ARG_SSLCA].str;
+
+        if (!transport_create(data))
+            goto fail;
+
+        return TRUE;
+    }
+
+
+ fail:
+    if (data != NULL) {
+        transport_destroy(data);
+
+        mrp_free(data);
+    }
+
+    return FALSE;
+}
+
+
+static void plugin_exit(mrp_plugin_t *plugin)
+{
+    wrt_data_t *data = (wrt_data_t *)plugin->data;
+
+    transport_destroy(data);
+
+    mrp_free(data);
+}
+
+
+#define PLUGIN_DESCRIPTION "Murphy resource Web runtime bridge plugin."
+#define PLUGIN_HELP        "Murphy resource protocol for web-runtimes."
+#define PLUGIN_AUTHORS     "Krisztian Litkey <kli@iki.fi>"
+#define PLUGIN_VERSION     MRP_VERSION_INT(0, 0, 1)
+
+static mrp_plugin_arg_t plugin_args[] = {
+    MRP_PLUGIN_ARGIDX(ARG_ADDRESS, STRING, "address", DEFAULT_ADDRESS),
+    MRP_PLUGIN_ARGIDX(ARG_HTTPDIR, STRING, "httpdir", DEFAULT_HTTPDIR),
+    MRP_PLUGIN_ARGIDX(ARG_SSLCERT, STRING, "sslcert", NULL),
+    MRP_PLUGIN_ARGIDX(ARG_SSLPKEY, STRING, "sslpkey", NULL),
+    MRP_PLUGIN_ARGIDX(ARG_SSLCA  , STRING, "sslca"  , NULL)
+
+};
+
+MURPHY_REGISTER_PLUGIN("resource-wrt",
+                       PLUGIN_VERSION, PLUGIN_DESCRIPTION, PLUGIN_AUTHORS,
+                       PLUGIN_HELP, MRP_SINGLETON, plugin_init, plugin_exit,
+                       plugin_args, MRP_ARRAY_SIZE(plugin_args),
+                       NULL, 0,
+                       NULL, 0,
+                       NULL);
diff --git a/src/plugins/resource-wrt/resource-api.js b/src/plugins/resource-wrt/resource-api.js
new file mode 100644 (file)
index 0000000..a739e6b
--- /dev/null
@@ -0,0 +1,585 @@
+
+
+/*
+ * debugging
+ */
+
+var WRT_MGR = 0;                         /* resource manager debugging */
+var WRT_MSG = 1;                         /* message debugging */
+var WRT_SET = 2;                         /* resource set debugging */
+
+var wrt_debug_names = [ 'MGR', 'MSG', 'SET'];
+var wrt_debug_mask  = 0x0;
+
+
+function wrt_debug_index_of(name) {
+    for (var idx in wrt_debug_names) {
+        if (wrt_debug_names[idx] == name)
+            return idx;
+    }
+
+    return -1;
+}
+
+
+function wrt_debug_mask_of(name) {
+    var idx = wrt_debug_index_of(name);
+
+    if (idx >= 0)
+        return (1 << wrt_debug_index_of(name));
+    else
+        return 0;
+}
+
+
+function wrt_debug_set(flags, enable) {
+    var mask = 0;
+    var flag;
+
+    if (typeof flags == typeof "" || typeof flags == typeof 1)
+        flags = [ flags ];
+
+    for (var idx in flags) {
+        flag = flags[idx];
+
+        if (typeof flag == typeof "")
+            mask |= wrt_debug_mask_of(flag);
+        else
+            mask |= (1 << flag);
+    }
+
+    if (enable)
+        wrt_debug_mask |= mask;
+    else
+        wrt_debug_mask &= ~mask;
+}
+
+
+function wrt_debug_enable (flags) {
+    wrt_debug_set(flags, true);
+}
+
+
+function wrt_debug_disable(flags) {
+    wrt_debug_set(flags, false);
+}
+
+
+function wrt_debug() {
+    var flag, msg, i;
+
+    if (arguments.length >= 2) {
+        flag = arguments[0];
+        mask = (1 << flag);
+
+        if (!(mask & wrt_debug_mask))
+            return;
+
+        flag = wrt_debug_names[flag];
+
+        for (i = 1, msg = ""; i < arguments.length; i++) {
+            msg += arguments[i];
+        }
+
+        console.log("D: [" + flag + "] " + msg);
+    }
+    else {
+        if (arguments.length == 1)
+            console.log("D: [ALL] " + arguments[0]);
+    }
+}
+
+
+/*
+ * our custom error type
+ */
+
+function WrtResourceError(message) {
+    this.name    = "Resource Error";
+    this.message = message;
+}
+
+
+/*
+ * resource manager
+ */
+
+WrtResourceManager.prototype.reset = function () {
+    this.connected = false;              /* no connection */
+    this.server    = null;               /* no server */
+    this.sck       = null;               /* no socket */
+    this.reqno     = 1;                  /* next request sequence number */
+    this.reqq      = [];                 /* empty request queue */
+    this.sets      = [];                 /* no resource sets */
+
+    this.onconnect    = null;            /* clear connection callback */
+    this.ondisconnect = null;            /* clear disconnect callback */
+    this.onfailed     = null;
+}
+
+
+/** Ensure we have a connection, throw an error if we don't. */
+WrtResourceManager.prototype.check_connection = function () {
+    if (!this.connected)
+        throw new WrtResourceError("not connected");
+}
+
+
+/** Event handler for server socket connection. */
+WrtResourceManager.prototype.sckopen = function () {
+    var mgr = this.manager;
+
+    wrt_debug(WRT_MGR, "connected to server " + mgr.server);
+    wrt_debug(WRT_MGR, "mgr.sck = " + mgr.sck);
+
+    mgr.connected = true;
+
+    if (mgr.onconnect)                   /* notify listener if any */
+        mgr.onconnect();
+}
+
+
+/** Event handler for server socket disconnection. */
+WrtResourceManager.prototype.sckclose = function () {
+    var mgr = this.manager;
+
+    wrt_debug(WRT_MGR, "disconnected from server");
+
+    mgr.connected = false;
+
+    if (mgr.ondisconnect)                /* notify listener if any */
+        mgr.ondisconnect();
+
+    mgr.reset();
+}
+
+
+/** Event handler for server socket connection error. */
+WrtResourceManager.prototype.sckerror = function () {
+    var mgr = this.manager;
+
+    wrt_debug(WRT_MGR, "failed to connect to server " + mgr.server);
+
+    if (mgr.onfailed) {                  /* notify listener if any */
+        mgr.ondisconnect = null;         /* only call error handler */
+        mgr.onfailed();
+    }
+}
+
+
+/** Event handler for incoming message. */
+WrtResourceManager.prototype.sckmessage = function (message) {
+    var mgr = this.manager;
+    var msg = JSON.parse(message.data);
+    var seq = msg.seq;
+    var pending, rset;
+
+    wrt_debug(WRT_MSG, "received ", msg.type, " message (#", seq, ")");
+
+    if (msg.type == 'event') {
+        rset = mgr.sets[msg.id];
+
+        if (rset) {
+            delete mgr.reqq[seq];
+            rset.notify(msg);
+        }
+    }
+    else {
+        pending = mgr.reqq[seq];
+
+        if (pending) {
+            delete mgr.reqq[seq];
+
+            pending.notify(msg);
+        }
+    }
+}
+
+
+/** Resource set constructor. */
+function WrtResourceSet (mgr, reqno) {
+    this.manager = mgr;
+    this.reqno   = reqno;
+}
+
+
+/** Deliver resource set event notification. */
+WrtResourceSet.prototype.notify = function (msg) {
+    var type = msg.type;
+
+    if (type == 'event') {
+        wrt_debug(WRT_MSG, "resource set notification...");
+
+        if (!this.resources)
+            this.resources = msg.resources;
+
+        this.state  = msg.state;
+        this.grant  = msg.grant;
+        this.advice = msg.advice;
+
+        if (this.onstatechanged)
+            this.onstatechanged(this.grant);
+    }
+    else if (type == 'create') {
+        var status = msg.status;
+        var mgr    = this.manager;
+
+        if (status == 0) {
+            this.id = msg.id;
+            mgr.sets[this.id] = this;
+
+            if (this.onsuccess)
+                this.onsuccess();
+        }
+        else if (this.onerror) {
+            var error = {
+                error:   msg.error   ? msg.error   : 1,
+                message: msg.message ? msg.message : "<server-side error>"
+            };
+
+            this.onerror(error);
+        }
+    }
+}
+
+
+/** Map resources to names. */
+WrtResourceSet.prototype.ensure_resource_map = function () {
+    var r;
+
+    if (this.resource_by_name)
+        return;
+
+    if (!this.resources)
+        throw new WrtError("resources/masks not known yet");
+
+    this.resource_by_name = {};
+
+    for (var i in this.resources) {
+        r = this.resources[i];
+        this.resource_by_name[r.name] = {
+            mask: r.mask,
+            attributes: r.attributes
+        }
+    }
+}
+
+
+/** Pending request constructor. */
+function WrtPendingRequest (mgr, reqno) {
+    this.manager = mgr;
+    this.reqno   = reqno;
+}
+
+
+WrtPendingRequest.prototype.map_resources = function (e) {
+    var i, r, m;
+
+    m = {};
+    for (i in e) {
+        r = e[i];
+
+        if (r.attributes)
+            m[r.name] = r.attributes;
+        else
+            m[r.name] = {};
+    }
+
+    return m;
+}
+
+
+/** Deliver pending request reply notification.*/
+WrtPendingRequest.prototype.notify = function (msg) {
+    var evtmap = {
+        'query-classes'  : { field: 'classes' },
+        'query-zones'    : { field: 'zones'   },
+        'query-resources': { field: 'resources', map: this.map_resources }
+    };
+    var status = msg.status;
+    var event, m;
+
+    if (status == 0) {
+        if (this.onsuccess && (m = evtmap[msg.type])) {
+            event = m.map ? m.map(msg[m.field]) : msg[m.field];
+            this.onsuccess(event);
+        }
+    }
+    else {
+        if (this.onerror) {
+            event = {
+                error:   msg.error   ? msg.error   : 666,
+                message: msg.message ? msg.message : "<server-side error>"
+            };
+            this.onerror(event);
+        }
+    }
+}
+
+
+/** Send a request to the server, return a pending request object for it. */
+WrtResourceManager.prototype.send_request = function (req) {
+    var pending, msg, seq;
+
+    seq = this.reqno++;
+
+    wrt_debug(WRT_MSG, "sending ", req.type, " request (#", seq, ")");
+
+    if (req.type == 'create')
+        pending = new WrtResourceSet(this, seq);
+    else
+        pending = new WrtPendingRequest(this, seq);
+
+    req.seq        = seq;
+    pending.req    = req;
+    this.reqq[seq] = pending;
+
+    this.sck.send(JSON.stringify(pending.req));
+
+    return pending;
+}
+
+
+/*
+ * Public Resource Manager API
+ */
+
+/** Resource Manager constructor. */
+function WrtResourceManager() {
+    this.reset();
+}
+
+
+/** Initiate connection to the given server. */
+WrtResourceManager.prototype.connect = function (server) {
+    if (this.connected)
+        throw new WrtResourceError("already connected");
+    else {
+        wrt_debug(WRT_MGR, "trying to connect to " + server);
+
+        this.server = server
+
+        if (typeof MozWebSocket != "undefined")
+            this.sck = new MozWebSocket(this.server, "murphy");
+        else
+            this.sck = new WebSocket(this.server, "murphy");
+
+        this.sck.manager   = this;
+        this.sck.onopen    = this.sckopen;
+        this.sck.onclose   = this.sckclose;
+        this.sck.onerror   = this.sckerror;
+        this.sck.onmessage = this.sckmessage;
+    }
+}
+
+
+/** Disconnect from the server. */
+WrtResourceManager.prototype.disconnect = function () {
+    this.check_connection();
+
+    wrt_debug(WRT_MGR, "disconnecting from " + this.server);
+
+    this.sck.close();
+    delete this.sck;
+}
+
+
+/** Initiate an application class name query. */
+WrtResourceManager.prototype.queryApplicationClassNames = function () {
+    this.check_connection();
+
+    wrt_debug(WRT_MGR, "initiating application class query");
+
+    return this.send_request({ type: 'query-classes'});
+}
+
+
+/** Initiate zone name query. */
+WrtResourceManager.prototype.queryZoneNames = function () {
+    this.check_connection();
+
+    wrt_debug(WRT_MGR, "initiating zone query");
+
+    return this.send_request({ type: 'query-zones' });
+}
+
+
+/** Initiate resource definition query. */
+WrtResourceManager.prototype.queryResourceDefinitions = function () {
+    this.check_connection();
+
+    wrt_debug(WRT_MGR, "initiating resource query");
+
+    return this.send_request({ type: 'query-resources'});
+}
+
+
+/** Create a new resource set. */
+WrtResourceManager.prototype.createResourceSet = function (resources, options) {
+    var appClass, zone, priority, flags;
+    var req;
+
+    this.check_connection();
+
+    wrt_debug(WRT_MGR, "creating new resource set");
+
+    appClass = options.class    ? options.class    : "player";
+    zone     = options.zone     ? options.zone     : "driver";
+    priority = options.priority ? options.priority : 0;
+
+    return this.send_request({ type: 'create',
+                               class: appClass, zone: zone, priority: priority,
+                               resources: resources });
+}
+
+/** Determine a WebSocket URI based on an HTTP URI. */
+WrtResourceManager.prototype.socketUri = function (http_uri) {
+    var proto, colon, rest;
+
+    colon = http_uri.indexOf(':');           /* get first colon */
+    proto = http_uri.substring(0, colon);    /* get protocol */
+    rest  = http_uri.substring(colon + 3);   /* get URI sans protocol:// */
+    addr  = rest.split("/")[0];              /* strip URI path from address */
+
+    switch (proto) {
+    case "http":  return "ws://"  + addr;
+    case "https": return "wss://" + addr;
+    default:      return null;
+    }
+}
+
+/** Acquire the resource set. */
+WrtResourceSet.prototype.acquire = function () {
+    this.manager.send_request({ type: 'acquire', id: this.id });
+}
+
+
+/** Release the resource set. */
+WrtResourceSet.prototype.release = function () {
+    this.manager.send_request({ type: 'release', id: this.id });
+}
+
+
+/** Get the mask of granted resources. */
+WrtResourceSet.prototype.getGrantedMask = function () {
+    return this.grant;
+}
+
+
+/** Get the mask of allocable resources. */
+WrtResourceSet.prototype.getAllocableMask = function () {
+    return this.advice;
+}
+
+
+/** Get resource mask by resource name. */
+WrtResourceSet.prototype.getMaskByResourceName = function (name) {
+    var r;
+
+    this.ensure_resource_map();
+
+    if ((r = this.resource_by_name[name]))
+        return r.mask;
+    else
+        return 0;
+}
+
+
+/** Get resource attributes by name. */
+WrtResourceSet.prototype.getAttributesByResourceName = function (name) {
+    var r;
+
+    this.ensure_resource_map();
+
+    if ((r = this.resource_by_name[name]))
+        return r.attributes;
+    else
+        return {};
+}
+
+
+/** Get the names of all resources in the set. */
+WrtResourceSet.prototype.getResourceNames = function () {
+    var names, i;
+
+    names = [];
+    for (var i in this.resources) {
+        names.push(this.resources[i].name);
+    }
+
+    return names;
+}
+
+
+/** Get the names of granted resources in the set. */
+WrtResourceSet.prototype.getGrantedResourceNames = function () {
+    var names, n;
+
+    this.ensure_resource_map();
+
+    names = [];
+
+    for (var n in this.resource_by_name) {
+        r = this.resource_by_name[n];
+        if (this.grant & r.mask)
+            names.push(n);
+    }
+
+    return names;
+}
+
+
+/** Get the names of granted resources in the set. */
+WrtResourceSet.prototype.getAllocableResourceNames = function () {
+    var names, n;
+
+    this.ensure_resource_map();
+
+    names = [];
+
+    for (var n in this.resource_by_name) {
+        r = this.resource_by_name[n];
+        if (this.advice & r.mask)
+            names.push(n);
+    }
+
+    return names;
+}
+
+
+/** Check if the named resource has been granted. */
+WrtResourceSet.prototype.isGranted = function (name) {
+    this.ensure_resource_map();
+
+    r = this.resource_by_name[name];
+
+    if (this.grant & r.mask)
+        return true;
+    else
+        return false;
+}
+
+
+/** Check if the named resource can be allocated. */
+WrtResourceSet.prototype.isAllocable = function (name) {
+    this.ensure_resource_map();
+
+    r = this.resource_by_name[name];
+
+    if (this.advice & r.mask)
+        return true;
+    else
+        return false;
+}
+
+
+/** Check if the set has been released. */
+WrtResourceSet.prototype.isReleased = function () {
+    return (this.state == 'release');
+}
+
+
+/** Check if the set has been pre-empted. */
+WrtResourceSet.prototype.isPreempted = function () {
+    return (this.state == 'acquire' && this.grant == 0);
+}
diff --git a/src/plugins/resource-wrt/resource-test.html b/src/plugins/resource-wrt/resource-test.html
new file mode 100644 (file)
index 0000000..f87323b
--- /dev/null
@@ -0,0 +1,399 @@
+<html lang="en">
+<head> <meta charset=utf-8 /> <title>Resource-Webruntime Test</title>
+  <script src="resource-api.js"></script>
+  <script>
+
+var ctx;
+
+
+function appClassQueryReplied (classes) {
+    console.log("Application classes: " + classes);
+}
+
+function appClassQueryFailed (error) {
+    console.log("Application class query failed: " +
+                error.error, + " (" + error.message + ")");
+}
+
+
+function zoneNameQueryReplied (zones) {
+    console.log("Zones: " + zones);
+}
+
+function zoneNameQueryFailed (error) {
+    console.log("Zone query failed: " +
+                error.error, + " (" + error.message + ")");
+}
+
+
+function resourceDefinitionQueryReplied (resources) {
+    var name, r, attributes, a;
+
+    console.log("Received resource definitions:: ");
+
+    for (name in resources) {
+        console.log("    resource " + name);
+
+        attributes = resources[name];
+
+        for (a in attributes) {
+            console.log("        attribute " + a + " = " + attributes[a]);
+        }
+    }
+}
+
+function resourceDefinitionQueryFailed (error) {
+    console.log("Resource definition query failed: " +
+                error.error, + " (" + error.message + ")");
+}
+
+
+function resourceSetCreated () {
+    ctx.rset = this;
+
+    console.log("Resource set created");
+    setStatus("resource set created");
+}
+
+function resourceSetFailed (error) {
+    console.log("Resource set creation failed: " +
+                error.error + " (" + error.message + ")");
+    setStatus("resource set failed");
+}
+
+function resourceSetEvent (mask) {
+    var names, mask, attributes, grant, advice, gnames, anames;
+
+    console.log("Resource set event: granted resource mask = " + mask);
+
+    names  = this.getResourceNames();
+    anames = this.getAllocableResourceNames();
+    gnames = this.getGrantedResourceNames();
+
+    console.log("All resources: " + names);
+    console.log("Granted resources: " + gnames);
+    console.log("Allocable resources: " + anames);
+
+    for (var i in names) {
+        console.log("mask of " + names[i] + ": " +
+                    this.getMaskByResourceName(names[i]));
+
+        if ((attributes = this.getAttributesByResourceName(names[i])))
+            console.log("attributes of " + names[i] + ": " + attributes);
+
+        console.log("isGranted: " + this.isGranted(names[i]));
+        console.log("isAllocable: " + this.isAllocable(names[i]));
+    }
+
+    if (mask == 0) {
+        setStatus(this.isPreempted ? "lost" : "released");
+    }
+    else
+        setStatus("acquired");
+
+    names = this.getResourceNames();
+    grant = this.getGrantedMask();
+    advice = this.getAllocableMask();
+
+    setContent('grant', grant);
+    setContent('advice', advice);
+    setContent('granted', gnames);
+    setContent('allocable', anames);
+
+    if (this.isGranted("audio_playback"))
+        playAudio();
+    else
+        stopAudio();
+}
+
+
+function managerConnected () {
+    var ctx = this.user_data;
+    var mgr = ctx.mgr;
+    var aqry, zqry, rqry, rset;
+
+    console.log("Manager connection up...");
+
+    setStatus("connected")
+
+    disableButton("connect");
+    enableButton("disconnect")
+    enableButton("acquire")
+    enableButton("release")
+
+
+    try {
+        aqry = mgr.queryApplicationClassNames();
+        aqry.onsuccess = appClassQueryReplied;
+        aqry.onerror   = appClassQueryFailed;
+
+        zqry = mgr.queryZoneNames();
+        zqry.onsuccess = zoneNameQueryReplied;
+        zqry.onerror   = zoneNameQueryFailed;
+
+        rqry = mgr.queryResourceDefinitions();
+        rqry.onsuccess = resourceDefinitionQueryReplied;
+        rqry.onerror   = resourceDefinitionQueryFailed;
+
+        var resources = [
+            {
+                name: 'audio_playback', attributes: { role: 'media' }
+            },
+            {
+                name: 'video_playback', flags: ['optional']
+            }
+        ];
+        var options = { priority: 0, 'class': 'player' };
+
+        setStatus("creating resource set");
+        rset = mgr.createResourceSet(resources, options);
+        rset.onsuccess      = resourceSetCreated;
+        rset.onerror        = resourceSetFailed;
+        rset.onstatechanged = resourceSetEvent;
+    }
+    catch (e) {
+        console.log("Query failed: " +
+                    e.error + "(" + e.message + ")");
+    }
+}
+
+
+function managerDisconnected () {
+    console.log("Manager connection down...");
+    setStatus("disconnected");
+
+    enableButton("connect");
+    disableButton("disconnect")
+    disableButton("acquire")
+    disableButton("release")
+
+    setContent('grant', "");
+    setContent('advice', "");
+    setContent('granted', "");
+    setContent('allocable', "");
+}
+
+
+function managerFailed () {
+    console.log("Manager failed to connect...");
+    setStatus("failed to connect");
+}
+
+
+function runtest () {
+    var mgr;
+
+    wrt_debug_enable([WRT_MGR, WRT_MSG]);
+
+    ctx = {};
+    mgr = new WrtResourceManager();
+
+    mgr.onconnect    = managerConnected;
+    mgr.ondisconnect = managerDisconnected;
+    mgr.onfailed     = managerFailed;
+    mgr.user_data    = ctx;
+
+    ctx.mgr = mgr;
+
+    mgr.connect(mgr.resourceUri(document.URL));
+}
+
+
+function setContent(id, text) {
+    var elem = document.getElementById(id);
+
+    if (elem)
+        elem.textContent = text;
+}
+
+
+function setStatus(status) {
+    setContent('status', status);
+}
+
+
+function enableButton(name) {
+    var btn = document.getElementById(name);
+
+    if (btn)
+        btn.disabled = false;
+}
+
+
+function disableButton(name) {
+    var btn = document.getElementById(name);
+
+    if (btn)
+        btn.disabled = true;
+}
+
+
+function initialize() {
+    disableButton("disconnect");
+    disableButton("acquire");
+    disableButton("release");
+    enableButton("connect");
+}
+
+
+function connect() {
+    var mgr;
+
+    setStatus("connecting...");
+
+    wrt_debug_enable([WRT_MGR, WRT_MSG]);
+
+    ctx = {};
+    mgr = new WrtResourceManager();
+
+    mgr.onconnect    = managerConnected;
+    mgr.ondisconnect = managerDisconnected;
+    mgr.onfailed     = managerFailed;
+    mgr.user_data    = ctx;
+
+    ctx.mgr = mgr;
+
+    console.log("server URI: " + mgr.socketUri(document.URL));
+    mgr.connect(mgr.socketUri(document.URL));
+}
+
+
+function disconnect() {
+    var mgr = ctx.mgr;
+
+    setStatus("disconnecting...");
+
+    mgr.disconnect();
+}
+
+
+function acquire() {
+    setStatus("acquiring...");
+
+    ctx.rset.acquire();
+}
+
+
+function release() {
+    setStatus("releasing...");
+
+    ctx.rset.release();
+}
+
+function fileSelected() {
+    var elem = document.getElementById('filepath');
+
+    console.log("File selected: " + elem.value);
+}
+
+
+function audioError(event) {
+    /*console.log('error:' + event.target.error.code);
+    setStatus("audio path: not valid");*/
+}
+
+
+function checkAudio() {
+    var audio = document.getElementById('audio');
+
+    if (audio.networkState == 1)
+        setStatus("audio path: OK");
+    else
+        setStatus("audio path: invalid");
+}
+
+
+function checkPath() {
+    var txt   = document.getElementById('filepath');
+    var audio = document.getElementById('audio');
+    var path  = "http://127.0.0.1/" + txt.value;
+
+    console.log("trying " + path);
+
+    audio.src = path;
+
+    if (audio.checkTimer)
+        window.clearTimeout(audio.checkTimer);
+
+    audio.checkTimer = window.setTimeout(function() { checkAudio(); }, 250);
+    audio.load();
+}
+
+
+function playAudio() {
+    var audio = document.getElementById('audio');
+
+    if (audio.networkState == 1)
+        audio.play();
+}
+
+
+function stopAudio() {
+    var audio = document.getElementById('audio');
+
+    audio.pause();
+}
+
+
+</script>
+
+</head>
+
+<body onload="/*runtest();*/ initialize();">
+
+
+<table>
+    <tr>
+        <th align=left>Audio File:</th>
+        <td align=left><input id=filepath type="text" accept="audio/*"
+                              onchange="fileSelected();"
+                              onkeyup="checkPath();"></td>
+    </tr>
+    <tr>
+        <th align=left>Actions:</th>
+        <td align=left>
+          <input type=button id=connect value="Connect"
+               onclick="connect();">
+          <input type=button id=disconnect value="Disconnect"
+               onclick="disconnect();" disabled>
+          <input type=button id=acquire value="Acquire"
+               onclick="acquire();" disabled>
+          <input type=button id=release value="Release"
+               onclick="release();" disabled>
+        </td>
+    </tr>
+    <tr>
+        <th align=left>Status:</th>
+       <td id=statush align=left><div id=status>disconnected</div></td>
+    </tr>
+    <tr>
+        <th align=left>grant mask:</th>
+        <td id=granth align=left><div id=grant></div></td>
+    </tr>
+    <tr>
+        <th align=left>advice mask:</th>
+        <td id=adviceh align=left><div id=advice></div></td>
+    </tr>
+    <tr>
+        <th align=left>granted:</th>
+        <td id=grantedh align=left><div id=granted></div></td>
+    </tr>
+    <tr>
+        <th align=left>allocable:</th>
+        <td id=allocableh align=left><div id=allocable></div></td>
+    </tr>
+
+    <tr>
+        <td colspan=2 width=500 align=center style="background-color: #e0e0e0;"><div id=wslm_drawing> </div></td>
+    </tr>
+    <tr>
+      <td>
+        <audio id=audio preload=metadata onerror="audioError(event);">
+        </audio>
+      <td>
+    </tr>
+</table>
+
+
+</body>
+</html>
diff --git a/src/plugins/resource-wrt/resource-wrt.h b/src/plugins/resource-wrt/resource-wrt.h
new file mode 100644 (file)
index 0000000..f0df672
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_WRT_H__
+#define __MURPHY_RESOURCE_WRT_H__
+
+#define RESWRT_QUERY_CLASSES   "query-classes"
+#define RESWRT_QUERY_ZONES     "query-zones"
+#define RESWRT_QUERY_RESOURCES "query-resources"
+
+#define RESWRT_CREATE_SET      "create"
+#define RESWRT_DESTROY_SET     "destroy"
+#define RESWRT_ACQUIRE_SET     "acquire"
+#define RESWRT_RELEASE_SET     "release"
+
+#define RESWRT_EVENT           "event"
+#define RESWRT_STATE_GRANTED   "acquire"
+#define RESWRT_STATE_RELEASE   "release"
+
+#endif /* __MURPHY_RESOURCE_WRT_H__ */
diff --git a/src/plugins/tests/Makefile.am b/src/plugins/tests/Makefile.am
new file mode 100644 (file)
index 0000000..b518650
--- /dev/null
@@ -0,0 +1,4 @@
+AM_CFLAGS = $(WARNING_CFLAGS) -I$(top_builddir)
+
+noinst_PROGRAMS  = 
+
diff --git a/src/resolver/console.c b/src/resolver/console.c
new file mode 100644 (file)
index 0000000..85de4da
--- /dev/null
@@ -0,0 +1,51 @@
+#include <murphy/core/console.h>
+#include <murphy/resolver/resolver.h>
+#include <murphy/resolver/target.h>
+
+static void dump(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    mrp_context_t *ctx = c->ctx;
+
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    if (ctx->r != NULL) {
+        mrp_resolver_dump_facts(ctx->r, c->stdout);
+        mrp_resolver_dump_targets(ctx->r, c->stdout);
+    }
+}
+
+static void dot(mrp_console_t *c, void *user_data, int argc, char **argv)
+{
+    mrp_context_t *ctx = c->ctx;
+
+    MRP_UNUSED(user_data);
+    MRP_UNUSED(argc);
+    MRP_UNUSED(argv);
+
+    if (ctx->r != NULL) {
+        mrp_resolver_dump_dot_graph(ctx->r, c->stdout);
+    }
+}
+
+#define RESOLVER_DESCRIPTION                                              \
+    "Resolver commands provide runtime diagnostics and debugging for\n"   \
+    "the Murphy resolver.\n"
+
+#define DUMP_SYNTAX  "dump"
+#define DUMP_SUMMARY "dump the resolver facts and targets"
+#define DUMP_DESCRIPTION                        \
+    "Dump the resolver facts and targets.\n"
+
+#define DOT_SYNTAX  "dot"
+#define DOT_SUMMARY "dump the resolver facts and targets in DOT format"
+#define DOT_DESCRIPTION                        \
+    "Dump the resolver facts and targets in DOT format.\n"
+
+MRP_CORE_CONSOLE_GROUP(resolver_group, "resolver", RESOLVER_DESCRIPTION, NULL, {
+        MRP_TOKENIZED_CMD("dump", dump, FALSE,
+                          DUMP_SYNTAX, DUMP_SUMMARY, DUMP_DESCRIPTION),
+        MRP_TOKENIZED_CMD("dot", dot, FALSE,
+                          DOT_SYNTAX, DOT_SUMMARY, DOT_DESCRIPTION),
+});
diff --git a/src/resolver/events.c b/src/resolver/events.c
new file mode 100644 (file)
index 0000000..c0171a0
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/mainloop.h>
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "events.h"
+
+MRP_REGISTER_EVENTS(events,
+             MRP_EVENT(MRP_RESOLVER_EVENT_STARTED, RESOLVER_UPDATE_STARTED),
+             MRP_EVENT(MRP_RESOLVER_EVENT_FAILED , RESOLVER_UPDATE_FAILED ),
+             MRP_EVENT(MRP_RESOLVER_EVENT_DONE   , RESOLVER_UPDATE_DONE   ));
+
+
+int emit_resolver_event(mrp_resolver_t *r, int event, const char *target,
+                        int level)
+{
+    uint16_t ttarget = MRP_RESOLVER_TAG_TARGET;
+    uint16_t tlevel  = MRP_RESOLVER_TAG_LEVEL;
+    int      flags   = MRP_EVENT_SYNCHRONOUS;
+
+    return mrp_event_emit_msg(r->bus, events[event].id, flags,
+                              MRP_MSG_TAG_STRING(ttarget, target),
+                              MRP_MSG_TAG_UINT32(tlevel , level));
+}
diff --git a/src/resolver/events.h b/src/resolver/events.h
new file mode 100644 (file)
index 0000000..ee0ee1b
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_EVENTS_H__
+#define __MURPHY_RESOLVER_EVENTS_H__
+
+/*
+ * resolver-related events
+ */
+
+enum {
+    RESOLVER_UPDATE_STARTED = 0,
+    RESOLVER_UPDATE_FAILED,
+    RESOLVER_UPDATE_DONE
+};
+
+
+int emit_resolver_event(mrp_resolver_t *r, int event, const char *target,
+                        int level);
+
+
+#endif /* __MURPHY_RESOLVER_EVENTS_H__ */
diff --git a/src/resolver/fact.c b/src/resolver/fact.c
new file mode 100644 (file)
index 0000000..00a0f6c
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy-db/mqi.h>
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "target.h"
+#include "fact.h"
+
+static int subscribe_db_events(mrp_resolver_t *r);
+static void unsubscribe_db_events(mrp_resolver_t *r);
+
+int create_fact(mrp_resolver_t *r, char *fact)
+{
+    int     i;
+    fact_t *f;
+
+    subscribe_db_events(r);
+
+    for (i = 0; i < r->nfact; i++) {
+        if (!strcmp(r->facts[i].name, fact))
+            return TRUE;
+    }
+
+    if (!mrp_reallocz(r->facts, r->nfact * sizeof(*r->facts),
+                      (r->nfact + 1) * sizeof(*r->facts)))
+        return FALSE;
+
+    f = r->facts + r->nfact++;
+    f->name  = mrp_strdup(fact);
+    f->table = mqi_get_table_handle(f->name + 1);
+
+    if (f->name != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+void destroy_facts(mrp_resolver_t *r)
+{
+    fact_t *f;
+    int     i;
+
+    unsubscribe_db_events(r);
+
+    for (i = 0, f = r->facts; i < r->nfact; i++, f++)
+        mrp_free(f->name);
+
+    mrp_free(r->facts);
+}
+
+
+uint32_t fact_stamp(mrp_resolver_t *r, int id)
+{
+    fact_t   *fact = r->facts + id;
+    uint32_t  stamp;
+
+    if (fact->table != MQI_HANDLE_INVALID)
+        stamp = mqi_get_table_stamp(fact->table);
+    else
+        stamp = 0; /* MQI_NO_STAMP */
+
+    return stamp;
+}
+
+
+const char *fact_name(mrp_resolver_t *r, int id)
+{
+   fact_t *fact = r->facts + id;
+
+   return fact->name;
+}
+
+
+fact_t *lookup_fact(mrp_resolver_t *r, const char *name)
+{
+    fact_t *f;
+    int     i;
+
+    for (i = 0, f = r->facts; i < r->nfact; i++, f++)
+        if (!strcmp(f->name, name))
+            return f;
+
+    return NULL;
+}
+
+
+static void update_fact_table(mrp_resolver_t *r, const char *name,
+                              mqi_handle_t tbl)
+{
+    fact_t *f;
+    int     i;
+
+    for (i = 0, f = r->facts; i < r->nfact; i++, f++) {
+        if (!strcmp(f->name + 1, name)) {
+            f->table = tbl;
+            return;
+        }
+    }
+}
+
+
+static void check_fact_tables(mrp_resolver_t *r)
+{
+    fact_t *f;
+    int     i;
+
+    for (i = 0, f = r->facts; i < r->nfact; i++, f++) {
+        if (f->table != MQI_HANDLE_INVALID)
+            mrp_debug("Fact table '%s' stamp: %u.",
+                      f->name, mqi_get_table_stamp(f->table));
+    }
+}
+
+
+static inline int open_db(void)
+{
+    static int opened = FALSE;
+    if (!opened)
+        opened = (mqi_open() == 0);
+
+    return opened;
+}
+
+
+static void table_event(mqi_event_t *e, void *user_data)
+{
+    mrp_resolver_t *r = (mrp_resolver_t *)user_data;
+
+    switch (e->event) {
+    case mqi_table_created:
+        mrp_debug("DB table created (%s, %u).",
+                  e->table.table.name, e->table.table.handle);
+        update_fact_table(r, e->table.table.name, e->table.table.handle);
+        break;
+    case mqi_table_dropped:
+        mrp_debug("DB table dropped (%s, %u).",
+                  e->table.table.name, e->table.table.handle);
+        update_fact_table(r, e->table.table.name, MQI_HANDLE_INVALID);
+        break;
+    default:
+        break;
+    }
+}
+
+
+static void transaction_event(mqi_event_t *e, void *user_data)
+{
+    mrp_resolver_t *r = (mrp_resolver_t *)user_data;
+
+    MRP_UNUSED(r);
+
+    switch (e->event) {
+    case mqi_transaction_end:
+        mrp_debug("DB transaction ended.");
+        check_fact_tables(r);
+        if (mqi_get_transaction_depth() == 1) {
+            mrp_debug("was not nested, scheduling update");
+            schedule_target_autoupdate(r);
+        }
+        else
+            mrp_debug("was nested");
+        break;
+    case mqi_transaction_start:
+        mrp_debug("DB transaction started.");
+        break;
+    default:
+        break;
+    }
+}
+
+
+static int subscribe_db_events(mrp_resolver_t *r)
+{
+    if (open_db()) {
+        if (mqi_create_table_trigger(table_event, r) == 0) {
+            if (mqi_create_transaction_trigger(transaction_event, r) == 0)
+                return TRUE;
+            else
+                mqi_drop_table_trigger(table_event, r);
+        }
+    }
+
+    return FALSE;
+}
+
+
+static void unsubscribe_db_events(mrp_resolver_t *r)
+{
+    mqi_drop_table_trigger(table_event, r);
+    mqi_drop_transaction_trigger(transaction_event, r);
+}
+
+
+mqi_handle_t start_transaction(mrp_resolver_t *r)
+{
+    MRP_UNUSED(r);
+
+    return mqi_begin_transaction();
+}
+
+
+int commit_transaction(mrp_resolver_t *r, mqi_handle_t tx)
+{
+    MRP_UNUSED(r);
+
+    return mqi_commit_transaction(tx) != -1;
+}
+
+
+int rollback_transaction(mrp_resolver_t *r, mqi_handle_t tx)
+{
+    MRP_UNUSED(r);
+
+    return mqi_rollback_transaction(tx) != -1;
+}
diff --git a/src/resolver/fact.h b/src/resolver/fact.h
new file mode 100644 (file)
index 0000000..c4e04a7
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_FACT_H__
+#define __MURPHY_RESOLVER_FACT_H__
+
+#include <murphy-db/mqi.h>
+#include "resolver.h"
+
+int create_fact(mrp_resolver_t *r, char *name);
+void destroy_facts(mrp_resolver_t *r);
+
+int fact_changed(mrp_resolver_t *r, int id);
+uint32_t fact_stamp(mrp_resolver_t *r, int id);
+const char *fact_name(mrp_resolver_t *r, int id);
+
+fact_t *lookup_fact(mrp_resolver_t *r, const char *name);
+
+
+mqi_handle_t start_transaction(mrp_resolver_t *r);
+int commit_transaction(mrp_resolver_t *r, mqi_handle_t tx);
+int rollback_transaction(mrp_resolver_t *r, mqi_handle_t tx);
+
+#endif /* __MURPHY_RESOLVER_FACT_H__ */
diff --git a/src/resolver/murphy-resolver.pc.in b/src/resolver/murphy-resolver.pc.in
new file mode 100644 (file)
index 0000000..48d4524
--- /dev/null
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: murphy-resolver
+Description: Murphy policy framework, resolver library.
+Requires: murphy-core murphy-common
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lmurphy-resolver -lmurphy-core -lmurphy-common
+Cflags: -I${includedir}
diff --git a/src/resolver/parser-api.h b/src/resolver/parser-api.h
new file mode 100644 (file)
index 0000000..6392359
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_PARSER_TYPES_H__
+#define __MURPHY_RESOLVER_PARSER_TYPES_H__
+
+#include <stdio.h>
+
+#include <murphy/common/list.h>
+
+#define YY_RES_RINGBUF_SIZE (8 * 1024)            /* token buffer size */
+
+/*
+ * a parsed target definition
+ */
+
+typedef struct {
+    char *type;                                   /* script type */
+    char *source;                                 /* script source */
+} yy_res_script_t;
+
+typedef struct {
+    mrp_list_hook_t  hook;                        /* to list of targets */
+    char            *name;                        /* target name */
+    char           **depends;                     /* target dependencies */
+    int              ndepend;                     /* number of dependencies */
+    char            *script_type;                 /* update script type */
+    char            *script_source;               /* update script source */
+} yy_res_target_t;
+
+
+typedef struct yy_res_input_s yy_res_input_t;
+
+struct yy_res_input_s {
+    yy_res_input_t *prev;                         /* previous input */
+    void           *yybuf;                        /* scanner buffer */
+    char           *name;                         /* name of this input */
+    int             line;                         /* line number in input */
+    FILE           *fp;                           /* input stream */
+};
+
+
+typedef struct {
+    mrp_list_hook_t targets;                      /* list of targets */
+    char           *auto_update;                  /* auto-update target */
+    char            ringbuf[YY_RES_RINGBUF_SIZE]; /* token ringbuffer */
+    int             offs;                         /* buffer insert offset */
+    yy_res_input_t *in;                           /* current input */
+    yy_res_input_t *done;                         /* processed inputs */
+} yy_res_parser_t;
+
+
+int parser_setup(yy_res_parser_t *parser, const char *path);
+void parser_cleanup(yy_res_parser_t *parser);
+int parser_parse_file(yy_res_parser_t *parser, const char *path);
+
+#endif /* __MURPHY_RESOLVER_PARSER_TYPES_H__ */
diff --git a/src/resolver/parser.y b/src/resolver/parser.y
new file mode 100644 (file)
index 0000000..b2f7496
--- /dev/null
@@ -0,0 +1,277 @@
+%{ /* -*- c -*- */
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+
+#include "murphy/resolver/resolver.h"
+#include "murphy/resolver/parser-api.h"
+#include "murphy/resolver/token.h"
+#include "murphy/resolver/scanner.h"
+
+void yy_res_error(yy_res_parser_t *parser, const char *msg);
+
+static tkn_strarr_t *strarr_append(tkn_strarr_t *arr, char *str);
+
+%}
+
+%union {
+    tkn_any_t        any;
+    tkn_string_t     string;
+    tkn_s16_t        s16;
+    tkn_u16_t        u16;
+    tkn_s32_t        s32;
+    tkn_u32_t        u32;
+    yy_res_target_t *target;
+    yy_res_script_t  script;
+    tkn_strarr_t    *strarr;
+}
+
+%defines
+%parse-param { yy_res_parser_t *parser }
+%lex-param   { yy_res_parser_t *parser }
+
+%token          KEY_TARGET
+%token          KEY_DEPENDS_ON
+%token <string> KEY_UPDATE_SCRIPT
+%token          KEY_END_SCRIPT
+%token          KEY_AUTOUPDATE
+%token <string> TKN_IDENT
+%token <string> TKN_FACT
+%token <string> TKN_SCRIPT_LINE
+%token <string> TKN_LEX_ERROR
+
+%type  <target> targets
+%type  <target> target
+%type  <strarr> optional_dependencies
+%type  <strarr> dependencies
+%type  <script> optional_script
+%type  <string> script_source
+
+%%
+
+input: optional_autoupdate targets
+    ;
+
+optional_autoupdate:
+/* no autoupdate target */ { parser->auto_update = NULL;                 }
+| KEY_AUTOUPDATE TKN_IDENT { parser->auto_update = mrp_strdup($2.value); }
+;
+
+targets:
+  target         { mrp_list_append(&parser->targets, &$1->hook); }
+| targets target { mrp_list_append(&parser->targets, &$2->hook); }
+| targets error  { YYABORT; }
+;
+
+target: KEY_TARGET TKN_IDENT optional_dependencies optional_script {
+    yy_res_target_t *t;
+    int i;
+
+    t = mrp_allocz(sizeof(*t));
+
+    if (t != NULL) {
+        mrp_list_init(&t->hook);
+
+        t->name = mrp_strdup($2.value);
+        if ($3 != NULL) {
+            t->depends = $3->strs;
+            t->ndepend = $3->nstr;
+        }
+
+        if ($4.source != NULL) {
+            t->script_type   = mrp_strdup($4.type);
+            t->script_source = mrp_strdup($4.source);
+        }
+
+        if (t->name != NULL) {
+            mrp_list_append(&parser->targets, &t->hook);
+            $$ = t;
+        }
+        else
+            YYABORT;
+    }
+
+    mrp_log_info("target '%s':", $2.value);
+
+    if ($3 != NULL) {
+        for (i = 0; i < $3->nstr; i++)
+            mrp_log_info("    depends on '%s'", $3->strs[i]);
+    }
+    else
+        mrp_log_info("    no dependencies");
+
+    if ($4.source != NULL)
+        mrp_log_info("    update script (%s): %s", $4.type, $4.source);
+    else
+        mrp_log_info("    no update script");
+  }
+;
+
+optional_dependencies:
+  /* no dependencies */       { $$ = NULL; }
+| KEY_DEPENDS_ON dependencies { $$ = $2;   }
+;
+
+dependencies:
+  TKN_IDENT                   { $$ = strarr_append(NULL, $1.value); }
+| TKN_FACT                    { $$ = strarr_append(NULL, $1.value); }
+| dependencies TKN_IDENT {
+    if (!strarr_append($1, $2.value))
+        YYABORT;
+    else
+        $$ = $1;
+  }
+| dependencies TKN_FACT {
+    if (!strarr_append($1, $2.value))
+        YYABORT;
+    else
+        $$ = $1;
+  }
+;
+
+optional_script:
+  /* no script */ {
+    $$.type   = NULL;
+    $$.source = NULL;
+  }
+| KEY_UPDATE_SCRIPT script_source KEY_END_SCRIPT {
+    $$.type   = $1.value;
+    $$.source = $2.value;
+  }
+;
+
+script_source:
+  TKN_SCRIPT_LINE {
+      int n;
+
+      n = strlen($1.value) + 2;
+      $$.value = mrp_allocz(n);
+
+      if ($$.value != NULL) {
+          strcpy($$.value, $1.value);
+          $$.value[n - 2] = '\n';
+          $$.value[n - 1] = '\0';
+      }
+      else
+          YYABORT;
+  }
+| script_source TKN_SCRIPT_LINE {
+    int o, n;
+
+    o = strlen($1.value);
+    n = o + strlen($2.value) + 2;
+    $$.value = mrp_reallocz($1.value, o, n);
+
+    if ($$.value == NULL)
+        YYABORT;
+
+    strcat($$.value, $2.value);
+    $$.value[n - 2] = '\n';
+    $$.value[n - 1] = '\0';
+  }
+;
+
+%%
+
+void yy_res_error(yy_res_parser_t *parser, const char *msg)
+{
+    MRP_UNUSED(parser);
+
+    mrp_log_error("parse error at %s:%d near token '%s': %s",
+                  yy_res_lval.any.source, yy_res_lval.any.line,
+                  yy_res_lval.any.token, msg);
+}
+
+
+static tkn_strarr_t *strarr_append(tkn_strarr_t *arr, char *str)
+{
+    int n;
+
+    if (arr == NULL) {
+        arr = mrp_allocz(sizeof(*arr));
+        if (arr == NULL)
+            return NULL;
+    }
+
+    if (!mrp_reallocz(arr->strs, arr->nstr, arr->nstr + 1))
+        return NULL;
+
+    n = arr->nstr++;
+    arr->strs[n] = mrp_strdup(str);
+
+    if (arr->strs[n] != NULL)
+        return arr;
+    else {
+        if (n == 0) {
+            mrp_free(arr->strs);
+            mrp_free(arr);
+        }
+
+        return NULL;
+    }
+}
+
+
+int parser_setup(yy_res_parser_t *parser, const char *path)
+{
+    mrp_clear(parser);
+    mrp_list_init(&parser->targets);
+
+    if (path != NULL)
+        return scanner_push_file(parser, path);
+    else
+        return TRUE;
+}
+
+
+void parser_cleanup(yy_res_parser_t *parser)
+{
+    mrp_list_hook_t  *tp, *tn;
+    yy_res_target_t  *t;
+    yy_res_input_t   *ip, *in;
+    char            **dep;
+    int               i;
+
+    mrp_list_foreach(&parser->targets, tp, tn) {
+        t = mrp_list_entry(tp, typeof(*t), hook);
+
+        mrp_free(t->name);
+        mrp_free(t->script_type);
+        mrp_free(t->script_source);
+
+        if (t->depends != NULL) {
+            for (i = 0, dep = t->depends; i < t->ndepend; i++, dep++)
+                mrp_free(*dep);
+
+            mrp_free(t->depends);
+        }
+
+        mrp_free(t);
+    }
+
+    ip = parser->in;
+    while (ip != NULL) {
+        in = ip->prev;
+        scanner_free_input(ip);
+        ip = in;
+    }
+
+    ip = parser->done;
+    while (ip != NULL) {
+        in = ip->prev;
+        scanner_free_input(ip);
+        ip = in;
+    }
+
+    mrp_free(parser->auto_update);
+}
+
+
+int parser_parse_file(yy_res_parser_t *parser, const char *path)
+{
+    if (parser_setup(parser, path))
+        return yy_res_parse(parser) == 0;
+    else
+        return FALSE;
+}
diff --git a/src/resolver/resolver-types.h b/src/resolver/resolver-types.h
new file mode 100644 (file)
index 0000000..9a1ee0f
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_TYPES_H__
+#define __MURPHY_RESOLVER_TYPES_H__
+
+#include <stdint.h>
+
+#include <murphy/common/mainloop.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/core/context.h>
+#include <murphy/core/scripting.h>
+
+#include <murphy-db/mqi.h>
+
+typedef struct target_s target_t;        /* opaque type for resolver targets */
+typedef struct fact_s   fact_t;          /* opaque type for tracked facts */
+
+/*
+ * a resolver target
+ */
+struct target_s {
+    char            *name;               /* target name */
+    uint32_t         stamp;              /* touch-stamp */
+    char           **depends;            /* dependencies stated in the input */
+    int              ndepend;            /* number of dependencies */
+    int             *update_facts;       /* facts to check when updating */
+    int             *update_targets;     /* targets to check when updating */
+    int             *directs;            /* direct dependencies */
+    int              ndirect;            /* number of direct dependencies */
+    uint32_t        *fact_stamps;        /* stamps of facts at last update */
+    mrp_scriptlet_t *script;             /* update script if any, or NULL */
+    int              prepared : 1;       /* ready for resolution */
+    int              precompiled : 1;
+};
+
+
+/*
+ * a tracked fact
+ */
+struct fact_s {
+    char         *name;                  /* fact name */
+    mqi_handle_t  table;                 /* associated DB table */
+    uint32_t      stamp;                 /* touch-stamp */
+};
+
+
+struct mrp_resolver_s {
+    mrp_context_t     *ctx;              /* murphy context we're running in */
+    mrp_event_bus_t   *bus;              /* bus for resolver events */
+    target_t          *targets;          /* targets defined in the ruleset */
+    int                ntarget;          /* number of targets */
+    fact_t            *facts;            /* facts tracked as dependencies */
+    int                nfact;            /* number of tracked facts */
+    target_t          *auto_update;      /* target to resolve on fact changes */
+    mrp_deferred_t    *auto_scheduled;   /* scheduled auto_update */
+    uint32_t           stamp;            /* update stamp */
+    mrp_context_tbl_t *ctbl;             /* context variable table */
+    int                level;            /* target update nesting level */
+};
+
+
+#endif /* __MURPHY_RESOLVER_TYPES_H__ */
diff --git a/src/resolver/resolver.c b/src/resolver/resolver.c
new file mode 100644 (file)
index 0000000..8b116d5
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+
+#include "scanner.h"
+#include "resolver-types.h"
+#include "target.h"
+#include "target-sorter.h"
+#include "fact.h"
+#include "resolver.h"
+
+
+mrp_resolver_t *mrp_resolver_create(mrp_context_t *ctx)
+{
+    mrp_resolver_t *r;
+
+    r = mrp_allocz(sizeof(mrp_resolver_t));
+
+    if (r != NULL) {
+        r->ctx  = ctx;
+        r->ctbl = mrp_create_context_table();
+        r->bus  = mrp_event_bus_get(ctx->ml, MRP_RESOLVER_BUS);
+
+        if (r->ctbl != NULL && r->bus != NULL)
+            return r;
+
+        mrp_free(r);
+    }
+
+    return NULL;
+}
+
+
+mrp_resolver_t *mrp_resolver_parse(mrp_resolver_t *r, mrp_context_t *ctx,
+                                   const char *path)
+{
+    yy_res_parser_t parser;
+
+    mrp_clear(&parser);
+
+    if (r == NULL) {
+        r = mrp_resolver_create(ctx);
+
+        if (r == NULL)
+            return NULL;
+    }
+
+    if (parser_parse_file(&parser, path)) {
+        if (create_targets(r, &parser) == 0 &&
+            sort_targets(r)            == 0 &&
+            compile_target_scripts(r)  == 0) {
+            parser_cleanup(&parser);
+            return r;
+        }
+    }
+    else
+        mrp_log_error("Failed to parse resolver input.");
+
+    mrp_resolver_destroy(r);
+    parser_cleanup(&parser);
+
+    return NULL;
+}
+
+
+int mrp_resolver_prepare(mrp_resolver_t *r)
+{
+    return (prepare_target_scripts(r) == 0);
+}
+
+
+void mrp_resolver_destroy(mrp_resolver_t *r)
+{
+    if (r != NULL) {
+        mrp_destroy_context_table(r->ctbl);
+        destroy_targets(r);
+        destroy_facts(r);
+
+        mrp_free(r);
+    }
+}
+
+
+int mrp_resolver_add_target(mrp_resolver_t *r, const char *target,
+                            const char **depend, int ndepend,
+                            const char *script_type,
+                            const char *script_source)
+{
+    return (create_target(r, target, depend, ndepend,
+                          script_type, script_source) != NULL);
+}
+
+
+int mrp_resolver_add_alias(mrp_resolver_t *r, const char *target,
+                           const char *alias)
+{
+    const char *depend[1] = { target };
+
+    return (create_target(r, alias, depend, 1, NULL, NULL) != NULL);
+}
+
+
+int mrp_resolver_add_prepared_target(mrp_resolver_t *r, const char *target,
+                                     const char **depend, int ndepend,
+                                     mrp_interpreter_t *interpreter,
+                                     void  *compiled_data, void *target_data)
+{
+    mrp_scriptlet_t *script;
+    target_t        *t;
+
+    t = create_target(r, target, depend, ndepend, NULL, NULL);
+
+    if (t != NULL) {
+        if (interpreter != NULL) {
+            script = mrp_allocz(sizeof(*script));
+
+            if (script != NULL) {
+                script->interpreter = interpreter;
+                script->data        = target_data;
+                script->compiled    = compiled_data;
+
+                t->script = script;
+            }
+            else
+                return FALSE;
+        }
+
+        t->precompiled = TRUE;
+        t->prepared    = TRUE;
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+int mrp_resolver_enable_autoupdate(mrp_resolver_t *r, const char *name)
+{
+    return generate_autoupdate_target(r, name);
+}
+
+
+int mrp_resolver_update_targetl(mrp_resolver_t *r, const char *target, ...)
+{
+    const char         *name;
+    mrp_script_value_t  value;
+    va_list             ap;
+    int                 id, status;
+
+    if (mrp_push_context_frame(r->ctbl) == 0) {
+        va_start(ap, target);
+        while ((name = va_arg(ap, char *)) != NULL) {
+            id = mrp_get_context_id(r->ctbl, name);
+
+            if (id > 0) {
+                value.type = va_arg(ap, int);
+
+#define         HANDLE_TYPE(_type, _member, _va_type)                   \
+                case MRP_SCRIPT_TYPE_##_type:                           \
+                    value._member =                                     \
+                        (typeof(value._member))va_arg(ap, _va_type);    \
+                    break
+
+                switch (value.type) {
+                    HANDLE_TYPE(STRING, str, char *  );
+                    HANDLE_TYPE(BOOL  , bln, int     );
+                    HANDLE_TYPE(UINT8 ,  u8, uint32_t);
+                    HANDLE_TYPE(SINT8 ,  s8, int32_t );
+                    HANDLE_TYPE(UINT16, u16, uint32_t);
+                    HANDLE_TYPE(SINT16, s16, int32_t );
+                    HANDLE_TYPE(UINT32, u32, uint32_t);
+                    HANDLE_TYPE(SINT32, s32, int32_t );
+                    HANDLE_TYPE(UINT64, u64, uint64_t);
+                    HANDLE_TYPE(SINT64, u64, uint64_t);
+                    HANDLE_TYPE(DOUBLE, dbl, double  );
+                default:
+                    errno  = EINVAL;
+                    status = -1;
+                    goto pop_frame;
+                }
+#undef          HANDLE_TYPE
+
+                if (mrp_set_context_value(r->ctbl, id, &value) < 0) {
+                    status = -1;
+                    goto pop_frame;
+                }
+            }
+            else {
+                errno  = ESRCH;
+                status = -1;
+                goto pop_frame;
+            }
+        }
+
+        status = update_target_by_name(r, target);
+
+    pop_frame:
+        mrp_pop_context_frame(r->ctbl);
+        va_end(ap);
+    }
+    else
+        status = -1;
+
+    return status;
+}
+
+
+int mrp_resolver_update_targetv(mrp_resolver_t *r, const char *target,
+                                const char **variables,
+                                mrp_script_value_t *values,
+                                int nvariable)
+{
+    const char         *name;
+    mrp_script_value_t *value;
+    int                 id, i, status;
+
+    if (mrp_push_context_frame(r->ctbl) == 0) {
+        for (i = 0; i < nvariable; i++) {
+            name  = variables[i];
+            value = values + i;
+            id    = mrp_get_context_id(r->ctbl, name);
+
+            if (id > 0) {
+                if (mrp_set_context_value(r->ctbl, id, value) < 0) {
+                    status = -1;
+                    goto pop_frame;
+                }
+            }
+            else {
+                errno  = ESRCH;
+                status = -1;
+                goto pop_frame;
+            }
+        }
+
+        status = update_target_by_name(r, target);
+
+    pop_frame:
+        mrp_pop_context_frame(r->ctbl);
+    }
+    else
+        status = -1;
+
+    return status;
+}
+
+
+void mrp_resolver_dump_targets(mrp_resolver_t *r, FILE *fp)
+{
+    fprintf(fp, "%d target%s\n", r->ntarget, r->ntarget != 1 ? "s" : "");
+    dump_targets(r, fp);
+}
+
+
+void mrp_resolver_dump_facts(mrp_resolver_t *r, FILE *fp)
+{
+    int     i;
+    fact_t *f;
+
+    fprintf(fp, "%d fact%s\n", r->nfact, r->nfact != 1 ? "s" : "");
+    for (i = 0; i < r->nfact; i++) {
+        f = r->facts + i;
+        fprintf(fp, "  #%d: %s (@%u)\n", i, f->name, fact_stamp(r, i));
+    }
+}
+
+
+int mrp_resolver_register_interpreter(mrp_interpreter_t *i)
+{
+    return mrp_register_interpreter(i);
+}
+
+
+int mrp_resolver_unregister_interpreter(const char *name)
+{
+    return mrp_unregister_interpreter(name);
+}
+
+
+int mrp_resolver_declare_variable(mrp_resolver_t *r, const char *name,
+                                  mrp_script_type_t type)
+{
+    return mrp_declare_context_variable(r->ctbl, name, type);
+}
+
+
+int mrp_resolver_get_value(mrp_resolver_t *r, int id, mrp_script_value_t *v)
+{
+    return mrp_get_context_value(r->ctbl, id, v);
+}
+
+
+int mrp_resolver_get_value_by_name(mrp_resolver_t *r, const char *name,
+                                   mrp_script_value_t *v)
+{
+    return mrp_get_context_value(r->ctbl, mrp_get_context_id(r->ctbl, name), v);
+}
diff --git a/src/resolver/resolver.h b/src/resolver/resolver.h
new file mode 100644 (file)
index 0000000..09682ee
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_H__
+#define __MURPHY_RESOLVER_H__
+
+#include <stdio.h>
+#include <stdbool.h>
+
+typedef struct mrp_resolver_s mrp_resolver_t;
+
+#include <murphy/common/macros.h>
+#include <murphy/core/context.h>
+#include <murphy/core/scripting.h>
+
+MRP_CDECL_BEGIN
+
+
+/*
+ * tags and names of resolver-related events we emit
+ */
+
+#define MRP_RESOLVER_BUS           "resolver-bus"
+#define MRP_RESOLVER_EVENT_STARTED "resolver-update-start"
+#define MRP_RESOLVER_EVENT_FAILED  "resolver-update-failed"
+#define MRP_RESOLVER_EVENT_DONE    "resolver-update-done"
+
+#define MRP_RESOLVER_TAG_TARGET ((uint16_t)1)
+#define MRP_RESOLVER_TAG_LEVEL  ((uint16_t)2)
+
+/** Just create a resolver context without parsing any input. */
+mrp_resolver_t *mrp_resolver_create(mrp_context_t *ctx);
+
+/** Parse the given resolver input file into a resolver context. */
+mrp_resolver_t *mrp_resolver_parse(mrp_resolver_t *r, mrp_context_t *ctx,
+                                   const char *path);
+
+/** Add a new target with the given parameters to the resolver context. */
+int mrp_resolver_add_target(mrp_resolver_t *r, const char *target,
+                            const char **depend, int ndepend,
+                            const char *script_type,
+                            const char *script_source);
+
+/** Add a precompiled target to the resolver context. */
+int mrp_resolver_add_prepared_target(mrp_resolver_t *r, const char *target,
+                                     const char **depend, int ndepend,
+                                     mrp_interpreter_t *interpreter,
+                                     void *compiled_data, void *target_data);
+
+/** Add an alias for the given target. */
+int mrp_resolver_add_alias(mrp_resolver_t *r, const char *target,
+                           const char *alias);
+
+/** Enable autoupdate, generate autoupdate target if needed. */
+int mrp_resolver_enable_autoupdate(mrp_resolver_t *r, const char *name);
+
+/** Destroy the given resolver context, freeing all associated resources. */
+void mrp_resolver_destroy(mrp_resolver_t *r);
+
+/** Prepare the targets for resolution (link scriptlets, etc.). */
+int mrp_resolver_prepare(mrp_resolver_t *r);
+
+/** Update the given target. The NULL-terminated variable argument list
+    after the target name sepcifies the resolver context variables to
+    set during the update. Use a single NULL to omit variables. */
+int mrp_resolver_update_targetl(mrp_resolver_t *r,
+                                const char *target, ...) MRP_NULLTERM;
+
+#define mrp_resolver_update_target mrp_resolver_update_targetl
+
+/** Update the given target. The variable name and type/value arrays
+    specify the resolver context variables to set during the update. */
+int mrp_resolver_update_targetv(mrp_resolver_t *r, const char *target,
+                                const char **variables,
+                                mrp_script_value_t *values,
+                                int nvariable);
+
+/** Declare a context variable with a given type. */
+int mrp_resolver_declare_variable(mrp_resolver_t *r, const char *name,
+                                  mrp_script_type_t type);
+
+
+/** Get the value of a context variable by id. */
+int mrp_resolver_get_value(mrp_resolver_t *r, int id, mrp_script_value_t *v);
+#define mrp_resolver_get_value_by_id mrp_resolver_get_value
+
+/** Get the value of a context variable by name. */
+int mrp_resolver_get_value_by_name(mrp_resolver_t *r, const char *name,
+                                   mrp_script_value_t *v);
+
+/** Print the given value to the given buffer. */
+char *mrp_print_value(char *buf, size_t size, mrp_script_value_t *value);
+
+/** Produce a debug dump of all targets. */
+void mrp_resolver_dump_targets(mrp_resolver_t *r, FILE *fp);
+
+/** Produce a debug dump of all tracked facts. */
+void mrp_resolver_dump_facts(mrp_resolver_t *r, FILE *fp);
+
+/** Register a script interpreter. */
+int mrp_resolver_register_interpreter(mrp_interpreter_t *i);
+
+/** Unregister a script interpreter. */
+int mrp_resolver_unregister_interpreter(const char *name);
+
+MRP_CDECL_END
+
+#endif /* __MURPHY_RESOLVER_H__ */
diff --git a/src/resolver/scanner.h b/src/resolver/scanner.h
new file mode 100644 (file)
index 0000000..13c872b
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_SCANNER_H__
+#define __MURPHY_RESOLVER_SCANNER_H__
+
+#include "murphy/resolver/parser-api.h"
+
+int scanner_push_file(yy_res_parser_t *parser, const char *path);
+void scanner_free_input(yy_res_input_t *in);
+
+int yy_res_lex(yy_res_parser_t *parser);
+
+#endif /* __MURPHY_RESOLVER_SCANNER_H__ */
diff --git a/src/resolver/scanner.l b/src/resolver/scanner.l
new file mode 100644 (file)
index 0000000..e603c3b
--- /dev/null
@@ -0,0 +1,327 @@
+%{ /* -*- c -*- */
+
+#define YY_DECL int yy_res_lex(yy_res_parser_t *parser)
+
+#include <stdio.h>
+#include <limits.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include "murphy/resolver/resolver.h"
+#include "murphy/resolver/scanner.h"
+#include "murphy/resolver/token.h"
+#include "murphy/resolver/parser-api.h"
+#include "murphy/resolver/parser.h"
+
+#define YY_NO_INPUT
+
+#define yy_res_create_buffer    yy_res__create_buffer
+#define yy_res_delete_buffer    yy_res__delete_buffer
+#define yy_res_switch_to_buffer yy_res__switch_to_buffer
+#define yy_res_scan_buffer      yy_res__scan_buffer
+
+
+/*
+ * lexical analyser input sources
+ *
+ * We support an include mechanism similar to the #include directive
+ * of the C-preprocessor. When one input file includes another, this
+ * is treated as if the latter file was verbatim copied in place of
+ * the include directive in the former file.
+ *
+ * The include mechanism is (almost) entirely implemented in the lexical
+ * analyser and is transparet to/hidden from the parser. The functions
+ * below and the macro CHECK_EOF take care of inclusion.
+ *
+ * Note that currently there is no any attempt to check for and prevent
+ * circular inclusion loops...
+ */
+
+int scanner_push_file(yy_res_parser_t *parser, const char *file)
+{
+    yy_res_input_t *in;
+    char            buf[PATH_MAX], *base;
+    const char     *path;
+    int             len;
+    FILE           *fp;
+
+    if (*file != '/' && parser->in != NULL) {
+        base = strrchr(parser->in->name, '/');
+        if (base != NULL) {
+            len = base - parser->in->name;
+            snprintf(buf, sizeof(buf), "%*.*s/%s",
+                     len, len, parser->in->name, file);
+            path = buf;
+        }
+        else
+            path = file;
+    }
+    else
+        path = file;
+
+    fp = fopen(path, "r");
+
+    if (fp != NULL) {
+        in = mrp_allocz(sizeof(*in));
+
+        if (in != NULL) {
+            in->fp    = fp;
+            in->name  = mrp_strdup(path);
+            in->line  = 1;
+            in->yybuf = yy_res_create_buffer(in->fp, YY_BUF_SIZE);
+            in->prev  = parser->in;
+
+            yy_res_switch_to_buffer(in->yybuf);
+
+            parser->in = in;
+
+            return TRUE;
+        }
+        else
+            fclose(fp);
+    }
+    else
+        mrp_log_error("Failed to open input file '%s' ('%s', error: %s).",
+                      file, path, strerror(errno));
+
+    return FALSE;
+}
+
+
+void scanner_free_input(yy_res_input_t *in)
+{
+    if (in != NULL) {
+        if (in->fp != NULL)
+            fclose(in->fp);
+        mrp_free(in->name);
+        yy_res_delete_buffer(in->yybuf);
+
+        mrp_free(in);
+    }
+}
+
+
+int scanner_pop_input(yy_res_parser_t *parser)
+{
+    yy_res_input_t *in, *prev;
+
+    if (parser->in != NULL) {
+        in   = parser->in;
+        prev = in->prev;
+
+        in->prev     = parser->done;
+        parser->done = in;
+
+        parser->in = prev;
+        if (prev != NULL) {
+            yy_res_switch_to_buffer(prev->yybuf);
+
+            return TRUE;                 /* more input to process */
+        }
+    }
+
+    return FALSE;                        /* no more input */
+}
+
+
+/*
+ * ringbuffer of tokens
+ *
+ * To simplify the lifecycle management of tokens passed between the
+ * lexical analyser and the parser we collect them into a ring buffer
+ * instead of dynamic allocation. This simplifies both the lexical
+ * analyser and the parser and allows us to have sane owner allocates /
+ * owner frees allocation semantics. The price we pay for this is that
+ * the ring buffer must be big enough to accomodate all the unprocessed
+ * tokens between bison rule reductions.
+ */
+
+static char *save_token(yy_res_parser_t *parser, char *str, size_t size)
+{
+    char *token;
+
+    if (!size)
+        size = strlen(str);
+
+    if (parser->offs + size + 1 >= YY_RES_RINGBUF_SIZE)
+        parser->offs = 0;
+
+    token = parser->ringbuf + parser->offs;
+    parser->offs += size + 1;
+
+#ifdef __MURPHY_RESOLVER_CHECK_RINGBUF__
+    if (*token != '\0') {
+        mrp_log_error("Token ring buffer overflow in resolver lexical "
+                      "analyser.");
+        exit(1);
+    }
+#endif
+
+    strncpy(token, str, size);
+    token[size] = '\0';
+
+    yy_res_lval.any.token  = token;
+    yy_res_lval.any.source = parser->in->name;
+    yy_res_lval.any.line   = parser->in->line;
+    yy_res_lval.any.size   = size;
+
+    return token;
+}
+
+
+/*
+ * string token types (must include all token types passed via STRING_TOKEN)
+ */
+
+typedef enum {
+    STRING_TYPE_IDENT,
+    STRING_TYPE_FACT,
+    STRING_TYPE_SCRIPT_LINE,
+} string_type_t;
+
+
+#define KEYWORD_TOKEN(tkn) do {                         \
+        save_token(parser, yy_res_text, yy_res_leng);   \
+                                                        \
+        mrp_debug("KEY_%s", #tkn);                      \
+                                                        \
+        return KEY_##tkn;                               \
+    } while (0)
+
+#define KEYWORD_TOKENV(tkn, val, size) do {             \
+        size_t  _size;                                  \
+        char   *_t;                                     \
+                                                        \
+        _size = size ? size : strlen(val);              \
+        _t    = save_token(parser, val, _size);         \
+        yy_res_lval.string.value = _t;                  \
+                                                        \
+        mrp_debug("KEY_%s ('%s')", #tkn, val);          \
+                                                        \
+        return KEY_##tkn;                               \
+    } while (0)
+
+
+#define STRING_TOKEN(tkn) do {                          \
+        char *_t, *_v;                                  \
+        int   _l;                                       \
+                                                        \
+        switch (STRING_TYPE_##tkn) {                    \
+        case STRING_TYPE_FACT:                          \
+            _v = yy_res_text;                           \
+            _l = yy_res_leng;                           \
+            break;                                      \
+        default:                                        \
+            _v = yy_res_text;                           \
+            _l = yy_res_leng;                           \
+        }                                               \
+                                                        \
+        _t = save_token(parser, _v, _l);                \
+        yy_res_lval.string.value = _t;                  \
+                                                        \
+        mrp_debug("TKN_%s ('%s')", #tkn, _t);           \
+                                                        \
+        return TKN_##tkn;                               \
+    } while (0)
+
+
+#define IGNORE_TOKEN(tkn) do {                          \
+        mrp_debug("ignore %s ('%s')", #tkn,             \
+                  yy_res_text);                         \
+    } while (0)
+
+
+#define INCLUDE_FILE() do {                             \
+        char *_p;                                       \
+        int   _l;                                       \
+                                                        \
+        _p = strchr(yy_res_text, '"');                  \
+        if (_p != NULL) {                               \
+            _p++;                                       \
+            _l = yy_res_leng - (_p - yy_res_text);      \
+            _p = save_token(parser, _p, _l - 1);        \
+                                                        \
+            mrp_debug("including file '%s'...", _p);    \
+                                                        \
+            if (!scanner_push_file(parser, _p))         \
+                return TKN_LEX_ERROR;                   \
+        }                                               \
+    } while (0)
+
+#define CHECK_EOF() do {                                \
+        if (!scanner_pop_input(parser))                 \
+            yyterminate();                              \
+    } while (0)
+
+%}
+
+%option warn
+%option batch
+%option noyywrap
+%option nounput
+
+WS                    [ \t]+
+OWS                   [ \t]*
+ESCAPED_EOL           \\\n
+EOL                   \n
+COMMENT               #.*
+
+INCLUDE               ^include{WS}\"[^\"]*\"
+
+TARGET                ^target
+AUTOUPDATE            ^auto-update-target
+DEPENDS_ON            depends\ on
+IDENT                 [a-zA-Z_][a-zA-Z0-9_]+
+FACT                  \${IDENT}(\.{IDENT})*
+UPDATE_SCRIPT         ^{WS}(update\ script|update\ script{OWS}\({OWS}{IDENT}{OWS}\))
+END_SCRIPT            ^{WS}end\ script
+PAREN_OPEN            \(
+PAREN_CLOSE           \)
+SCRIPT_LINE           ^{WS}.*
+
+%x SCRIPT
+
+%%
+
+{TARGET}              { KEYWORD_TOKEN(TARGET);                         }
+{AUTOUPDATE}          { KEYWORD_TOKEN(AUTOUPDATE);                     }
+{DEPENDS_ON}          { KEYWORD_TOKEN(DEPENDS_ON);                     }
+{IDENT}               { STRING_TOKEN(IDENT);                           }
+{FACT}                { STRING_TOKEN(FACT);                            }
+
+{UPDATE_SCRIPT}       {
+                        char   *type, *end, *p;
+                        size_t  len;
+
+                        BEGIN(SCRIPT);
+
+                        type = strchr(yy_res_text, '(');
+
+                        if (type != NULL) {
+                            end = strchr(type, ')');
+                            len = end - type - 1;
+                            p   = type + 1;
+                            while (*p == ' ' || *p == '\t')
+                                p++, len--;
+                            while (len > 0 &&
+                                   (p[len-1] == ' ' || p[len-1] == '\t'))
+                                len--;
+
+                            KEYWORD_TOKENV(UPDATE_SCRIPT, p, len);
+                        }
+                        else
+                            KEYWORD_TOKENV(UPDATE_SCRIPT, "simple", 0);
+                      }
+<SCRIPT>{END_SCRIPT}  { BEGIN(INITIAL); KEYWORD_TOKEN(END_SCRIPT);     }
+<SCRIPT>{SCRIPT_LINE} { STRING_TOKEN(SCRIPT_LINE);                     }
+
+<*>{WS}               { /*IGNORE_TOKEN(WS);*/                          }
+<*>{EOL}              { parser->in->line++; /*IGNORE_TOKEN(EOL);*/     }
+{ESCAPED_EOL}         { parser->in->line++; /*IGNORE_TOKEN(EOL);*/     }
+<INITIAL>{COMMENT}    { parser->in->line++; /*IGNORE_TOKEN(COMMENT);*/ }
+{INCLUDE}             { INCLUDE_FILE();                                }
+<<EOF>>               { CHECK_EOF();                                   }
+
+.                     { mrp_log_error("Unhandled token '%s'",
+                                      yy_res_text);                    }
diff --git a/src/resolver/scripting/simple/builtins.c b/src/resolver/scripting/simple/builtins.c
new file mode 100644 (file)
index 0000000..4ec083f
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <murphy/common/log.h>
+#include <murphy/core/method.h>
+
+#include "builtins.h"
+
+static int builtin_echo(mrp_plugin_t *plugin, const char *name,
+                        mrp_script_env_t *env)
+{
+    mrp_script_value_t *arg;
+    int                 i;
+    char                buf[512], *t;
+
+    MRP_UNUSED(plugin);
+    MRP_UNUSED(name);
+
+    for (i = 0, arg = env->args, t = ""; i < env->narg; i++, arg++, t=" ")
+        printf("%s%s", t, mrp_print_value(buf, sizeof(buf), arg));
+
+    printf("\n");
+
+    return TRUE;
+}
+
+
+int export_builtins(void)
+{
+    mrp_method_descr_t methods[] = {
+        {
+            .name       = "echo",
+            .signature  = NULL  ,
+            .native_ptr = NULL  ,
+            .script_ptr = builtin_echo,
+            .plugin     = NULL
+        },
+        { NULL, NULL, NULL, NULL, NULL }
+    }, *m;
+
+    for (m = methods; m->name != NULL; m++) {
+        if (mrp_export_method(m) < 0) {
+            mrp_log_error("Failed to export function '%s'.", m->name);
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
diff --git a/src/resolver/scripting/simple/builtins.h b/src/resolver/scripting/simple/builtins.h
new file mode 100644 (file)
index 0000000..fb51709
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SIMPLE_BUILTINS_H__
+#define __MURPHY_SIMPLE_BUILTINS_H__
+
+int export_builtins(void);
+
+#endif
diff --git a/src/resolver/scripting/simple/call.c b/src/resolver/scripting/simple/call.c
new file mode 100644 (file)
index 0000000..ab1bc9c
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/core/plugin.h>       /* XXX TODO, needed for method.h */
+#include <murphy/core/method.h>
+
+#include "call.h"
+
+function_call_t *create_call(char *function, arg_t *args, int narg)
+{
+    function_call_t *c;
+
+    c = mrp_allocz(sizeof(*c));
+
+    if (c != NULL) {
+        c->name = mrp_strdup(function);
+
+        if (c->name != NULL) {
+            mrp_list_init(&c->hook);
+            c->args = args;
+            c->narg = narg;
+
+            return c;
+        }
+
+        mrp_free(c);
+    }
+
+    return NULL;
+}
+
+
+void destroy_call(function_call_t *c)
+{
+    if (c != NULL) {
+        mrp_list_delete(&c->hook);
+        mrp_free(c->name);
+        destroy_arguments(c->args, c->narg);
+        mrp_free(c);
+    }
+}
+
+
+int set_constant_value_arg(arg_t *arg, mrp_script_value_t *value)
+{
+    arg->cst.type  = ARG_CONST_VALUE;
+    arg->cst.value = *value;
+
+    if (value->type == MRP_SCRIPT_TYPE_STRING) {
+        arg->cst.value.str = mrp_strdup(value->str);
+
+        if (arg->cst.value.str != NULL)
+            return TRUE;
+        else
+            return FALSE;
+    }
+    else
+        return TRUE;
+}
+
+
+int set_context_value_arg(arg_t *arg, char *name)
+{
+    arg->val.type = ARG_CONTEXT_VAR;
+    arg->val.name = mrp_strdup(name);
+
+    if (arg->val.name != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+int set_context_set_arg(arg_t *arg, char *name, mrp_script_value_t *value)
+{
+    arg->set.type  = ARG_CONTEXT_SET;
+    arg->set.name  = mrp_strdup(name);
+
+    if (arg->set.name == NULL)
+        return FALSE;
+
+    arg->set.id    = -1;
+    arg->set.value = *value;
+
+    if (value->type == MRP_SCRIPT_TYPE_STRING) {
+        arg->set.value.str = mrp_strdup(value->str);
+
+        if (arg->set.value.str == NULL)
+            return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+void destroy_arguments(arg_t *args, int narg)
+{
+    arg_t *a;
+    int    i;
+
+    for (i = 0, a = args; i < narg; i++, a++) {
+        if (a->type           == ARG_CONST_VALUE &&
+            a->cst.value.type == MRP_SCRIPT_TYPE_STRING)
+            mrp_free(a->cst.value.str);
+        else if (a->type == ARG_CONTEXT_VAR)
+            mrp_free(a->val.name);
+    }
+
+    mrp_free(args);
+}
+
+
+int link_call(function_call_t *c)
+{
+    mrp_plugin_t  *plugin;
+    int          (*script_ptr)(mrp_plugin_t *plugin, const char *name,
+                               mrp_script_env_t *env);
+
+    if (c->script_ptr == NULL) {
+        if (mrp_import_method(c->name, NULL, NULL, &script_ptr, &plugin) < 0) {
+            mrp_log_error("Failed to find method '%s'.", c->name);
+            return FALSE;
+        }
+        else {
+            c->script_ptr = script_ptr;
+            c->plugin     = plugin;
+        }
+    }
+
+    return TRUE;
+}
+
+int execute_call(function_call_t *c, mrp_context_tbl_t *tbl)
+{
+    mrp_script_env_t   env;
+    mrp_script_value_t args[c->narg];
+    arg_t             *a;
+    int                narg, n, status;
+
+    if (MRP_UNLIKELY(c->script_ptr == NULL)) {
+        if (!link_call(c))
+            return -ENOENT;
+    }
+
+    mrp_push_context_frame(tbl);
+
+    for (n = narg = 0, a = c->args; n < (int)MRP_ARRAY_SIZE(args); n++, a++) {
+        switch (a->type) {
+        case ARG_CONST_VALUE:
+            args[narg++] = a->cst.value;
+            break;
+        case ARG_CONTEXT_VAR:
+            if (a->val.id <= 0)
+                a->val.id = mrp_get_context_id(tbl, a->val.name);
+            if (mrp_get_context_value(tbl, a->val.id, args + narg) < 0) {
+                status = -ENOENT;
+                goto pop_frame;
+            }
+            narg++;
+            break;
+        case ARG_CONTEXT_SET:
+            if (a->set.id <= 0)
+                a->set.id = mrp_get_context_id(tbl, a->set.name);
+            if (mrp_set_context_value(tbl, a->set.id, &a->set.value) < 0) {
+                status = -errno;
+                goto pop_frame;
+            }
+        default:
+            status = -EINVAL;
+            goto pop_frame;
+        }
+    }
+
+    env.args = args;
+    env.narg = narg;
+    env.ctbl = tbl;
+
+    status = c->script_ptr(c->plugin, c->name, &env);
+
+ pop_frame:
+    mrp_pop_context_frame(tbl);
+
+    return status;
+}
+
+
+static void dump_arg(FILE *fp, arg_t *arg)
+{
+    mrp_script_value_t *val;
+    char                vbuf[64];
+
+    switch (arg->type) {
+    case ARG_CONST_VALUE:
+        val = &arg->cst.value;
+        fprintf(fp, "%s", mrp_print_value(vbuf, sizeof(vbuf), val));
+        break;
+
+    case ARG_CONTEXT_VAR:
+        fprintf(fp, "&%s", arg->val.name);
+        break;
+
+    case ARG_CONTEXT_SET:
+        val = &arg->set.value;
+        fprintf(fp, "&%s=%s", arg->set.name,
+                mrp_print_value(vbuf, sizeof(vbuf), val));
+        break;
+
+    default:
+        fprintf(fp, "<unknown/unhandled argument type>");
+    }
+}
+
+
+void dump_call(FILE *fp, function_call_t *c)
+{
+    int   i;
+    char *t;
+
+    fprintf(fp, "    %s", c->name);
+
+    fprintf(fp, "(");
+    for (i = 0, t = ""; i < c->narg; i++, t = ", ") {
+        fprintf(fp, "%s", t);
+        dump_arg(fp, c->args + i);
+    }
+    fprintf(fp, ")\n");
+}
diff --git a/src/resolver/scripting/simple/call.h b/src/resolver/scripting/simple/call.h
new file mode 100644 (file)
index 0000000..9535f7c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SIMPLE_SCRIPT_CALL_H__
+#define __MURPHY_SIMPLE_SCRIPT_CALL_H__
+
+#include <murphy/core/scripting.h>
+
+#include "simple-script.h"
+
+function_call_t *create_call(char *name, arg_t *args, int narg);
+int link_call(function_call_t *c);
+void destroy_call(function_call_t *c);
+void dump_call(FILE *fp, function_call_t *c);
+
+int set_constant_value_arg(arg_t *arg, mrp_script_value_t *value);
+int set_context_value_arg(arg_t *arg, char *name);
+int set_context_set_arg(arg_t *arg, char *name,
+                        mrp_script_value_t *value);
+void destroy_arguments(arg_t *args, int narg);
+int execute_call(function_call_t *c, mrp_context_tbl_t *tbl);
+
+#endif /* __MURPHY_SIMPLE_SCRIPT_CALL_H__ */
diff --git a/src/resolver/scripting/simple/simple-parser-api.h b/src/resolver/scripting/simple/simple-parser-api.h
new file mode 100644 (file)
index 0000000..8de61b0
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SIMPLE_SCRIPT_PARSER_API_H__
+#define __MURPHY_SIMPLE_SCRIPT_PARSER_API_H__
+
+#include <stdio.h>
+
+#include <murphy/common/list.h>
+
+#include "simple-script.h"
+
+#define YY_SMPL_RINGBUF_SIZE (8 * 1024)            /* token buffer size */
+
+/*
+ * a parsed script
+ */
+
+typedef struct {
+    mrp_list_hook_t  statements;                    /* list of statements */
+    void            *yybuf;                         /* scanner buffer */
+    int              line;                          /* input line number */
+    char             ringbuf[YY_SMPL_RINGBUF_SIZE]; /* token ringbuffer */
+    int              offs;                          /* buffer insert offset */
+} yy_smpl_parser_t;
+
+
+
+
+
+int simple_parser_setup(yy_smpl_parser_t *parser, const char *script);
+void simple_parser_cleanup(yy_smpl_parser_t *parser);
+int simple_parser_parse(yy_smpl_parser_t *parser, const char *script);
+
+#endif /* __MURPHY_SIMPLE_SCRIPT_PARSER_API_H__ */
diff --git a/src/resolver/scripting/simple/simple-parser.y b/src/resolver/scripting/simple/simple-parser.y
new file mode 100644 (file)
index 0000000..4134d90
--- /dev/null
@@ -0,0 +1,343 @@
+%{ /* -*- c -*- */
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+
+#include "simple-parser-api.h"
+#include "token.h"
+#include "simple-scanner.h"
+#include "call.h"
+
+void yy_smpl_error(yy_smpl_parser_t *parser, const char *msg);
+
+static tkn_strarr_t *strarr_append(tkn_strarr_t *arr, char *str);
+static int initialize_argument(tkn_args_t *args, tkn_expr_t *expr);
+static int append_argument(tkn_args_t *args, tkn_expr_t *expr);
+static void cleanup_arguments(tkn_args_t *args);
+
+%}
+
+%union {
+    tkn_any_t       any;
+    tkn_string_t    string;
+    tkn_s8_t        sint8;
+    tkn_u8_t        uint8;
+    tkn_s16_t       sint16;
+    tkn_u16_t       uint16;
+    tkn_s32_t       sint32;
+    tkn_u32_t       uint32;
+    tkn_s64_t       sint64;
+    tkn_u64_t       uint64;
+    tkn_dbl_t       dbl;
+    tkn_strarr_t   *strarr;
+    tkn_string_t    error;
+    tkn_args_t      args;
+    tkn_expr_t      expr;
+    tkn_value_t     value;
+}
+
+%defines
+%parse-param { yy_smpl_parser_t *parser }
+%lex-param   { yy_smpl_parser_t *parser }
+
+%token          KEY_TARGET
+%token          KEY_DEPENDS_ON
+%token          KEY_UPDATE_SCRIPT
+%token          KEY_END_SCRIPT
+
+%token <string> TKN_IDENT
+%token <string> TKN_CONTEXT_VAR
+%token <string> TKN_STRING
+
+%token <sint8>  TKN_SINT8
+%token <uint8>  TKN_UINT8
+%token <sint16> TKN_SINT16
+%token <uint16> TKN_UINT16
+%token <sint32> TKN_SINT32
+%token <uint32> TKN_UINT32
+%token <sint64> TKN_SINT64
+%token <uint64> TKN_UINT64
+%token <dbl>    TKN_DOUBLE
+
+%token <string> TKN_PARENTH_OPEN  "("
+%token <string> TKN_PARENTH_CLOSE ")"
+%token <string> TKN_COMMA         ","
+%token <string> TKN_EQUAL         "="
+
+%token <string> TKN_ERROR
+
+%type <value>  constant_expression
+%type <string> context_value
+%type <expr>   context_assign
+%type <expr>   expression
+%type <args>   arguments
+
+%%
+
+input: statements
+  ;
+
+statements:
+  statement
+| statements statement
+| statements error     { YYABORT; }
+  ;
+
+statement: function_call
+  ;
+
+function_call:
+  TKN_IDENT "(" ")" {
+      function_call_t *c;
+
+      c = create_call($1.value, NULL, 0);
+
+      if (c != NULL)
+          mrp_list_append(&parser->statements, &c->hook);
+      else {
+          mrp_log_error("Failed to create new simple-script call.");
+          YYABORT;
+      }
+  }
+| TKN_IDENT "(" arguments ")" {
+      function_call_t *c;
+
+      c = create_call($1.value, $3.args, $3.narg);
+
+      if (c != NULL)
+          mrp_list_append(&parser->statements, &c->hook);
+      else {
+          mrp_log_error("Failed to create new simple-script call.");
+          YYABORT;
+      }
+  }
+  ;
+
+arguments:
+  expression {
+      if (!initialize_argument(&$$, &$1))
+          YYABORT;
+  }
+| arguments "," expression {
+      if (!append_argument(&$$, &$3)) {
+          cleanup_arguments(&$$);
+          YYABORT;
+      }
+  }
+| arguments error { YYABORT; }
+  ;
+
+expression:
+  constant_expression {
+      $$.cst.type  = EXPR_CONSTANT;
+      $$.cst.value = $1.value;
+  }
+| context_value {
+      $$.val.type  = EXPR_CONTEXT_VALUE;
+      $$.val.name  = $1.value;
+  }
+| context_assign {
+      $$ = $1;
+  }
+  ;
+
+constant_expression:
+  TKN_STRING {
+      $$.value.type = MRP_SCRIPT_TYPE_STRING;
+      $$.value.str  = $1.value;
+  }
+| TKN_SINT8 {
+      $$.value.type = MRP_SCRIPT_TYPE_SINT8;
+      $$.value.s8   = $1.value;
+  }
+| TKN_UINT8 {
+      $$.value.type = MRP_SCRIPT_TYPE_UINT8;
+      $$.value.u8   = $1.value;
+  }
+| TKN_SINT16 {
+      $$.value.type = MRP_SCRIPT_TYPE_SINT16;
+      $$.value.s16  = $1.value;
+  }
+| TKN_UINT16 {
+      $$.value.type = MRP_SCRIPT_TYPE_UINT16;
+      $$.value.u16  = $1.value;
+  }
+| TKN_SINT32 {
+      $$.value.type = MRP_SCRIPT_TYPE_SINT32;
+      $$.value.s32  = $1.value;
+  }
+| TKN_UINT32 {
+      $$.value.type = MRP_SCRIPT_TYPE_UINT32;
+      $$.value.u32  = $1.value;
+  }
+| TKN_SINT64 {
+      $$.value.type = MRP_SCRIPT_TYPE_SINT64;
+      $$.value.s64  = $1.value;
+  }
+| TKN_UINT64 {
+      $$.value.type = MRP_SCRIPT_TYPE_UINT64;
+      $$.value.u64  = $1.value;
+  }
+| TKN_DOUBLE {
+      $$.value.type = MRP_SCRIPT_TYPE_DOUBLE;
+      $$.value.dbl = $1.value;
+  }
+  ;
+
+context_value: TKN_CONTEXT_VAR { $$ = $1; }
+  ;
+
+context_assign: TKN_CONTEXT_VAR "=" constant_expression {
+      mrp_debug("context_assign");
+      $$.set.type  = EXPR_CONTEXT_SET;
+      $$.set.name  = $1.value;
+      $$.set.value = $3.value;
+  }
+  ;
+
+
+%%
+
+static int initialize_argument(tkn_args_t *args, tkn_expr_t *expr)
+{
+    arg_t *arg;
+
+    args->args = mrp_allocz(sizeof(*args->args));
+
+    if (args->args != NULL) {
+        arg        = args->args;
+        args->narg = 1;
+
+        switch (expr->type) {
+        case EXPR_CONSTANT:
+            set_constant_value_arg(arg, &expr->cst.value);
+            break;
+        case EXPR_CONTEXT_VALUE:
+            set_context_value_arg(&args->args[0], expr->val.name);
+            break;
+
+        case EXPR_CONTEXT_SET:
+            set_context_set_arg(&args->args[0],
+                                expr->set.name, &expr->set.value);
+            break;
+
+        default:
+            return FALSE;
+        }
+
+        return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static int append_argument(tkn_args_t *args, tkn_expr_t *expr)
+{
+    arg_t *arg;
+
+    if (!mrp_reallocz(args->args, args->narg, args->narg + 1))
+        return FALSE;
+
+    arg = args->args + args->narg;
+    args->narg++;
+
+    switch (expr->type) {
+    case EXPR_CONSTANT:
+        set_constant_value_arg(arg, &expr->cst.value);
+        break;
+    case EXPR_CONTEXT_VALUE:
+        set_context_value_arg(&args->args[0], expr->val.name);
+        break;
+    case EXPR_CONTEXT_SET:
+        set_context_set_arg(&args->args[0], expr->set.name, &expr->set.value);
+
+    default:
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+
+static void cleanup_arguments(tkn_args_t *args)
+{
+    destroy_arguments(args->args, args->narg);
+}
+
+
+void yy_smpl_error(yy_smpl_parser_t *parser, const char *msg)
+{
+    MRP_UNUSED(parser);
+
+    mrp_log_error("parse error at line %d near token '%s': %s",
+                  yy_smpl_lval.any.line, yy_smpl_lval.any.token, msg);
+}
+
+
+static tkn_strarr_t *strarr_append(tkn_strarr_t *arr, char *str)
+{
+    int n;
+
+    if (arr == NULL) {
+        arr = mrp_allocz(sizeof(*arr));
+        if (arr == NULL)
+            return NULL;
+    }
+
+    if (!mrp_reallocz(arr->strs, arr->nstr, arr->nstr + 1))
+        return NULL;
+
+    n = arr->nstr++;
+    arr->strs[n] = mrp_strdup(str);
+
+    if (arr->strs[n] != NULL)
+        return arr;
+    else {
+        if (n == 0) {
+            mrp_free(arr->strs);
+            mrp_free(arr);
+        }
+
+        return NULL;
+    }
+}
+
+
+int simple_parser_setup(yy_smpl_parser_t *parser, const char *script)
+{
+    MRP_UNUSED(strarr_append);
+
+    mrp_clear(parser);
+
+    mrp_list_init(&parser->statements);
+    parser->line = 1;
+
+    return simple_scanner_setup(parser, script);
+}
+
+
+void simple_parser_cleanup(yy_smpl_parser_t *parser)
+{
+    mrp_list_hook_t *p, *n;
+    function_call_t *c;
+
+    mrp_list_foreach(&parser->statements, p, n) {
+        c = mrp_list_entry(p, typeof(*c), hook);
+
+        destroy_call(c);
+    }
+
+    simple_scanner_cleanup(parser);
+}
+
+
+int simple_parser_parse(yy_smpl_parser_t *parser, const char *script)
+{
+    if (simple_parser_setup(parser, script))
+        if (yy_smpl_parse(parser) == 0)
+            return TRUE;
+
+    return FALSE;
+}
diff --git a/src/resolver/scripting/simple/simple-scanner.h b/src/resolver/scripting/simple/simple-scanner.h
new file mode 100644 (file)
index 0000000..27c734d
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SIMPLE_SCRIPT_SCANNER_H__
+#define __MURPHY_SIMPLE_SCRIPT_SCANNER_H__
+
+#include "murphy/resolver/scripting/simple/simple-parser-api.h"
+
+int simple_scanner_setup(yy_smpl_parser_t *parser, const char *script);
+void simple_scanner_cleanup(yy_smpl_parser_t *parser);
+int yy_smpl_lex(yy_smpl_parser_t *parser);
+
+#endif /* __MURPHY_RESOLVER_SCANNER_H__ */
diff --git a/src/resolver/scripting/simple/simple-scanner.l b/src/resolver/scripting/simple/simple-scanner.l
new file mode 100644 (file)
index 0000000..3f7bd01
--- /dev/null
@@ -0,0 +1,295 @@
+%{ /* -*- c -*- */
+
+#define YY_DECL int yy_smpl_lex(yy_smpl_parser_t *parser)
+
+#include <stdio.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include "murphy/resolver/resolver.h"
+#include "murphy/resolver/scripting/simple/simple-scanner.h"
+#include "murphy/resolver/scripting/simple/token.h"
+#include "murphy/resolver/scripting/simple/simple-parser-api.h"
+#include "murphy/resolver/scripting/simple/simple-parser.h"
+
+#define yy_smpl_create_buffer    yy_smpl__create_buffer
+#define yy_smpl_delete_buffer    yy_smpl__delete_buffer
+#define yy_smpl_switch_to_buffer yy_smpl__switch_to_buffer
+#define yy_smpl_scan_buffer      yy_smpl__scan_buffer
+#define yy_smpl_scan_string      yy_smpl__scan_string
+#define yy_smpl_input            input
+#define yy_smpl_unput            unput
+
+/*
+ * lexical analyser input sources
+ */
+
+int simple_scanner_setup(yy_smpl_parser_t *parser, const char *script)
+{
+    parser->yybuf = yy_smpl_scan_string(script);
+
+    if (parser->yybuf != NULL)
+        return TRUE;
+    else
+        return FALSE;
+}
+
+
+void simple_scanner_cleanup(yy_smpl_parser_t *parser)
+{
+    if (parser->yybuf != NULL) {
+        yy_smpl_delete_buffer(parser->yybuf);
+        parser->yybuf = NULL;
+    }
+}
+
+
+/*
+ * ringbuffer of tokens
+ *
+ * To simplify the lifecycle management of tokens passed between the
+ * lexical analyser and the parser we collect them into a ring buffer
+ * instead of dynamic allocation. This simplifies both the lexical
+ * analyser and the parser and allows us to have sane owner allocates /
+ * owner frees allocation semantics. The price we pay for this is that
+ * the ring buffer must be big enough to accomodate all the unprocessed
+ * tokens between bison rule reductions.
+ */
+
+static char *save_token(yy_smpl_parser_t *parser, char *str, size_t size)
+{
+    char *token;
+
+    if (!size)
+        size = strlen(str);
+
+    if (parser->offs + size + 1 >= YY_SMPL_RINGBUF_SIZE)
+        parser->offs = 0;
+
+    token = parser->ringbuf + parser->offs;
+    parser->offs += size + 1;
+
+#ifdef __MURPHY_SIMPLE_SCRIPT_CHECK_RINGBUF__
+    if (*token != '\0') {
+        mrp_log_error("Token ring buffer overflow in simple-script lexical "
+                      "analyser.");
+        exit(1);
+    }
+#endif
+
+    strncpy(token, str, size);
+    token[size] = '\0';
+
+    yy_smpl_lval.any.token = token;
+    yy_smpl_lval.any.line  = parser->line;
+    yy_smpl_lval.any.size  = size;
+
+    return token;
+}
+
+
+/*
+ * string token types (must include all token types passed via STRING_TOKEN)
+ */
+
+typedef enum {
+    STRING_TYPE_IDENT,
+    STRING_TYPE_CONTEXT_VAR,
+    STRING_TYPE_STRING,
+} string_type_t;
+
+
+#define KEYWORD_TOKEN(tkn) do {                         \
+        save_token(parser, yy_smpl_text, yy_smpl_leng); \
+                                                        \
+        mrp_debug("KEY_%s", #tkn);                      \
+                                                        \
+        return KEY_##tkn;                               \
+    } while (0)
+
+
+#define STRING_TOKEN(tkn) do {                          \
+        char *_t, *_v;                                  \
+        int   _l;                                       \
+                                                        \
+        switch (STRING_TYPE_##tkn) {                    \
+        case STRING_TYPE_STRING:                        \
+            _v = yy_smpl_text + 1;                      \
+            _l = yy_smpl_leng - 2;                      \
+            break;                                      \
+        case STRING_TYPE_CONTEXT_VAR:                   \
+            _v = yy_smpl_text + 1;                      \
+            _l = yy_smpl_leng - 1;                      \
+            break;                                      \
+        default:                                        \
+            _v = yy_smpl_text;                          \
+            _l = yy_smpl_leng;                          \
+        }                                               \
+                                                        \
+        _t = save_token(parser, _v, _l);                \
+        yy_smpl_lval.string.value = _t;                 \
+                                                        \
+        mrp_debug("TKN_%s: '%s'", #tkn, _t);            \
+                                                        \
+        return TKN_##tkn;                               \
+    } while (0)
+
+
+#define OTHER_TOKEN(tkn) do {                           \
+        char *_t, *_v;                                  \
+        int   _l;                                       \
+                                                        \
+        _v = yy_smpl_text;                              \
+        _l = yy_smpl_leng;                              \
+                                                        \
+        _t = save_token(parser, _v, _l);                \
+        yy_smpl_lval.string.value = _t;                 \
+                                                        \
+        mrp_debug("TKN_%s: '%s'", #tkn, _t);            \
+                                                        \
+        return TKN_##tkn;                               \
+    } while (0)
+
+
+#define INTEGER_TOKEN(tkn, type) do {                                  \
+        typeof(yy_smpl_lval.type.value)  _value;                       \
+        char *_tkn, *_end;                                             \
+                                                                       \
+        if (#type[0] == 'u')                                           \
+            _value = (typeof(_value))                                  \
+                strtoull(yy_smpl_text, &_end, 0);                      \
+        else                                                           \
+            _value = (typeof(_value))                                  \
+                strtoll(yy_smpl_text, &_end, 0);                       \
+                                                                       \
+        _tkn = save_token(parser, yy_smpl_text, yy_smpl_leng);         \
+                                                                       \
+        if (!*_end) {                                                  \
+            yy_smpl_lval.type.value = _value;                          \
+            mrp_debug("TKN_%s: '%s'", #tkn, _tkn);                     \
+                                                                       \
+            return TKN_##tkn;                                          \
+        }                                                              \
+        else {                                                         \
+            if ((_end[0] == 'S' || _end[0] == 'U') &&                  \
+                ((_end[1] == '8' && !_end[2])                    ||    \
+                 (_end[1] == '1' && _end[2] == '6' && !_end[3])  ||    \
+                 (_end[1] == '3' && _end[2] == '2' && !_end[3])  ||    \
+                 (_end[1] == '6' && _end[2] == '4' && !_end[3]))) {    \
+                yy_smpl_lval.type.value = _value;                      \
+                mrp_debug("TKN_%s: '%s'", #tkn, _tkn);                 \
+                                                                       \
+                return TKN_##tkn;                                      \
+            }                                                          \
+            else {                                                     \
+                yy_smpl_lval.error.value = "couldn't parse integer";   \
+                mrp_debug("TKN_ERROR: failed to parse integer.");      \
+                                                                       \
+                return TKN_ERROR;                                      \
+            }                                                          \
+        }                                                              \
+    } while (0)
+
+
+#define DOUBLE_TOKEN() do {                                            \
+        char *_tkn, *_end;                                             \
+                                                                       \
+        yy_smpl_lval.dbl.value = strtod(yy_smpl_text, &_end);          \
+                                                                       \
+        _tkn = save_token(parser, yy_smpl_text, yy_smpl_leng);         \
+                                                                       \
+        if (!*_end) {                                                  \
+            mrp_debug("TKN_DOUBLE: '%s'", _tkn);                       \
+                                                                       \
+            return TKN_DOUBLE;                                         \
+        }                                                              \
+        else {                                                         \
+            yy_smpl_lval.error.value = "couldn't parse integer";       \
+            mrp_debug("TKN_ERROR: failed to parse integer.");          \
+                                                                       \
+            return TKN_ERROR;                                          \
+        }                                                              \
+    } while (0)
+
+
+#define IGNORE_TOKEN(tkn) do {                                         \
+        mrp_debug("ignore %s ('%s')", #tkn, yy_smpl_text);             \
+    } while (0)
+
+
+#define PROCESS_ESCAPE() do {                                           \
+        int _c;                                                         \
+                                                                        \
+        switch ((_c = yy_smpl_input())) {                               \
+        case '\n':                                                      \
+            mrp_debug("ignore escaped '\\n'");                          \
+            parser->line++;                                             \
+            break;                                                      \
+        default:                                                        \
+            mrp_debug("escaped '%c'", _c);                              \
+            yy_smpl_unput(_c);                                          \
+        }                                                               \
+    } while (0)
+
+%}
+
+%option warn
+%option batch
+%option noyywrap
+
+
+WS                    [ \t]+
+EMPTY_LINE            [ \t]*$
+ESCAPE                \\
+IDENT                 [a-zA-Z_][a-zA-Z0-9_]+
+CONTEXT_VAR           &{IDENT}
+EOL                   \n
+STRING                ('[^\n']*')|(\"[^\n\"]*\")
+INTEGER               [+-]?[0-9]+
+HEXAINT               [+-]?0x[0-9a-fA-F]+
+DOUBLE                [+-]?[0-9]+\.[0-9]+
+SINT8                 ({INTEGER}|{HEXAINT})S8
+UINT8                 ({INTEGER}|{HEXAINT})U8
+SINT16                ({INTEGER}|{HEXAINT})S16
+UINT16                ({INTEGER}|{HEXAINT})U16
+SINT32                ({INTEGER}|{HEXAINT})S32
+UINT32                ({INTEGER}|{HEXAINT})U32
+SINT64                ({INTEGER}|{HEXAINT})S64
+UINT64                ({INTEGER}|{HEXAINT})U64
+
+%%
+
+{IDENT}               { STRING_TOKEN(IDENT);           }
+{CONTEXT_VAR}         { STRING_TOKEN(CONTEXT_VAR);     }
+{STRING}              { STRING_TOKEN(STRING);          }
+
+{SINT8}               { INTEGER_TOKEN(SINT8 , sint8 ); }
+{UINT8}               { INTEGER_TOKEN(UINT8 , uint8 ); }
+{SINT16}              { INTEGER_TOKEN(SINT16, sint16); }
+{UINT16}              { INTEGER_TOKEN(UINT16, uint16); }
+{SINT32}              { INTEGER_TOKEN(SINT32, sint32); }
+{UINT32}              { INTEGER_TOKEN(UINT32, uint32); }
+{SINT64}              { INTEGER_TOKEN(SINT64, sint64); }
+{UINT64}              { INTEGER_TOKEN(UINT64, uint64); }
+{INTEGER}             { INTEGER_TOKEN(SINT32, sint32); }
+{HEXAINT}             { INTEGER_TOKEN(SINT32, sint32); }
+{DOUBLE}              { DOUBLE_TOKEN();                }
+
+
+\(                    { OTHER_TOKEN(PARENTH_OPEN);     }
+\)                    { OTHER_TOKEN(PARENTH_CLOSE);    }
+,                     { OTHER_TOKEN(COMMA);            }
+=                     { OTHER_TOKEN(EQUAL);            }
+
+{WS}                  { /*IGNORE_TOKEN(WS);*/                         }
+{EOL}                 { parser->line++; /*IGNORE_TOKEN(EOL);*/        }
+{EMPTY_LINE}          { parser->line++; /*IGNORE_TOKEN(EMPTY_LINE);*/ }
+{ESCAPE}              { PROCESS_ESCAPE();                             }
+
+<<EOF>>               { yy_smpl_pop_buffer_state();
+                        parser->yybuf = NULL;
+                        yyterminate();                                }
+
+.                     { mrp_log_error("Unhandled token '%s'",
+                                      yy_smpl_text);                  }
diff --git a/src/resolver/scripting/simple/simple-script.c b/src/resolver/scripting/simple/simple-script.c
new file mode 100644 (file)
index 0000000..9249748
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/macros.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+#include <murphy/common/list.h>
+
+#include <murphy/resolver/resolver.h>
+#include "simple-parser-api.h"
+#include "call.h"
+#include "builtins.h"
+
+
+static void simple_dump(FILE *fp, simple_script_t *ss)
+{
+    mrp_list_hook_t *p, *n;
+    function_call_t *c;
+
+    if (ss != NULL) {
+        mrp_list_foreach(&ss->statements, p, n) {
+            c = mrp_list_entry(p, typeof(*c), hook);
+
+            dump_call(fp, c);
+        }
+    }
+}
+
+
+static int simple_compile(mrp_scriptlet_t *script)
+{
+    static int builtins_exported = FALSE;
+
+    yy_smpl_parser_t  parser;
+    simple_script_t  *ss;
+
+    if (!builtins_exported) {
+        if (!export_builtins())
+            return -1;
+        else
+            builtins_exported = TRUE;
+    }
+
+    if (simple_parser_parse(&parser, script->source)) {
+        ss = mrp_allocz(sizeof(*ss));
+
+        if (ss != NULL) {
+            script->compiled = ss;
+
+            mrp_list_move(&ss->statements, &parser.statements);
+            simple_parser_cleanup(&parser);
+
+            return 0;
+        }
+        else
+            simple_parser_cleanup(&parser);
+    }
+
+    return -1;
+}
+
+
+static int simple_prepare(mrp_scriptlet_t *s)
+{
+    simple_script_t *ss = s->compiled;
+    mrp_list_hook_t *p, *n;
+    function_call_t *c;
+
+    if (ss != NULL) {
+        mrp_list_foreach(&ss->statements, p, n) {
+            c = mrp_list_entry(p, typeof(*c), hook);
+
+            if (!link_call(c)) {
+                errno = ENOENT;
+                return -1;
+            }
+        }
+
+        return 0;
+    }
+    else {
+        errno = EINVAL;
+        return -1;
+    }
+}
+
+
+static int simple_execute(mrp_scriptlet_t *s, mrp_context_tbl_t *tbl)
+{
+    simple_script_t *ss = s->compiled;
+    mrp_list_hook_t *p, *n;
+    function_call_t *c;
+    int              status;
+
+    if (ss != NULL) {
+        mrp_list_foreach(&ss->statements, p, n) {
+            c = mrp_list_entry(p, typeof(*c), hook);
+
+            status = execute_call(c, tbl);
+
+            if (status <= 0)
+                return status;
+        }
+    }
+
+    return TRUE;
+}
+
+
+static void simple_cleanup(mrp_scriptlet_t *s)
+{
+    MRP_UNUSED(s);
+    MRP_UNUSED(simple_dump);
+
+    return;
+}
+
+MRP_REGISTER_INTERPRETER("simple",
+                         simple_compile, simple_prepare,
+                         simple_execute, simple_cleanup);
diff --git a/src/resolver/scripting/simple/simple-script.h b/src/resolver/scripting/simple/simple-script.h
new file mode 100644 (file)
index 0000000..c4d0533
--- /dev/null
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SIMPLE_SCRIPT_H__
+#define __MURPHY_SIMPLE_SCRIPT_H__
+
+#include <murphy/core/plugin.h>
+#include <murphy/core/scripting.h>
+
+typedef struct {
+    mrp_list_hook_t statements;          /* list of (call) statements */
+} simple_script_t;
+
+
+typedef enum {
+    ARG_UNKNOWN = 0,
+    ARG_CONST_VALUE,
+    ARG_CONTEXT_VAR,
+    ARG_CONTEXT_SET,
+} arg_type_t;
+
+
+typedef struct {
+    arg_type_t         type;             /* ARG_CONST_VALUE */
+    mrp_script_value_t value;            /* actual value */
+} const_arg_t;
+
+
+typedef struct {
+    arg_type_t  type;                    /* ARG_CONTEXT_VAR */
+    char       *name;                    /* name of variable */
+    int         id;                      /* variable id */
+} ctx_val_arg_t;
+
+
+typedef struct {
+    arg_type_t          type;            /* ARG_CONTEXT_SET */
+    char               *name;            /* name of variable */
+    int                 id;              /* variable id */
+    mrp_script_value_t  value;           /* value to set to */
+} ctx_set_arg_t;
+
+
+typedef union {
+    arg_type_t     type;                 /* argument type */
+    const_arg_t    cst;                  /* constant argument */
+    ctx_val_arg_t  val;                  /* context variable value */
+    ctx_set_arg_t  set;                  /* context variable assignment */
+} arg_t;
+
+
+typedef struct {
+    char                     *name;      /* name of the function to call */
+    arg_t                    *args;      /* arguments to pass */
+    int                       narg;      /* number of arguments */
+    mrp_list_hook_t           hook;      /* to list of statements */
+
+    int          (*script_ptr)(mrp_plugin_t *plugin, const char *name,
+                               mrp_script_env_t *env);
+    mrp_plugin_t  *plugin;
+} function_call_t;
+
+
+#endif /* __MURPHY_SIMPLE_SCRIPT_H__ */
diff --git a/src/resolver/scripting/simple/token.h b/src/resolver/scripting/simple/token.h
new file mode 100644 (file)
index 0000000..1e263ae
--- /dev/null
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_SIMPLE_SCRIPT_TOKEN_H__
+#define __MURPHY_SIMPLE_SCRIPT_TOKEN_H__
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * common token fields
+ */
+
+#define SIMPLE_TOKEN_FIELDS                                               \
+    const char *token;                   /* token string */               \
+    int         line;                    /* and on this line */           \
+    size_t      size                     /* token size */
+
+/*
+ * a generic token
+ */
+
+typedef struct {
+    SIMPLE_TOKEN_FIELDS;
+} tkn_any_t;
+
+
+/*
+ * a string token
+ */
+
+typedef struct {
+    SIMPLE_TOKEN_FIELDS;
+    char *value;
+} tkn_string_t;
+
+
+#define DEFINE_INTEGER_TOKEN(_ttype, _ctype)    \
+    typedef struct {                            \
+        SIMPLE_TOKEN_FIELDS;                    \
+        _ctype value;                           \
+    } tkn_##_ttype##_t
+
+#define DEFINE_ARRAY_TOKEN(_ttype, _cype)       \
+    typedef struct {                            \
+        SIMPLE_TOKEN_FIELDS;                    \
+        _ctype *values;                         \
+        int     nvalue;                         \
+    } tkn_##_ttype##_arr
+
+
+DEFINE_INTEGER_TOKEN(u8 , uint8_t );
+DEFINE_INTEGER_TOKEN(s8 ,  int8_t );
+DEFINE_INTEGER_TOKEN(u16, uint16_t);
+DEFINE_INTEGER_TOKEN(s16,  int16_t);
+DEFINE_INTEGER_TOKEN(u32, uint32_t);
+DEFINE_INTEGER_TOKEN(s32,  int32_t);
+DEFINE_INTEGER_TOKEN(u64, uint64_t);
+DEFINE_INTEGER_TOKEN(s64,  int64_t);
+
+typedef struct {
+    SIMPLE_TOKEN_FIELDS;
+    double value;
+} tkn_dbl_t;
+
+/*
+DEFINE_ARRAY_TOKEN(u8 , uint8_t );
+DEFINE_ARRAY_TOKEN(s8 ,  int8_t );
+DEFINE_ARRAY_TOKEN(u16, uint16_t);
+DEFINE_ARRAY_TOKEN(u16, uint16_t);
+DEFINE_ARRAY_TOKEN(s16,  int16_t);
+DEFINE_ARRAY_TOKEN(u32, uint32_t);
+DEFINE_ARRAY_TOKEN(s32,  int32_t);
+DEFINE_ARRAY_TOKEN(u64, uint64_t);
+DEFINE_ARRAY_TOKEN(s64,  int64_t);
+DEFINE_ARRAY_TOKEN(str, char *  );
+*/
+
+
+/*
+ * string array tokens
+ */
+
+typedef struct {
+    SIMPLE_TOKEN_FIELDS;
+    int    nstr;
+    char **strs;
+} tkn_strarr_t;
+
+
+/*
+ * function call arguments
+ */
+
+typedef struct {
+    SIMPLE_TOKEN_FIELDS;
+    arg_t *args;
+    int    narg;
+} tkn_args_t;
+
+
+/*
+ * a constant value
+ */
+
+typedef struct {
+    SIMPLE_TOKEN_FIELDS;
+    mrp_script_value_t value;
+} tkn_value_t;
+
+/*
+ * an expression
+ */
+
+typedef enum {
+    EXPR_UNKNOWN = 0,
+    EXPR_CONSTANT,                       /* a constant value */
+    EXPR_CONTEXT_VALUE,                  /* a context variable value */
+    EXPR_CONTEXT_SET                     /* a context variable assignment */
+} expr_type_t;
+
+
+typedef struct {
+    expr_type_t        type;             /* EXPR_CONSTANT */
+    mrp_script_value_t value;            /* constant value with a type */
+} const_expr_t;
+
+typedef struct {
+    expr_type_t  type;                   /* EXPR_CONTEXT_VALUE */
+    char        *name;                   /* context variable name */
+} ctx_val_expr_t;
+
+typedef struct {
+    expr_type_t         type;            /* EXPR_CONTEXT_SET */
+    char               *name;            /* context variable name */
+    mrp_script_value_t  value;           /* value to set */
+} ctx_set_expr_t;
+
+typedef union {
+    expr_type_t    type;                 /* expression type, EXPR_* */
+    const_expr_t   cst;                  /* constant expression */
+    ctx_val_expr_t val;                  /* context variable value */
+    ctx_set_expr_t set;                  /* context variable assignment */
+} tkn_expr_t;
+
+#ifdef __MURPHY_SIMPLE_SCRIPT_CHECK_RINGBUF__
+#    define SIMPLE_TOKEN_DONE(t)         memset((t).token, 0, (t).size)
+#else
+#    define SIMPLE_TOKEN_DONE(t)         do {} while (0)
+#endif
+
+#define SIMPLE_TOKEN_SAVE(str, size) save_token((str), (size))
+
+
+#endif /* __MURPHY_SIMPLE_SCRIPT_TOKEN_H__ */
diff --git a/src/resolver/target-sorter.c b/src/resolver/target-sorter.c
new file mode 100644 (file)
index 0000000..0d3978f
--- /dev/null
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+
+#include "scanner.h"
+#include "resolver.h"
+#include "resolver-types.h"
+#include "fact.h"
+#include "target.h"
+#include "target-sorter.h"
+
+
+/*
+ * dependency graph used to determine target update orders
+ */
+
+typedef struct {
+    mrp_resolver_t *r;                   /* resolver context */
+    int            *edges;               /* edges between nodes */
+    int             nnode;               /* number of graph nodes */
+} graph_t;
+
+
+static graph_t *build_graph(mrp_resolver_t *r);
+static int sort_graph(graph_t *g, int target_idx);
+static void free_graph(graph_t *g);
+static void dump_graph(graph_t *g, FILE *fp);
+
+
+int sort_targets(mrp_resolver_t *r)
+{
+    graph_t *g;
+    int      i, status;
+
+    g = build_graph(r);
+
+    if (g != NULL) {
+        dump_graph(g, stdout);
+
+        status = 0;
+
+        for (i = 0; i < r->ntarget; i++) {
+            target_t *t = r->targets + i;
+
+            mrp_free(t->update_targets);
+            mrp_free(t->update_facts);
+            mrp_free(t->fact_stamps);
+            mrp_free(t->directs);
+            t->update_targets = NULL;
+            t->update_facts   = NULL;
+            t->fact_stamps    = NULL;
+            t->ndirect        = 0;
+        }
+
+        for (i = 0; i < r->ntarget; i++) {
+            if (sort_graph(g, i) < 0) {
+                mrp_log_error("Failed to determine update order for "
+                              "resolver target '%s'.", r->targets[i].name);
+
+                if (errno == ELOOP)
+                    mrp_log_error("Cyclic dependency detected.");
+
+                status = -1;
+                break;
+            }
+        }
+
+        free_graph(g);
+    }
+    else
+        status = -1;
+
+    return status;
+}
+
+
+static inline int fact_id(graph_t *g, char *fact)
+{
+    int i;
+
+    for (i = 0; i < g->r->nfact; i++)
+        if (!strcmp(fact, g->r->facts[i].name))
+            return i;
+
+    return -1;
+}
+
+
+static inline int target_id(graph_t *g, char *target)
+{
+    int i;
+
+    for (i = 0; i < g->r->ntarget; i++)
+        if (!strcmp(target, g->r->targets[i].name))
+            return g->r->nfact + i;
+
+    return -1;
+}
+
+
+static inline char *node_name(graph_t *g, int id)
+{
+    if (id < g->r->nfact)
+        return g->r->facts[id].name;
+    else
+        return g->r->targets[id - g->r->nfact].name;
+}
+
+
+static inline int node_id(graph_t *g, char *name)
+{
+    if (name[0] == '$')
+        return fact_id(g, name);
+    else
+        return target_id(g, name);
+}
+
+
+static inline int *edge_markp(graph_t *g, int n1, int n2)
+{
+    static int invalid = 0;
+
+    if (n1 >= 0 && n2 >= 0)
+        return g->edges + (n1 * g->nnode) + n2;
+    else
+        return &invalid;
+}
+
+
+static inline void mark_node(graph_t *g, int node)
+{
+    *edge_markp(g, node, node) = 1;
+}
+
+
+static inline void unmark_node(graph_t *g, int node)
+{
+    *edge_markp(g, node, node) = 0;
+}
+
+
+static inline int node_present(graph_t *g, int node)
+{
+    return *edge_markp(g, node, node) == 1;
+}
+
+
+static graph_t *build_graph(mrp_resolver_t *r)
+{
+    graph_t  *g;
+    int       tid, did, i, j;
+    target_t *t;
+
+    g = mrp_allocz(sizeof(*g));
+
+    if (g != NULL) {
+        g->r     = r;
+        g->nnode = r->nfact + r->ntarget;
+        g->edges = mrp_allocz(g->nnode * g->nnode * sizeof(*g->edges));
+
+        if (g->edges == NULL)
+            goto fail;
+
+        for (i = 0; i < r->ntarget; i++) {
+            t   = r->targets + i;
+            tid = r->nfact + i;
+
+            for (j = 0; j < t->ndepend; j++) {
+                mrp_debug("adding edge: %s <- %s", t->depends[j], t->name);
+                did = node_id(g, t->depends[j]);
+
+                if (did < 0)
+                    goto fail;
+
+                *edge_markp(g, did, tid) = 1;
+            }
+        }
+    }
+
+    return g;
+
+ fail:
+    if (g != NULL) {
+        mrp_free(g->edges);
+        mrp_free(g);
+    }
+    return NULL;
+}
+
+
+static int mark_present_nodes(graph_t *g, int target_idx)
+{
+    int       tid, did, i;
+    target_t *t;
+
+    tid = g->r->nfact + target_idx;
+    t   = g->r->targets + target_idx;
+
+    if (node_present(g, tid))
+        return TRUE;
+
+    mark_node(g, tid);
+
+    for (i = 0; i < t->ndepend; i++) {
+        did = node_id(g, t->depends[i]);
+
+        if (did < 0)
+            return FALSE;
+
+        if (*t->depends[i] == '$')
+            mark_node(g, did);
+        else {
+            if (!mark_present_nodes(g, did - g->r->nfact))
+                return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+
+/*
+ * queues we use for topological sorting
+ */
+
+typedef struct {
+    int  size;                           /* max queue capacity */
+    int *items;                          /* queue item buffer */
+    int  head;                           /* push index */
+    int  tail;                           /* pop index */
+} que_t;
+
+#define EMPTY_QUE {.items = NULL }       /* initializer for empty queue */
+
+
+static int que_init(que_t *q, int size)
+{
+    mrp_free(q->items);
+
+    q->items = mrp_alloc(size * sizeof(*q->items));
+
+    if (q->items != NULL) {
+        q->size = size;
+        q->head = 0;
+        q->tail = 0;
+
+        return TRUE;
+    }
+    else
+        return FALSE;
+}
+
+
+static void que_cleanup(que_t *q)
+{
+    mrp_free(q->items);
+    q->items = NULL;
+}
+
+
+static void que_push(que_t *q, int item)
+{
+    /* we know the max size, so we don't check for overflow here */
+    q->items[q->tail++] = item;
+    q->tail            %= q->size;
+}
+
+
+static int que_pop(que_t *q, int *itemp)
+{
+    if (q->head != q->tail) {
+        *itemp   = q->items[q->head++];
+        q->head %= q->size;
+
+        return TRUE;
+    }
+    else {
+        *itemp = -1;
+        return FALSE;
+    }
+}
+
+
+static int sort_graph(graph_t *g, int target_idx)
+{
+    /*
+     * Notes:
+     *
+     *   We perform a topological sort of the dependency graph here
+     *   for our target with the given idx. We include only nodes
+     *   for facts and targets which are relevant for our target.
+     *   These are the ones which our target directly or indirectly
+     *   depends on. We use the otherwise unused diagonal (no target
+     *   can depend on itself) of our edge matrix to mark which nodes
+     *   are present in the graph. Then we use the following algorithm
+     *   to sort the subgraph of relevant nodes:
+     *
+     *       initialize que L to be empty
+     *       initialize que Q with all nodes without incoming edges
+     *       while Q is not empty
+     *           pop a node <n> from Q
+     *           push <n> to L
+     *           remove all edges that start from <n>
+     *           for all nodes <m> where we removed an incoming node
+     *               if <m> has no more incoming edges
+     *                   push <m> to Q
+     *
+     *       if there are any remaining edges
+     *           return an error about cyclic dependency
+     *       else
+     *           L is the sorted subgraph (our target is the last item in L)
+     *
+     *   The resulted sort order of our target is then used as the
+     *   dependency check/update order when the resolver is asked to
+     *   update that target.
+     */
+
+    target_t *target;
+    int       edges[g->nnode * g->nnode];
+    que_t     L = EMPTY_QUE, Q = EMPTY_QUE;
+    int       i, j, m, id, node, nedge, nfact, ntarget;
+
+    target = g->r->targets + target_idx;
+
+    /* save full graph */
+    memcpy(edges, g->edges, sizeof(edges));
+
+    if (!que_init(&L, g->nnode + 1) || !que_init(&Q, g->nnode))
+        goto fail;
+
+    /* find and mark relevant nodes in the graph */
+    mark_present_nodes(g, target_idx);
+
+    mrp_debug("-- target %s --", target->name);
+    /*dump_graph(g, stdout);*/
+
+    /* push all relevant facts, they do not depend on anything */
+    for (i = 0; i < g->r->nfact; i++) {
+        id = i;
+        if (node_present(g, id)) {
+            que_push(&Q, id);
+            unmark_node(g, id);
+        }
+    }
+
+    /* push all relevant targets that have no dependencies */
+    for (i = 0; i < g->r->ntarget; i++) {
+        id = g->r->nfact + i;
+        if (g->r->targets[i].depends == NULL && node_present(g, id)) {
+            que_push(&Q, id);
+            unmark_node(g, id);
+        }
+    }
+
+    /* try sorting the marked subgraph */
+    while (que_pop(&Q, &node)) {
+        que_push(&L, node);
+
+        mrp_debug("popped node %s", node_name(g, node));
+
+        for (m = 0; m < g->nnode; m++) {
+            if (m == node || !node_present(g, m))
+                continue;
+            *edge_markp(g, node, m) = 0;
+            nedge = 0;
+            for (j = 0; j < g->nnode; j++) {
+                if (j == m)
+                    continue;
+                if (node_present(g, j) && *edge_markp(g, j, m))
+                    nedge++;
+            }
+            if (nedge == 0) {
+                mrp_debug("node %s empty, pushing it", node_name(g, m));
+                que_push(&Q, m);
+                unmark_node(g, m);
+            }
+            else
+                mrp_debug("node %s not empty yet", node_name(g, m));
+        }
+    }
+
+    /* check if the subgraph has any remaining edges */
+    nedge = 0;
+    for (node = 0; node < g->nnode; node++) {
+        if (!node_present(g, node))
+            continue;
+        for (m = 0; m < g->nnode; m++) {
+            if (m == node || !node_present(g, m))
+                continue;
+            if (*edge_markp(g, node, m) == 1) {
+                errno = ELOOP;
+                goto fail;
+            }
+        }
+    }
+
+    mrp_debug("----- %s: graph sorted successfully -----", target->name);
+
+    for (i = 0; i < L.tail; i++)
+        mrp_debug(" %s", node_name(g, L.items[i]));
+    mrp_debug("-----");
+
+    /* save the result in the given target */
+    if (L.tail > 0) {
+        nfact   = 0;
+        ntarget = 0;
+
+        for (i = 0; i < L.tail; i++) {
+            if (L.items[i] < g->r->nfact)
+                nfact++;
+            else
+                ntarget++;
+        }
+
+        if (nfact > 0) {
+            target->update_facts = mrp_alloc_array(int, nfact + 1);
+            target->fact_stamps  = mrp_allocz_array(uint32_t, nfact);
+
+            if (target->update_facts != NULL && target->fact_stamps != NULL) {
+                for (i = 0; i < nfact; i++)
+                    target->update_facts[i] = L.items[i];
+                target->update_facts[i] = -1;
+            }
+            else
+                goto fail;
+        }
+
+        if (ntarget > 0) {
+            target->update_targets = mrp_alloc_array(int, ntarget + 1);
+            if (target->update_targets != NULL) {
+                for (i = 0; i < ntarget; i++)
+                    target->update_targets[i] = L.items[nfact+i] - g->r->nfact;
+                target->update_targets[i] = -1;
+            }
+            else
+                goto fail;
+        }
+
+        target->ndirect = 0;
+        target->directs = mrp_allocz_array(int, target->ndepend);
+
+        if (target->ndepend == 0 || target->directs != NULL) {
+            fact_t   *f;
+            target_t *t;
+
+            for (i = 0; i < target->ndepend; i++) {
+                if (*target->depends[i] != '$')
+                    continue;
+
+                f = lookup_fact(g->r, target->depends[i]);
+
+                if (f != NULL)
+                    target->directs[target->ndirect++] = f - g->r->facts;
+                else
+                    target->directs[target->ndirect++] = -1;
+            }
+
+            for (i = 0; i < target->ndepend; i++) {
+                if (*target->depends[i] == '$')
+                    continue;
+
+                t = lookup_target(g->r, target->depends[i]);
+
+                if (t != NULL)
+                    target->directs[target->ndirect++] = g->r->nfact +
+                        t - g->r->targets;
+                else
+                    target->directs[target->ndirect++] = -1;
+            }
+        }
+        else
+            goto fail;
+    }
+
+    que_cleanup(&L);
+    que_cleanup(&Q);
+
+    /* restore the original full graph */
+    memcpy(g->edges, edges, sizeof(edges));
+
+    return 0;
+
+ fail:
+    que_cleanup(&L);
+    que_cleanup(&Q);
+
+    memcpy(g->edges, edges, sizeof(edges));
+
+    return -1;
+}
+
+
+static void free_graph(graph_t *g)
+{
+    if (g != NULL) {
+        mrp_free(g->edges);
+        mrp_free(g);
+    }
+}
+
+
+static void dump_graph(graph_t *g, FILE *fp)
+{
+    int i, j;
+
+    fprintf(fp, "Graph edges:\n");
+
+    fprintf(fp, "  %20.20s ", "");
+    for (i = 0; i < g->nnode; i++)
+        fprintf(fp, " %d", i % 10);
+    fprintf(fp, "\n");
+
+    for (i = 0; i < g->nnode; i++) {
+        fprintf(fp, "  %20.20s: ", node_name(g, i));
+        for (j = 0; j < g->nnode; j++)
+            fprintf(fp, "%d ", *edge_markp(g, i, j));
+        fprintf(fp, "\n");
+    }
+}
diff --git a/src/resolver/target-sorter.h b/src/resolver/target-sorter.h
new file mode 100644 (file)
index 0000000..a18b625
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_TARGET_SORTER_H__
+#define __MURPHY_RESOLVER_TARGET_SORTER_H__
+
+#include "resolver-types.h"
+#include "resolver.h"
+
+int sort_targets(mrp_resolver_t *r);
+
+#endif /* __MURPHY_RESOLVER_TARGET_SORTER_H__ */
diff --git a/src/resolver/target.c b/src/resolver/target.c
new file mode 100644 (file)
index 0000000..7099d38
--- /dev/null
@@ -0,0 +1,761 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdarg.h>
+#include <errno.h>
+#include <alloca.h>
+
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/list.h>
+
+#include <murphy/core/scripting.h>
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "fact.h"
+#include "events.h"
+#include "target-sorter.h"
+#include "target.h"
+
+
+
+int create_targets(mrp_resolver_t *r, yy_res_parser_t *parser)
+{
+    mrp_list_hook_t *lp, *ln;
+    yy_res_target_t *pt;
+    int              auto_update;
+
+    auto_update = -1;
+
+    mrp_list_foreach(&parser->targets, lp, ln) {
+        pt = mrp_list_entry(lp, typeof(*pt), hook);
+
+        if (create_target(r, pt->name, (const char **)pt->depends, pt->ndepend,
+                          pt->script_type, pt->script_source) == NULL)
+
+            return -1;
+
+        if (parser->auto_update != NULL) {
+            if (!strcmp(parser->auto_update, pt->name))
+                auto_update = r->ntarget - 1;
+        }
+    }
+
+    if (auto_update >= 0)
+        r->auto_update = r->targets + auto_update;
+    else {
+        if (parser->auto_update != NULL) {
+            mrp_log_error("Auto-update target '%s' does not exist.",
+                          parser->auto_update);
+            errno = ENOENT;
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+static void purge_target(target_t *t)
+{
+    int i;
+
+    mrp_free(t->name);
+    mrp_free(t->update_facts);
+    mrp_free(t->update_targets);
+    mrp_free(t->fact_stamps);
+    mrp_free(t->directs);
+
+    for (i = 0; i < t->ndepend; i++)
+        mrp_free(t->depends[i]);
+    mrp_free(t->depends);
+
+    mrp_destroy_script(t->script);
+}
+
+
+void destroy_targets(mrp_resolver_t *r)
+{
+    target_t *t;
+    int       i;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++)
+        purge_target(t);
+
+    mrp_free(r->targets);
+
+    if (r->auto_scheduled != NULL) {
+        mrp_del_deferred(r->auto_scheduled);
+        r->auto_scheduled = NULL;
+    }
+}
+
+
+target_t *create_target(mrp_resolver_t *r, const char *target,
+                        const char **depends, int ndepend,
+                        const char *script_type, const char *script_source)
+{
+    target_t *t;
+    size_t    old_size, new_size;
+    int       i, j, found, nduplicate;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+        if (!strcmp(t->name, target)) {
+            errno = EEXIST;
+            return NULL;
+        }
+    }
+
+    old_size = sizeof(*r->targets) *  r->ntarget;
+    new_size = sizeof(*r->targets) * (r->ntarget + 1);
+
+    if (!mrp_reallocz(r->targets, old_size, new_size))
+        return NULL;
+
+    t       = r->targets + r->ntarget++;
+    t->name = mrp_strdup(target);
+
+    if (t->name == NULL)
+        goto undo_and_fail;
+
+    if (depends != NULL) {
+        t->depends = mrp_allocz_array(char *, ndepend);
+
+        if (t->depends != NULL) {
+            nduplicate = 0;
+            for (i = 0; i < ndepend; i++) {
+                found = FALSE;
+                for (j = 0; j < i; j++)
+                    if (!strcmp(depends[i], depends[j]))
+                        found = TRUE;
+                if (!found) {
+                    t->depends[i - nduplicate] = mrp_strdup(depends[i]);
+
+                    if (t->depends[i] == NULL)
+                        goto undo_and_fail;
+                }
+                else
+                    nduplicate++;
+            }
+
+            t->ndepend = ndepend - nduplicate;
+
+            if (nduplicate > 0) {
+                mrp_reallocz(t->depends, ndepend, t->ndepend);
+                mrp_log_warning("Filtered out %d duplicate%s dependencies "
+                                "from target '%s'.", nduplicate,
+                                nduplicate == 1 ? "" : "s", t->name);
+            }
+        }
+        else
+            goto undo_and_fail;
+    }
+
+    for (i = 0; i < t->ndepend; i++) {
+        if (*t->depends[i] == '$')
+            if (!create_fact(r, t->depends[i]))
+                goto undo_and_fail;
+    }
+
+    if (script_source != NULL) {
+        t->script = mrp_create_script(script_type, script_source);
+
+        if (t->script == NULL) {
+            if (errno == ENOENT)
+                mrp_log_error("Unsupported script type '%s' used in "
+                              "target '%s'.", script_type, t->name);
+            else
+                mrp_log_error("Failed to set up script for target '%s'.",
+                              t->name);
+
+            goto undo_and_fail;
+        }
+    }
+
+    return t;
+
+
+ undo_and_fail:
+    purge_target(t);
+    mrp_realloc(r->targets, old_size);
+    r->ntarget--;
+
+    return NULL;
+}
+
+
+int generate_autoupdate_target(mrp_resolver_t *r, const char *name)
+{
+    const char **depends;
+    int          ndepend, i;
+    target_t    *t, *at;
+
+    if (r->auto_update != NULL)
+        return FALSE;
+
+    mrp_debug("constructing autoupdate target '%s'...", name);
+
+    if (r->ntarget > 0) {
+        depends = alloca(r->ntarget * sizeof(depends[0]));
+        ndepend = 0;
+
+        if (sort_targets(r) != 0) {
+            mrp_debug("failed to sort dependency graph");
+            return FALSE;
+        }
+
+        for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+            if (t->update_facts != NULL && t->update_facts[0] >= 0) {
+                mrp_debug("  including target '%s' (%s)", t->name,
+                          fact_name(r, t->update_facts[0]));
+                depends[ndepend] = t->name;
+                ndepend++;
+            }
+            else
+                mrp_debug("  excluding target '%s'", t->name);
+        }
+    }
+    else {
+        depends = NULL;
+        ndepend = 0;
+    }
+
+    at = create_target(r, name, depends, ndepend, NULL, NULL);
+
+    if (at != NULL) {
+        r->auto_update = at;
+
+        return (sort_targets(r) == 0);
+    }
+    else
+        return FALSE;
+}
+
+
+int compile_target_scripts(mrp_resolver_t *r)
+{
+    target_t *t;
+    int       i;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+        if (!t->prepared) {
+            if (mrp_compile_script(t->script) < 0) {
+                mrp_log_error("Failed to compile script for target '%s'.",
+                              t->name);
+                return -1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+int prepare_target_scripts(mrp_resolver_t *r)
+{
+    target_t *t;
+    int       i;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+        if (!t->prepared) {
+            if (mrp_prepare_script(t->script) == 0)
+                t->prepared = TRUE;
+            else {
+                mrp_log_error("Failed to prepare script for target '%s'.",
+                              t->name);
+                return -1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+static int older_than_facts(mrp_resolver_t *r, target_t *t)
+{
+    int i, id;
+
+    /*
+     * If a target does not depend directly or indirectly on any
+     * facts, it always needs to be updated and is considered to
+     * be older than its (nonexistent) fact dependencies even if
+     * this seems a bit unintuitive at first. If there are fact
+     * dependencies the target is considered older if any of the
+     * facts have a newer stamp than the target.
+     */
+
+    if (t->update_facts == NULL)
+        return TRUE;
+    else {
+#ifdef CHECK_TRANSITIVE_CLOSURE_OF_FACTS
+        for (i = 0; (id = t->update_facts[i]) >= 0; i++) {
+            if (fact_stamp(r, id) > t->fact_stamps[i])
+                return TRUE;
+        }
+#else
+        for (i = 0; i < t->ndirect; i++) {
+            id = t->directs[i];
+
+            if (id < r->nfact) {
+                if (fact_stamp(r, id) > t->fact_stamps[i])
+                    return TRUE;
+            }
+        }
+#endif
+    }
+
+    return FALSE;
+}
+
+
+static int older_than_targets(mrp_resolver_t *r, target_t *t)
+{
+    int       i, id;
+    target_t *dep;
+
+    /*
+     * Although the target itself is always the last item in its own
+     * sorted target dependencies (as the list is the topologically
+     * sorted dependency graph) we don't special case this out here as
+     * a target cannot be newer than itself by definition.
+     */
+
+    for (i = 0; (id = t->update_targets[i]) >= 0; i++) {
+        dep = r->targets + id;
+
+        if (dep->stamp > t->stamp)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+
+static void save_fact_stamps(mrp_resolver_t *r, target_t *t, uint32_t *buf)
+{
+    int id, idx, i;
+
+    if (t->update_facts != NULL) {
+        id  = t - r->targets;
+        idx = id * r->nfact;
+
+        for (i = 0; (id = t->update_facts[i]) >= 0; i++, idx++)
+            buf[idx] = t->fact_stamps[i];
+    }
+}
+
+
+static void restore_fact_stamps(mrp_resolver_t *r, target_t *t, uint32_t *buf)
+{
+    int id, idx, i;
+
+    if (t->update_facts != NULL) {
+        id  = t - r->targets;
+        idx = id * r->nfact;
+
+        for (i = 0; (id = t->update_facts[i]) >= 0; i++, idx++)
+            t->fact_stamps[i] = buf[idx];
+    }
+}
+
+
+static void save_target_stamps(mrp_resolver_t *r, target_t *t, uint32_t *buf)
+{
+    target_t *dep;
+    int       i, id;
+
+    for (i = 0; (id = t->update_targets[i]) >= 0; i++) {
+        dep = r->targets + id;
+        save_fact_stamps(r, dep, buf);
+    }
+}
+
+
+static void restore_target_stamps(mrp_resolver_t *r, target_t *t, uint32_t *buf)
+{
+    target_t *dep;
+    int       i, id;
+
+    for (i = 0; (id = t->update_targets[i]) >= 0; i++) {
+        dep = r->targets + id;
+        restore_fact_stamps(r, dep, buf);
+    }
+}
+
+
+static void update_target_stamps(mrp_resolver_t *r, target_t *t)
+{
+    int i, id;
+
+    if (t->update_facts != NULL)
+        for (i = 0; (id = t->update_facts[i]) >= 0; i++)
+            t->fact_stamps[i] = fact_stamp(r, id);
+
+    t->stamp = r->stamp;
+}
+
+
+static int update_target(mrp_resolver_t *r, target_t *t)
+{
+    mqi_handle_t  tx;
+    target_t     *dep;
+    uint32_t      stamps[r->ntarget * r->nfact];
+    int           i, id, status, needs_update, level;
+
+    tx = start_transaction(r);
+
+    if (tx == MQI_HANDLE_INVALID) {
+        if (errno != 0)
+            return -errno;
+        else
+            return -EINVAL;
+    }
+
+    r->stamp = r->stamp + 1;
+
+    level = r->level++;
+    emit_resolver_event(r, RESOLVER_UPDATE_STARTED, t->name, level);
+
+    save_target_stamps(r, t, stamps);
+
+    status       = TRUE;
+    needs_update = older_than_facts(r, t);
+
+    for (i = 0; (id = t->update_targets[i]) >= 0; i++) {
+        dep = r->targets + id;
+
+        if (dep == t)
+            break;
+
+        /*                              hmm... is this really needed? */
+        if (older_than_facts(r, dep) || older_than_targets(r, dep)) {
+            needs_update = TRUE;
+            status       = mrp_execute_script(dep->script, r->ctbl);
+
+            if (status <= 0)
+                break;
+            else
+                update_target_stamps(r, dep);
+        }
+    }
+
+    if (needs_update && status > 0) {
+        status = mrp_execute_script(t->script, r->ctbl);
+
+        if (status > 0)
+            update_target_stamps(r, t);
+    }
+
+    if (status <= 0) {
+        rollback_transaction(r, tx);
+        restore_target_stamps(r, t, stamps);
+        emit_resolver_event(r, RESOLVER_UPDATE_FAILED, t->name, level);
+    }
+    else {
+        if (!commit_transaction(r, tx)) {
+            restore_target_stamps(r, t, stamps);
+            if (errno != 0)
+                status = -errno;
+            else
+                status = -EINVAL;
+        }
+    }
+
+    if (status <= 0)
+        emit_resolver_event(r, RESOLVER_UPDATE_FAILED, t->name, level);
+    else
+        emit_resolver_event(r, RESOLVER_UPDATE_DONE  , t->name, level);
+
+    r->level--;
+
+    return status;
+}
+
+
+target_t *lookup_target(mrp_resolver_t *r, const char *name)
+{
+   target_t *t;
+    int       i;
+
+    for (i = 0, t = r->targets; i < r->ntarget; i++, t++) {
+        if (!strcmp(t->name, name))
+            return t;
+    }
+
+    return NULL;
+}
+
+
+int update_target_by_name(mrp_resolver_t *r, const char *name)
+{
+    target_t *t = lookup_target(r, name);
+
+    if (t != NULL)
+        return update_target(r, t);
+    else
+        return FALSE;
+}
+
+
+int update_target_by_id(mrp_resolver_t *r, int id)
+{
+    if (id < r->ntarget)
+        return update_target(r, r->targets + id);
+    else
+        return FALSE;
+}
+
+
+static int autoupdate_target(mrp_resolver_t *r)
+{
+    if (r->auto_update != NULL)
+        return mrp_resolver_update_targetl(r, r->auto_update->name, NULL);
+    else
+        return TRUE;
+}
+
+
+static void autoupdate_cb(mrp_deferred_t *d, void *user_data)
+{
+    mrp_resolver_t *r = (mrp_resolver_t *)user_data;
+
+    mrp_debug("running scheduled target autoupdate");
+    mrp_disable_deferred(d);
+    autoupdate_target(r);
+}
+
+
+int schedule_target_autoupdate(mrp_resolver_t *r)
+{
+    if (r->auto_update != NULL) {
+        if (r->ctx != NULL && r->auto_scheduled == NULL)
+            r->auto_scheduled = mrp_add_deferred(r->ctx->ml, autoupdate_cb, r);
+
+        if (r->auto_scheduled != NULL)
+            mrp_enable_deferred(r->auto_scheduled);
+        else
+            return FALSE;
+
+        mrp_debug("scheduled target autoupdate (%s)", r->auto_update->name);
+    }
+
+    return TRUE;
+}
+
+
+void dump_targets(mrp_resolver_t *r, FILE *fp)
+{
+    int       i, j, idx;
+    target_t *t;
+
+    for (i = 0; i < r->ntarget; i++) {
+        t = r->targets + i;
+        fprintf(fp, "#%d: %s (@%u)\n", i, t->name, t->stamp);
+
+        fprintf(fp, "  dependencies:");
+        if (t->depends != NULL) {
+            for (j = 0; j < t->ndepend; j++)
+                fprintf(fp, " %s", t->depends[j]);
+            fprintf(fp, "\n");
+
+            fprintf(fp, "  facts to check:");
+            if (t->update_facts != NULL) {
+                for (j = 0; (idx = t->update_facts[j]) >= 0; j++)
+                    fprintf(fp, " %s (@%u)", r->facts[idx].name,
+                            t->fact_stamps[j]);
+                fprintf(fp, "\n");
+            }
+            else
+                fprintf(fp, "<none>\n");
+
+            fprintf(fp, "  target update order:");
+            if (t->update_targets != NULL) {
+                for (j = 0; (idx = t->update_targets[j]) >= 0; j++)
+                    fprintf(fp, " %s (@%u)", r->targets[idx].name,
+                            r->targets[idx].stamp);
+                fprintf(fp, "\n");
+            }
+            else
+                fprintf(fp, "<none>\n");
+
+            fprintf(fp, "  direct dependencies:");
+            if (t->ndirect > 0) {
+                for (j = 0; j < t->ndirect; j++) {
+                    idx = t->directs[j];
+                    if (idx < r->nfact)
+                        fprintf(fp, " %s", r->facts[idx].name);
+                    else
+                        fprintf(fp, " %s", r->targets[idx-r->nfact].name);
+                }
+                fprintf(fp, "\n");
+            }
+            else
+                fprintf(fp, "<none>\n");
+        }
+        else
+            fprintf(fp, " <none>\n");
+
+        if (t->script != NULL) {
+            if (t->script->source != NULL) {
+                fprintf(fp, "  update script (%s):\n",
+                        t->script->interpreter->name);
+                fprintf(fp, "%s", t->script->source);
+                fprintf(fp, "  end script\n");
+            }
+            else if (t->script->data != NULL) {
+                fprintf(fp, "  precompiled update (%s):\n",
+                        t->script->interpreter->name);
+                fprintf(fp, "    %p\n", t->script->data);
+                fprintf(fp, "  end script\n");
+            }
+        }
+        else
+            fprintf(fp, "  no update script\n");
+    }
+}
+
+typedef enum {
+    DOT_NODE_TYPE_FACT,
+    DOT_NODE_TYPE_TABLE,
+    DOT_NODE_TYPE_SINK,
+    DOT_NODE_TYPE_SELECT,
+    DOT_NODE_TYPE_OTHER,
+} dot_node_type_t;
+
+
+static dot_node_type_t dot_node_type(char *name)
+{
+    if (!name)
+        return DOT_NODE_TYPE_OTHER;
+
+    if (name[0] == '$')
+        return DOT_NODE_TYPE_FACT;
+
+    if (strncmp(name, "_table_", 7) == 0)
+        return DOT_NODE_TYPE_TABLE;
+
+    if (strncmp(name, "_sink_", 6) == 0)
+        return DOT_NODE_TYPE_SINK;
+
+    if (strncmp(name, "_select_", 8) == 0)
+        return DOT_NODE_TYPE_SELECT;
+
+    return DOT_NODE_TYPE_OTHER;
+}
+
+
+static char *dot_fix(char *name)
+{
+    dot_node_type_t t = dot_node_type(name);
+
+    switch(t) {
+        case DOT_NODE_TYPE_FACT:
+            /* remove illegal characters */
+            return &name[1];
+
+        case DOT_NODE_TYPE_TABLE:
+            return &name[7];
+
+        case DOT_NODE_TYPE_SINK:
+            return &name[6];
+
+        case DOT_NODE_TYPE_SELECT:
+            return &name[8];
+
+        default:
+            break;
+    }
+    return name;
+}
+
+
+static char *dot_get_shape(dot_node_type_t t)
+{
+    switch(t) {
+        case DOT_NODE_TYPE_FACT:
+        case DOT_NODE_TYPE_TABLE:
+            return "box";
+
+        case DOT_NODE_TYPE_SINK:
+            return "trapezium";
+
+        case DOT_NODE_TYPE_SELECT:
+            return "diamond";
+
+        default:
+            break;
+    }
+    return "ellipse";
+}
+
+
+void mrp_resolver_dump_dot_graph(mrp_resolver_t *r, FILE *fp)
+{
+    int i, j;
+    target_t *t;
+
+    fprintf(fp, "digraph decision_graph {\n");
+
+    /* vertexes */
+    for (i = 0; i < r->ntarget; i++) {
+        dot_node_type_t i_type;
+        char *name;
+
+        t = r->targets + i;
+        name = dot_fix(t->name);
+        if (strcmp(name, "autoupdate") == 0)
+            continue;
+
+        i_type = dot_node_type(t->name);
+        fprintf(fp, "    %s [shape=%s];\n", name, dot_get_shape(i_type));
+    }
+
+    fprintf(fp, "\n");
+
+    /* edges */
+    for (i = 0; i < r->ntarget; i++) {
+        t = r->targets + i;
+        char *i_name = dot_fix(t->name);
+
+        if (strcmp(i_name, "autoupdate") == 0)
+            continue;
+
+        if (t->depends != NULL) {
+            for (j = 0; j < t->ndepend; j++) {
+                char *j_name = dot_fix(t->depends[j]);
+
+                fprintf(fp, "    %s -> %s;\n", i_name, j_name);
+            }
+        }
+    }
+
+    fprintf(fp, "}\n");
+}
diff --git a/src/resolver/target.h b/src/resolver/target.h
new file mode 100644 (file)
index 0000000..aa894a7
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_TARGET_H__
+#define __MURPHY_RESOLVER_TARGET_H__
+
+#include "resolver-types.h"
+#include "resolver.h"
+#include "parser-api.h"
+
+int create_targets(mrp_resolver_t *r, yy_res_parser_t *parser);
+void destroy_targets(mrp_resolver_t *r);
+target_t *create_target(mrp_resolver_t *r, const char *target,
+                        const char **depends, int ndepend,
+                        const char *script_type, const char *script_source);
+int generate_autoupdate_target(mrp_resolver_t *r, const char *name);
+int compile_target_scripts(mrp_resolver_t *r);
+int prepare_target_scripts(mrp_resolver_t *r);
+
+int update_target_by_name(mrp_resolver_t *r, const char *name);
+int update_target_by_id(mrp_resolver_t *r, int id);
+int schedule_target_autoupdate(mrp_resolver_t *r);
+
+target_t *lookup_target(mrp_resolver_t *s, const char *name);
+void dump_targets(mrp_resolver_t *r, FILE *fp);
+
+/** Dump the resolver dependency graph in DOT format. */
+void mrp_resolver_dump_dot_graph(mrp_resolver_t *r, FILE *fp);
+
+
+#endif /* __MURPHY_RESOLVER_TARGET_H__ */
diff --git a/src/resolver/test-input b/src/resolver/test-input
new file mode 100644 (file)
index 0000000..ee320a8
--- /dev/null
@@ -0,0 +1,22 @@
+#auto-update-target all
+
+target all
+    depends on video_route audio_route \
+               audio_volume audio_cork audio_mute \
+               $vibra $backlight
+# signal_enforcement_points(&target='all', 'audio', 'video', 1, -2, +3)
+# foobar(&just='a test', +1, -2, 3.141, -1U8, 0xffffffffffU64)
+# hex(-0xf)
+    update script
+        echo('Running actions for target "all"...')
+        echo('foo', 'bar', 'foobar...', 1, 2, 3.141, -9.81)
+    end script
+
+include "test-input-video"
+include "test-input-audio"
+
+target luatest
+    depends on $audio_playback_owner
+    update script (lua)
+        print("Hello from a resolver update Lua scriptlet...")
+    end script
\ No newline at end of file
diff --git a/src/resolver/test-input-audio b/src/resolver/test-input-audio
new file mode 100644 (file)
index 0000000..56a7d56
--- /dev/null
@@ -0,0 +1,24 @@
+target audio_route
+    depends on $audio_device_selectable $audio_output_configuration \
+               $resource_owner
+    update script
+        echo('Running audio_route actions...', 3, 2, 1, 0)
+    end script
+
+target audio_volume
+    depends on $resource_owner $resource_set
+    update script
+        echo('Running actions for target "audio_volume"...')
+    end script
+
+target audio_cork
+    depends on $resource_owner
+    update script
+        echo('Running actions for target "audio_cork"...')
+    end script
+
+target audio_mute
+    depends on $mute
+    update script
+        echo('Running actions for target "audio_mute"...')
+    end script
diff --git a/src/resolver/test-input-video b/src/resolver/test-input-video
new file mode 100644 (file)
index 0000000..9bc3027
--- /dev/null
@@ -0,0 +1,5 @@
+target video_route
+    depends on $video_device_selectable $gconf
+    update script
+        echo('Running actions for target "video_route"...')
+    end script
diff --git a/src/resolver/tests/Makefile.am b/src/resolver/tests/Makefile.am
new file mode 100644 (file)
index 0000000..583ad81
--- /dev/null
@@ -0,0 +1,10 @@
+noinst_PROGRAMS = parser-test
+AM_CFLAGS       = $(WARNING_CFLAGS) -I$(top_builddir) $(JSON_CFLAGS)
+
+# parser test
+parser_test_SOURCES = parser-test.c
+parser_test_CFLAGS  = $(AM_CFLAGS)
+parser_test_LDADD   = ../../libmurphy-resolver.la   \
+                      ../../murphy-db/mqi/libmqi.la \
+                      ../../murphy-db/mdb/libmdb.la \
+                      ../../libmurphy-common.la
diff --git a/src/resolver/tests/parser-test.c b/src/resolver/tests/parser-test.c
new file mode 100644 (file)
index 0000000..218399a
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <murphy/common.h>
+#include <murphy/core/method.h>
+#include <murphy/resolver/resolver.h>
+
+
+typedef struct {
+    const char     *file;
+    mrp_resolver_t *r;
+    int             log_mask;
+    const char     *log_target;
+    int             debug;
+} context_t;
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options] [transport-address]\n\n"
+           "The possible options are:\n"
+           "  -f, --file                     input file to user\n"
+           "  -t, --log-target=TARGET        log target to use\n"
+           "      TARGET is one of stderr,stdout,syslog, or a logfile path\n"
+           "  -l, --log-level=LEVELS         logging level to use\n"
+           "      LEVELS is a comma separated list of info, error and warning\n"
+           "  -v, --verbose                  increase logging verbosity\n"
+           "  -d, --debug                    enable given debug confguration\n"
+           "  -D, --list-debug               list known debug sites\n"
+           "  -h, --help                     show help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void config_set_defaults(context_t *ctx)
+{
+    mrp_clear(ctx);
+    ctx->file = "test-input";
+    ctx->log_mask   = MRP_LOG_UPTO(MRP_LOG_DEBUG);
+    ctx->log_target = MRP_LOG_TO_STDERR;
+}
+
+
+int parse_cmdline(context_t *ctx, int argc, char **argv)
+{
+#   define OPTIONS "f:l:t:d:vh"
+    struct option options[] = {
+        { "file"      , required_argument, NULL, 'f' },
+        { "log-level" , required_argument, NULL, 'l' },
+        { "log-target", required_argument, NULL, 't' },
+        { "verbose"   , optional_argument, NULL, 'v' },
+        { "debug"     , required_argument, NULL, 'd' },
+        { "help"      , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int  opt;
+
+    config_set_defaults(ctx);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'f':
+            ctx->file = optarg;
+            break;
+
+        case 'v':
+            ctx->log_mask <<= 1;
+            ctx->log_mask  |= 1;
+            break;
+
+        case 'l':
+            ctx->log_mask = mrp_log_parse_levels(optarg);
+            if (ctx->log_mask < 0)
+                print_usage(argv[0], EINVAL, "invalid log level '%s'", optarg);
+            break;
+
+        case 't':
+            ctx->log_target = mrp_log_parse_target(optarg);
+            if (!ctx->log_target)
+                print_usage(argv[0], EINVAL, "invalid log target '%s'", optarg);
+            break;
+
+        case 'd':
+            ctx->debug = TRUE;
+            mrp_debug_set_config(optarg);
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%c'", opt);
+        }
+    }
+
+    return TRUE;
+}
+
+
+int main(int argc, char *argv[])
+{
+    context_t  c;
+    int        i;
+    char      *target;
+
+    if (!parse_cmdline(&c, argc, argv))
+        exit(1);
+
+    mrp_log_set_mask(c.log_mask);
+    mrp_log_set_target(c.log_target);
+
+    if (c.debug)
+        mrp_debug_enable(TRUE);
+
+    c.r = mrp_resolver_parse(NULL, NULL, c.file);
+
+    if (c.r == NULL)
+        mrp_log_error("Failed to parse input file '%s'.", c.file);
+    else {
+        mrp_log_info("Input file '%s' parsed successfully.", c.file);
+        mrp_resolver_dump_targets(c.r, stdout);
+        mrp_resolver_dump_facts(c.r, stdout);
+
+        mrp_resolver_declare_variable(c.r, "var1", MRP_SCRIPT_TYPE_STRING);
+        mrp_resolver_declare_variable(c.r, "var2", MRP_SCRIPT_TYPE_STRING);
+        mrp_resolver_declare_variable(c.r, "var3", MRP_SCRIPT_TYPE_BOOL);
+        mrp_resolver_declare_variable(c.r, "var4", MRP_SCRIPT_TYPE_SINT32);
+        mrp_resolver_declare_variable(c.r, "var5", MRP_SCRIPT_TYPE_UINT32);
+        mrp_resolver_declare_variable(c.r, "var6", MRP_SCRIPT_TYPE_UNKNOWN);
+
+        for (i = optind; i < argc; i++) {
+            target = argv[i];
+            printf("========== Target %s ==========\n", target);
+
+            if (mrp_resolver_update_targetl(c.r, argv[i],
+                                            "var1", MRP_SCRIPT_STRING("foo"),
+                                            "var2", MRP_SCRIPT_STRING("bar"),
+                                            "var3", MRP_SCRIPT_BOOL(TRUE),
+                                            "var4", MRP_SCRIPT_SINT32(-1),
+                                            "var5", MRP_SCRIPT_UINT32(123),
+                                            "var6", MRP_SCRIPT_DOUBLE(3.141),
+                                            NULL) > 0)
+                printf("Resolved OK.\n");
+            else
+                printf("Resolving FAILED.\n");
+
+#if 0
+            {
+                int nvariable = 6;
+                const char *variables[nvariable];
+                mrp_script_value_t values[nvariable];
+
+                variables[0] = "var1";
+                values[0]    = MRP_SCRIPT_VALUE_STRING("foo");
+                variables[1] = "var2";
+                values[1]    = MRP_SCRIPT_VALUE_STRING("bar");
+                variables[2] = "var3";
+                values[2]    = MRP_SCRIPT_VALUE_BOOL(TRUE);
+                variables[3] = "var4";
+                values[3]    = MRP_SCRIPT_VALUE_SINT32(-3);
+                variables[4] = "var5";
+                values[4]    = MRP_SCRIPT_VALUE_UINT32(369);
+                variables[5] = "var6";
+                values[5]    = MRP_SCRIPT_VALUE_SINT32(-3141);
+
+            if (mrp_resolver_update_targetv(c.r, argv[i], variables, values,
+                                            nvariable) > 0)
+                printf("Resolved OK.\n");
+            else
+                printf("Resolving FAILED.\n");
+            }
+#endif
+        }
+    }
+
+    mrp_resolver_destroy(c.r);
+    c.r = NULL;
+
+    return 0;
+}
diff --git a/src/resolver/tests/test b/src/resolver/tests/test
new file mode 100644 (file)
index 0000000..8e7da32
--- /dev/null
@@ -0,0 +1,19 @@
+target all
+    depends on target1 target2
+
+target target1
+    depends on target3
+
+target target2
+    depends on target4 target5
+
+target target3
+    depends on $fact1
+
+target target4
+    depends on $fact2 $fact3
+
+target target5
+    depends on target6
+
+target target6
diff --git a/src/resolver/token.h b/src/resolver/token.h
new file mode 100644 (file)
index 0000000..7635ad1
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOLVER_TOKEN_H__
+#define __MURPHY_RESOLVER_TOKEN_H__
+
+#include <stdint.h>
+#include <stdlib.h>
+
+/*
+ * common token fields
+ */
+
+#define RESOLVER_TOKEN_FIELDS                                             \
+    const char *token;                   /* token string */               \
+    const char *source;                  /* encountered in this source */ \
+    int         line;                    /* and on this line */           \
+    size_t      size                     /* token size */
+
+/*
+ * a generic token
+ */
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+} tkn_any_t;
+
+
+/*
+ * a string token
+ */
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+    char *value;
+} tkn_string_t;
+
+
+/*
+ * signed and unsigned 16-bit integer tokens
+ */
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+    int16_t value;
+} tkn_s16_t;
+
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+    uint16_t value;
+} tkn_u16_t;
+
+
+/*
+ * signed and unsigned 32-bit integer tokens
+ */
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+    int32_t value;
+} tkn_s32_t;
+
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+    uint32_t value;
+} tkn_u32_t;
+
+
+typedef struct {
+    RESOLVER_TOKEN_FIELDS;
+    int    nstr;
+    char **strs;
+} tkn_strarr_t;
+
+#ifdef __MURPHY_RESOLVER_CHECK_RINGBUF__
+#    define RESOLVER_TOKEN_DONE(t)         memset((t).token, 0, (t).size)
+#else
+#    define RESOLVER_TOKEN_DONE(t)         do {} while (0)
+#endif
+
+#define RESOLVER_TOKEN_SAVE(str, size) save_token((str), (size))
+
+
+#endif /* __MURPHY_RESOLVER_TOKEN_H__ */
diff --git a/src/resource/Makefile b/src/resource/Makefile
new file mode 100644 (file)
index 0000000..2c0a593
--- /dev/null
@@ -0,0 +1,7 @@
+ifneq ($(strip $(MAKECMDGOALS)),)
+%:
+       $(MAKE) -C .. $(MAKECMDGOALS)
+else
+all:
+       $(MAKE) -C .. all
+endif
diff --git a/src/resource/application-class.c b/src/resource/application-class.c
new file mode 100644 (file)
index 0000000..ec9a9c3
--- /dev/null
@@ -0,0 +1,558 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/log.h>
+
+#include <murphy/resource/manager-api.h>
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/config-api.h>
+
+#include <murphy-db/mqi.h>
+
+#include "application-class.h"
+#include "resource-set.h"
+#include "resource-owner.h"
+#include "zone.h"
+
+#define CLASS_MAX        64
+#define NAME_LENGTH      24
+
+#define CLASS_NAME_IDX   0
+#define PRIORITY_IDX     1
+
+
+/*
+ * sorting key bit layout
+ *
+ * +---------+----+----+--------+
+ * | 31 - 29 | 28 | 27 | 26 - 0 |
+ * +---------+----+----+--------+
+ *      |      |    |       |
+ *      |      |    |       +---- 0x07ffffff stamp of the last request
+ *      |      |    +------------ 0x08000000 state (set if acquiring)
+ *      |      +----------------- 0x10000000 usage (set if shared)
+ *      +------------------------ 0xe0000000 priority (0-7)
+ */
+#define MASK(b)   (((uint32_t)1 << (b)) - (uint32_t)1)
+
+#define STAMP_SHIFT     0
+#define STATE_SHIFT     (STAMP_SHIFT + MRP_KEY_STAMP_BITS)
+#define USAGE_SHIFT     (STATE_SHIFT + MRP_KEY_STATE_BITS)
+#define PRIORITY_SHIFT  (USAGE_SHIFT + MRP_KEY_USAGE_BITS)
+
+#define STAMP_MASK      MASK(MRP_KEY_STAMP_BITS)
+#define STATE_MASK      MASK(MRP_KEY_STATE_BITS)
+#define USAGE_MASK      MASK(MRP_KEY_USAGE_BITS)
+#define PRIORITY_MASK   MASK(MRP_KEY_PRIORITY_BITS)
+
+#define STAMP_KEY(p)    (((uint32_t)(p) & STAMP_MASK)    << STAMP_SHIFT)
+#define STATE_KEY(p)    (((uint32_t)(p) & STATE_MASK)    << STATE_SHIFT)
+#define USAGE_KEY(p)    (((uint32_t)(p) & USAGE_MASK)    << USAGE_SHIFT)
+#define PRIORITY_KEY(p) (((uint32_t)(p) & PRIORITY_MASK) << PRIORITY_SHIFT)
+
+#define STAMP_MAX       STAMP_MASK
+
+typedef struct {
+    const char *class_name;
+    uint32_t    priority;
+} class_row_t;
+
+
+static MRP_LIST_HOOK(class_list);
+static mrp_htbl_t *name_hash;
+
+static void init_name_hash(void);
+static int  add_to_name_hash(mrp_application_class_t *);
+#if 0
+static void remove_from_name_hash(mrp_application_class_t *);
+#endif
+
+static mqi_handle_t get_database_table(void);
+static void insert_into_application_class_table(const char *, uint32_t);
+
+
+mrp_application_class_t *mrp_application_class_create(const char *name,
+                                                    uint32_t pri,
+                                                    bool modal,
+                                                    bool share,
+                                                    mrp_resource_order_t order)
+{
+    mrp_application_class_t *class;
+    mrp_list_hook_t *insert_before, *clhook, *n;
+    uint32_t zone;
+
+    MRP_ASSERT(name, "invalid argument");
+
+    if (modal && share) {
+        mrp_log_error("Class '%s' is both modal and shared. "
+                      "Sharing will be disabled", name);
+        share = false;
+    }
+
+    /* looping through all classes to check the uniqueness of the
+       name & priority of the new class and find the insertion point */
+    insert_before = &class_list;
+
+    mrp_list_foreach_back(&class_list, clhook, n) {
+        class = mrp_list_entry(clhook, mrp_application_class_t, list);
+
+        if (!strcasecmp(name, class->name)) {
+            mrp_log_warning("Multiple definitions for class '%s'", name);
+            return NULL;
+        }
+
+        if (pri == class->priority) {
+            mrp_log_error("Priority clash. Classes '%s' and '%s' would have "
+                          "the same priority", name, class->name);
+        }
+
+        if (pri < class->priority)
+            insert_before = &class->list;
+    }
+
+    if (!(class = mrp_allocz(sizeof(mrp_application_class_t)))) {
+        mrp_log_error("Memory alloc failure. Can't create resource class '%s'",
+                      name);
+        return NULL;
+    }
+
+    class->name = mrp_strdup(name);
+    class->priority = pri;
+    class->modal = modal;
+    class->share = share;
+    class->order = order;
+
+    for (zone = 0;  zone < MRP_ZONE_MAX;  zone++)
+        mrp_list_init(&class->resource_sets[zone]);
+
+    /* list do not have insert_before function,
+       so don't be mislead by the name */
+    mrp_list_append(insert_before, &class->list);
+
+    add_to_name_hash(class);
+
+    insert_into_application_class_table(class->name, class->priority);
+
+    return class;
+}
+
+
+mrp_application_class_t *mrp_application_class_find(const char *name)
+{
+    mrp_application_class_t *class = NULL;
+
+    if (name_hash && name)
+        class = mrp_htbl_lookup(name_hash, (void *)name);
+
+    return class;
+}
+
+mrp_application_class_t *mrp_application_class_iterate_classes(void **cursor)
+{
+    mrp_list_hook_t *entry;
+
+    MRP_ASSERT(cursor, "invalid argument");
+
+    entry = (*cursor == NULL) ? class_list.prev : (mrp_list_hook_t *)*cursor;
+
+    if (entry == &class_list)
+        return NULL;
+
+    *cursor = entry->prev;
+
+    return mrp_list_entry(entry, mrp_application_class_t, list);
+}
+
+mrp_resource_set_t *
+mrp_application_class_iterate_rsets(mrp_application_class_t *class,
+                                    uint32_t                 zone,
+                                    void                   **cursor)
+{
+    mrp_list_hook_t *list, *entry;
+
+    MRP_ASSERT(class && zone < MRP_ZONE_MAX && cursor, "invalid argument");
+
+    list  = class->resource_sets + zone;
+    entry = (*cursor == NULL) ? list->prev : (mrp_list_hook_t *)*cursor;
+
+    if (entry == list)
+        return NULL;
+
+    *cursor = entry->prev;
+
+    return mrp_list_entry(entry, mrp_resource_set_t, class.list);
+}
+
+const char *mrp_application_class_get_name(mrp_application_class_t *class)
+{
+    if (!class || !class->name)
+        return "<unknown class>";
+
+    return class->name;
+}
+
+
+const char **mrp_application_class_get_all_names(uint32_t buflen,
+                                                 const char **buf)
+{
+    mrp_list_hook_t *entry, *n;
+    mrp_application_class_t *class;
+    bool freeit = false;
+    uint32_t i  = 0;
+
+    MRP_ASSERT(!buf || (buf && buflen > 1), "invalid argument");
+
+    if (!buf) {
+        freeit = true;
+        buflen = CLASS_MAX;
+        if (!(buf = mrp_allocz(sizeof(const char *) * buflen))) {
+            mrp_log_error("Memory alloc failure. Can't get class names");
+            return NULL;
+        }
+    }
+
+    mrp_list_foreach(&class_list, entry, n) {
+        class = mrp_list_entry(entry, mrp_application_class_t, list);
+
+        if (i >= buflen-1) {
+            if (freeit)
+                mrp_free(buf);
+            return NULL;
+        }
+
+        buf[i++] = class->name;
+    }
+
+    buf[i] = NULL;
+
+    return buf;
+}
+
+uint32_t mrp_application_class_get_priority(mrp_application_class_t *class)
+{
+    return class ? class->priority : 0;
+}
+
+int mrp_application_class_add_resource_set(const char *class_name,
+                                           const char *zone_name,
+                                           mrp_resource_set_t *rset,
+                                           uint32_t reqid)
+{
+    mrp_application_class_t *class;
+    mrp_zone_t *zone;
+
+
+    MRP_ASSERT(class_name && rset && zone_name, "invalid argument");
+    MRP_ASSERT(!rset->class.ptr || !mrp_list_empty(&rset->class.list),
+               "attempt to add multiple times the same resource set");
+
+    if (!(class = mrp_application_class_find(class_name)))
+        return -1;
+
+    if (!(zone = mrp_zone_find_by_name(zone_name)))
+        return -1;
+
+    rset->class.ptr = class;
+    rset->zone = mrp_zone_get_id(zone);
+
+    if (rset->state == mrp_resource_acquire)
+        mrp_resource_set_acquire(rset, reqid);
+    else {
+        rset->request.id = reqid;
+
+        if (rset->state == mrp_resource_no_request)
+            rset->state = mrp_resource_release;
+
+        mrp_application_class_move_resource_set(rset);
+
+        mrp_resource_set_notify(rset, MRP_RESOURCE_EVENT_CREATED);
+
+        mrp_resource_owner_update_zone(rset->zone, rset, reqid);
+    }
+    
+    return 0;
+}
+
+void mrp_application_class_move_resource_set(mrp_resource_set_t *rset)
+{
+    mrp_application_class_t *class;
+    mrp_list_hook_t *list, *lentry, *n, *insert_before;
+    mrp_resource_set_t *rentry;
+    uint32_t key;
+    uint32_t zone;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    mrp_list_delete(&rset->class.list);
+
+    class = rset->class.ptr;
+    zone  = rset->zone;
+
+    list = insert_before = class->resource_sets + zone;
+    key  = mrp_application_class_get_sorting_key(rset);
+
+    mrp_list_foreach_back(list, lentry, n) {
+        rentry = mrp_list_entry(lentry, mrp_resource_set_t, class.list);
+
+         if (key >= mrp_application_class_get_sorting_key(rentry))
+             break;
+
+         insert_before = lentry;
+    }
+
+    mrp_list_append(insert_before, &rset->class.list);
+}
+
+uint32_t mrp_application_class_get_sorting_key(mrp_resource_set_t *rset)
+{
+    mrp_application_class_t *class;
+    bool     lifo;
+    uint32_t rqstamp;
+    uint32_t priority;
+    uint32_t usage;
+    uint32_t state;
+    uint32_t stamp;
+    uint32_t key;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    class = rset->class.ptr;
+    lifo  = (class->order == MRP_RESOURCE_ORDER_LIFO);
+
+    rqstamp  = rset->request.stamp;
+
+    priority = PRIORITY_KEY(rset->class.priority);
+    usage    = USAGE_KEY(rset->resource.share ? 1 : 0);
+    state    = STATE_KEY(rset->state == mrp_resource_acquire ? 1 : 0);
+    stamp    = STAMP_KEY(lifo ? rqstamp : STAMP_MAX - rqstamp);
+
+    key = priority | usage | state | stamp;
+
+    return key;
+}
+
+
+int mrp_application_class_print(char *buf, int len, bool with_rsets)
+{
+#define PRINT(fmt, args...) \
+    do { if (p<e) { p += snprintf(p, e-p, fmt , ##args); } } while (0)
+
+    mrp_zone_t *zone;
+    mrp_application_class_t *class;
+    mrp_resource_set_t *rset;
+    mrp_list_hook_t *clen, *n;
+    mrp_list_hook_t *list, *rsen, *m;
+    uint32_t zid;
+    char *p, *e;
+    int width, l;
+    int clcnt, rscnt;
+
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(buf, "invalid argument");
+
+    e = (p = buf) + len;
+    clcnt = rscnt = 0;
+    width = 0;
+
+    if (!with_rsets) {
+        mrp_list_foreach(&class_list, clen, n) {
+            class = mrp_list_entry(clen, mrp_application_class_t, list);
+            if ((l = strlen(class->name)) > width)
+                width = l;
+        }
+    }
+
+    PRINT("Application classes:\n");
+
+    mrp_list_foreach_back(&class_list, clen, n) {
+        class = mrp_list_entry(clen, mrp_application_class_t, list);
+        clcnt++;
+
+        if (with_rsets)
+            PRINT("  %3u - %s ", class->priority, class->name);
+        else
+            PRINT("   %-*s ", width, class->name);
+
+        if (class->modal)
+            PRINT(" modal");
+        if (class->share)
+            PRINT(" share");
+
+        PRINT("\n");
+
+        if (!with_rsets)
+            continue;
+
+        for (zid = 0;   zid < MRP_ZONE_MAX;   zid++) {
+            zone = mrp_zone_find_by_id(zid);
+            list = class->resource_sets + zid;
+
+            if (!mrp_list_empty(list)) {
+                if (!zone) {
+                    PRINT("           Resource-sets in zone %u:\n", zid);
+                }
+                else {
+                    PRINT("           Resource-sets in %s zone:", zone->name);
+                    p += mrp_zone_attribute_print(zone, p, e-p);
+                    PRINT("\n");
+                }
+
+                mrp_list_foreach_back(list, rsen, m) {
+                    rset = mrp_list_entry(rsen, mrp_resource_set_t,class.list);
+                    p += mrp_resource_set_print(rset, 13, p, e-p);
+                }
+            }
+        }
+    }
+
+    if (!clcnt)
+        PRINT("   <none>\n");
+
+    return p - buf;
+
+#undef PRINT
+}
+
+
+static void init_name_hash(void)
+{
+    mrp_htbl_config_t  cfg;
+
+    if (!name_hash) {
+        cfg.nentry  = CLASS_MAX;
+        cfg.comp    = mrp_string_comp;
+        cfg.hash    = mrp_string_hash;
+        cfg.free    = NULL;
+        cfg.nbucket = cfg.nentry / 2;
+
+        name_hash = mrp_htbl_create(&cfg);
+
+        MRP_ASSERT(name_hash, "failed to make name_hash for resource classes");
+    }
+}
+
+
+static int add_to_name_hash(mrp_application_class_t *class)
+{
+    MRP_ASSERT(class && class->name, "invalid argument");
+
+    init_name_hash();
+
+    if (!mrp_htbl_insert(name_hash, (void *)class->name, class))
+        return -1;
+
+    return 0;
+}
+
+#if 0
+static void remove_from_name_hash(mrp_application_class_t *class)
+{
+    mrp_application_class_t *deleted;
+
+    if (class && class->name && name_hash) {
+        deleted = mrp_htbl_remove(name_hash, (void *)class->name, false);
+
+        MRP_ASSERT(!deleted || deleted == class, "confused with data "
+                   "structures when deleting resource-class from name hash");
+
+        /* in case we were not compiled with debug enabled */
+        if (deleted != class) {
+            mrp_log_error("confused with data structures when deleting "
+                          "resource-class '%s' from name hash", class->name);
+        }
+    }
+}
+#endif
+
+
+static mqi_handle_t get_database_table(void)
+{
+    MQI_COLUMN_DEFINITION_LIST(coldefs,
+        MQI_COLUMN_DEFINITION( "name"     , MQI_VARCHAR(NAME_LENGTH) ),
+        MQI_COLUMN_DEFINITION( "priority" , MQI_UNSIGNED             )
+    );
+
+    MQI_INDEX_DEFINITION(indexdef,
+        MQI_INDEX_COLUMN("priority")
+    );
+
+    static mqi_handle_t  table = MQI_HANDLE_INVALID;
+    static char         *name  = "application_classes";
+
+    if (table == MQI_HANDLE_INVALID) {
+        mqi_open();
+
+        table = MQI_CREATE_TABLE(name, MQI_TEMPORARY, coldefs, indexdef);
+
+        if (table == MQI_HANDLE_INVALID)
+            mrp_log_error("Can't create table '%s': %s", name,strerror(errno));
+    }
+
+    return table;
+}
+
+static void insert_into_application_class_table(const char *name, uint32_t pri)
+{
+    MQI_COLUMN_SELECTION_LIST(cols,
+        MQI_COLUMN_SELECTOR(CLASS_NAME_IDX, class_row_t, class_name),
+        MQI_COLUMN_SELECTOR(PRIORITY_IDX  , class_row_t, priority  )
+    );
+
+    class_row_t   row;
+    mqi_handle_t  table   = get_database_table();
+    class_row_t  *rows[2] = {&row, NULL};
+
+    MRP_ASSERT(name, "invalid argument");
+    MRP_ASSERT(table != MQI_HANDLE_INVALID, "database problem");
+
+    row.class_name = name;
+    row.priority = pri;
+
+    if (MQI_INSERT_INTO(table, cols, rows) != 1)
+        mrp_log_error("Failed to add application class '%s' to database",name);
+}
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/application-class.h b/src/resource/application-class.h
new file mode 100644 (file)
index 0000000..02b692b
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_APPLICATION_CLASS_H__
+#define __MURPHY_APPLICATION_CLASS_H__
+
+#include <murphy/common/list.h>
+
+#include "data-types.h"
+
+
+
+struct mrp_application_class_s {
+    mrp_list_hook_t       list;
+    const char           *name;
+    uint32_t              priority;
+    bool                  share;
+    bool                  modal;
+    mrp_resource_order_t  order;
+    mrp_list_hook_t       resource_sets[MRP_ZONE_MAX];
+};
+
+mrp_application_class_t *mrp_application_class_find(const char *);
+mrp_application_class_t *mrp_application_class_iterate_classes(void **);
+mrp_resource_set_t *
+mrp_application_class_iterate_rsets(mrp_application_class_t*,uint32_t,void**);
+
+void mrp_application_class_move_resource_set(mrp_resource_set_t *);
+
+uint32_t mrp_application_class_get_sorting_key(mrp_resource_set_t *);
+
+
+#endif  /* __MURPHY_APPLICATION_CLASS_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/attribute.c b/src/resource/attribute.c
new file mode 100644 (file)
index 0000000..2f354ea
--- /dev/null
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include "attribute.h"
+#include "client-api.h"
+
+static mrp_attr_value_t *get_attr_value_from_list(mrp_attr_t *, const char *,
+                                                  mqi_data_type_t);
+
+
+int mrp_attribute_copy_definitions(mrp_attr_def_t *from, mrp_attr_def_t *to)
+{
+    mrp_attr_def_t *s, *d;
+
+    MRP_ASSERT(to,"invalid argument");
+
+    if (from) {
+        for (s = from, d = to;   s->name;   s++, d++) {
+
+            if (!(d->name = mrp_strdup(s->name)))
+                goto no_memory;
+
+            d->access = s->access;
+
+            if ((d->type = s->type) != mqi_string)
+                d->value = s->value;
+            else {
+                if (!(d->value.string = mrp_strdup(s->value.string))) {
+                    mrp_free((void *)d->name);
+                    memset(d, 0, sizeof(*d));
+                    goto no_memory;
+                }
+            }
+        }
+    }
+
+    return 0;
+
+ no_memory:
+    mrp_log_error("Memory alloc failure. Can't copy attribute definition");
+    return -1;
+}
+
+
+mrp_attr_t *mrp_attribute_get_value(uint32_t          idx,
+                                    mrp_attr_t       *value,
+                                    uint32_t          nattr,
+                                    mrp_attr_def_t   *defs,
+                                    mrp_attr_value_t *attrs)
+{
+    mrp_attr_t *vdst;
+    mrp_attr_def_t *adef;
+
+    MRP_ASSERT(!nattr || (nattr > 0 && defs && attrs), "invalid argument");
+    MRP_ASSERT(idx < nattr, "invalid argument");
+
+    if ((vdst = value) || (vdst = mrp_alloc(sizeof(mrp_attr_t)))) {
+        adef = defs + idx;
+
+        if (!(adef->access & MRP_RESOURCE_READ))
+            memset(vdst, 0, sizeof(mrp_attr_t));
+        else {
+            vdst->name  = adef->name;
+            vdst->type  = adef->type;
+            vdst->value = attrs[idx];
+        }
+    }
+
+    return vdst;
+}
+
+
+mrp_attr_t *mrp_attribute_get_all_values(uint32_t          nvalue,
+                                         mrp_attr_t       *values,
+                                         uint32_t          nattr,
+                                         mrp_attr_def_t   *defs,
+                                         mrp_attr_value_t *attrs)
+{
+    mrp_attr_def_t *adef;
+    mrp_attr_t *vdst, *vend;
+    uint32_t i;
+
+    MRP_ASSERT((!nvalue || (nvalue > 0 && values)) &&
+               (!nattr  || (nattr  > 0 && defs)),
+               "invalid argument");
+
+    if (nvalue)
+        nvalue--;
+    else {
+        for (i = 0;  i < nattr;  i++) {
+            if (!attrs || (attrs && (defs[i].access & MRP_RESOURCE_READ)))
+                nvalue++;
+        }
+
+        if (!(values = mrp_allocz(sizeof(mrp_attr_t) * (nvalue + 1)))) {
+            mrp_log_error("Memory alloc failure. Can't get attributes");
+            return NULL;
+        }
+    }
+
+    vend = (vdst = values) + nvalue;
+
+    for (i = 0;     i < nattr && vdst < vend;    i++) {
+        adef = defs  + i;
+
+        if (!(adef->access && MRP_RESOURCE_READ))
+            continue;
+
+        vdst->name   =  adef->name;
+        vdst->type   =  adef->type;
+        vdst->value  =  attrs ? attrs[i] : adef->value;
+
+        vdst++;
+    }
+
+    memset(vdst, 0, sizeof(*vdst));
+
+    return values;
+}
+
+int mrp_attribute_set_values(mrp_attr_t      *values,
+                             uint32_t          nattr,
+                             mrp_attr_def_t   *defs,
+                             mrp_attr_value_t *attrs)
+{
+    mrp_attr_def_t *adef;
+    mrp_attr_value_t *vsrc;
+    mrp_attr_value_t *vdst;
+    uint32_t i;
+
+
+    MRP_ASSERT(!nattr || (nattr > 0 && defs && attrs),
+               "invalid arguments");
+
+    for (i = 0;  i < nattr;  i++) {
+        adef = defs  + i;
+        vdst = attrs + i;
+
+        if (!(adef->access & MRP_RESOURCE_WRITE) ||
+            !(vsrc = get_attr_value_from_list(values, adef->name, adef->type)))
+            vsrc = &adef->value; /* default value */
+
+        if (adef->type !=  mqi_string)
+            *vdst = *vsrc;
+        else if (vdst->string != vsrc->string) {
+            /* if the string is not the same, change it */
+            mrp_free((void *)vdst->string);
+            if (!(vdst->string = mrp_strdup(vsrc->string)))
+                return -1;
+        }
+    }
+
+    return 0;
+}
+
+
+int mrp_attribute_print(uint32_t          nattr,
+                        mrp_attr_def_t   *adefs,
+                        mrp_attr_value_t *avals,
+                        char             *buf,
+                        int               len)
+{
+#define PRINT(fmt, args...)  if (p<e) { p += snprintf(p, e-p, fmt , ##args); }
+
+    mrp_attr_def_t *adef;
+    mrp_attr_value_t *aval;
+    uint32_t i;
+    char *p, *e;
+
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(adefs && avals && buf, "invalid argument");
+
+    e = (p = buf) + len;
+
+    for (i = 0;  i < nattr;  i++) {
+        adef = adefs + i;
+        aval = avals + i;
+
+        PRINT(" %s:", adef->name);
+
+        switch (adef->type) {
+        case mqi_string:    PRINT("'%s'", aval->string  );   break;
+        case mqi_integer:   PRINT("%d"  , aval->integer );   break;
+        case mqi_unsignd:   PRINT("%u"  , aval->unsignd );   break;
+        case mqi_floating:  PRINT("%lf" , aval->floating);   break;
+        default:            PRINT(" <unsupported type>" );   break;
+        }
+
+    }
+
+    return p - buf;
+
+#undef PRINT
+}
+
+
+static mrp_attr_value_t *get_attr_value_from_list(mrp_attr_t     *list,
+                                                  const char     *name,
+                                                  mqi_data_type_t type)
+{
+    mrp_attr_t *attr;
+
+    MRP_ASSERT(name, "invalid argument");
+
+    if (list) {
+        for (attr = list;   attr->name;   attr++) {
+            if (!strcasecmp(name, attr->name) && type == attr->type)
+                return &attr->value;
+        }
+    }
+
+    return NULL;
+}
+
+void mrp_resource_set_free_attribute(mrp_attr_t *attr)
+{
+    if (!attr)
+        return;
+
+    mrp_free(attr);
+}
+
+mrp_attr_t *mrp_resource_set_get_attribute_by_name(
+        mrp_resource_set_t *resource_set, const char *resource_name,
+        const char *attribute_name)
+{
+    mrp_attr_t *attr = NULL, *attrs;
+    uint32_t res_id;
+    mrp_attr_t attr_buf[128];
+    uint32_t attr_idx = 0;
+
+    memset(attr_buf, 0, sizeof(attr_buf));
+
+    res_id = mrp_resource_definition_get_resource_id_by_name(resource_name);
+    attrs = mrp_resource_definition_read_all_attributes(res_id, 128, attr_buf);
+
+    if (!attrs)
+        return NULL;
+
+    while (attrs->name != NULL) {
+        if (strcmp(attrs->name, attribute_name) == 0) {
+
+            mrp_attr_t *buf = mrp_allocz(sizeof(mrp_attr_t));
+            mrp_resource_set_read_attribute(resource_set, resource_name,
+                    attr_idx, buf);
+
+            attr = buf;
+
+            break;
+        }
+        attr_idx++;
+        attrs++;
+    }
+
+    return attr;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/attribute.h b/src/resource/attribute.h
new file mode 100644 (file)
index 0000000..e83e23c
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_ATTRIBUTE_H__
+#define __MURPHY_ATTRIBUTE_H__
+
+#include "data-types.h"
+
+
+int mrp_attribute_copy_definitions(mrp_attr_def_t *, mrp_attr_def_t *);
+mrp_attr_t *mrp_attribute_get_value(uint32_t, mrp_attr_t *, uint32_t,
+                                    mrp_attr_def_t *, mrp_attr_value_t *);
+mrp_attr_t *mrp_attribute_get_all_values(uint32_t, mrp_attr_t *, uint32_t,
+                                         mrp_attr_def_t *, mrp_attr_value_t *);
+int mrp_attribute_set_values(mrp_attr_t *, uint32_t, mrp_attr_def_t *,
+                             mrp_attr_value_t *);
+
+int mrp_attribute_print(uint32_t, mrp_attr_def_t *, mrp_attr_value_t *,
+                        char *, int);
+
+
+#endif  /* __MURPHY_ATTRIBUTE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/client-api.h b/src/resource/client-api.h
new file mode 100644 (file)
index 0000000..5f56dfa
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_CLIENT_API_H__
+#define __MURPHY_RESOURCE_CLIENT_API_H__
+
+#include <murphy/resource/common-api.h>
+
+mrp_resource_client_t *mrp_resource_client_create(const char *name,
+                                                  void *user_data);
+void mrp_resource_client_destroy(mrp_resource_client_t *client);
+
+mrp_resource_set_t *mrp_resource_client_find_set(mrp_resource_client_t *client,
+                                                 uint32_t resource_set_id);
+
+
+const char **mrp_zone_get_all_names(uint32_t buflen, const char **buf);
+
+const char **mrp_resource_definition_get_all_names(uint32_t buflen,
+                                                   const char **buf);
+
+uint32_t mrp_resource_definition_get_resource_id_by_name(const char *name);
+
+mrp_attr_t *
+mrp_resource_definition_read_all_attributes(uint32_t resource_id,
+                                            uint32_t buflen,
+                                            mrp_attr_t *buf);
+
+const char **mrp_application_class_get_all_names(uint32_t buflen,
+                                                 const char **buf);
+
+int mrp_application_class_add_resource_set(const char *class_name,
+                                           const char *zone_name,
+                                           mrp_resource_set_t *resource_set,
+                                           uint32_t request_id);
+
+mrp_resource_set_t *mrp_resource_set_create(mrp_resource_client_t *client,
+                                            bool auto_release,
+                                            bool dont_wait,
+                                            uint32_t priority,
+                                            mrp_resource_event_cb_t event_cb,
+                                            void *user_data);
+void mrp_resource_set_destroy(mrp_resource_set_t *resource_set);
+
+uint32_t mrp_get_resource_set_id(mrp_resource_set_t *resource_set);
+
+mrp_resource_state_t
+mrp_get_resource_set_state(mrp_resource_set_t *resource_set);
+
+mrp_resource_mask_t
+mrp_get_resource_set_grant(mrp_resource_set_t *resource_set);
+
+mrp_resource_mask_t
+mrp_get_resource_set_advice(mrp_resource_set_t *resource_set);
+
+mrp_resource_client_t *
+mrp_get_resource_set_client(mrp_resource_set_t *resource_set);
+
+
+int mrp_resource_set_add_resource(mrp_resource_set_t *resource_set,
+                                  const char *resource_name,
+                                  bool shared,
+                                  mrp_attr_t *attribute_list,
+                                  bool mandatory);
+
+mrp_attr_t *
+mrp_resource_set_read_attribute(mrp_resource_set_t *resource_set,
+                                const char *resource_name,
+                                uint32_t attribute_index,
+                                mrp_attr_t *buf);
+
+mrp_attr_t *
+mrp_resource_set_read_all_attributes(mrp_resource_set_t *resource_set,
+                                     const char *resource_name,
+                                     uint32_t buflen,
+                                     mrp_attr_t *buf);
+
+int mrp_resource_set_write_attributes(mrp_resource_set_t *resource_set,
+                                      const char *resource_name,
+                                      mrp_attr_t *attribute_list);
+
+void mrp_resource_set_acquire(mrp_resource_set_t *resource_set,
+                              uint32_t request_id);
+
+void mrp_resource_set_release(mrp_resource_set_t *resource_set,
+                              uint32_t request_id);
+
+mrp_resource_t *
+mrp_resource_set_iterate_resources(mrp_resource_set_t *resource_set,void **it);
+
+uint32_t mrp_resource_get_id(mrp_resource_t *resource);
+const char *mrp_resource_get_name(mrp_resource_t *resource);
+mrp_resource_mask_t mrp_resource_get_mask(mrp_resource_t *resource);
+bool mrp_resource_is_shared(mrp_resource_t *resource);
+
+/* Find a resource set given the resource set id. */
+mrp_resource_set_t *mrp_resource_set_find_by_id(uint32_t id);
+
+/* Get a single attribute object that contains the current value. */
+mrp_attr_t *mrp_resource_set_get_attribute_by_name(
+        mrp_resource_set_t *resource_set, const char *resource_name,
+        const char *attribute_name);
+
+/* Free the object obtained with mrp_resource_set_get_attribute_by_name. */
+void mrp_resource_set_free_attribute(mrp_attr_t *attr);
+
+#endif  /* __MURPHY_RESOURCE_CLIENT_API_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/common-api.h b/src/resource/common-api.h
new file mode 100644 (file)
index 0000000..4b49e2b
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_COMMON_API_H__
+#define __MURPHY_RESOURCE_COMMON_API_H__
+
+#include <murphy/resource/data-types.h>
+
+
+mrp_attr_t *mrp_resource_read_attribute(mrp_resource_t *resource,
+                                        uint32_t attribute_index,
+                                        mrp_attr_t *buf);
+mrp_attr_t *mrp_resource_read_all_attributes(mrp_resource_t *resource,
+                                             uint32_t buflen,
+                                             mrp_attr_t *buf);
+int mrp_resource_write_attributes(mrp_resource_t *resource, mrp_attr_t *attrs);
+
+
+#endif  /* __MURPHY_RESOURCE_COMMON_API_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/config-api.h b/src/resource/config-api.h
new file mode 100644 (file)
index 0000000..6d7f60a
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_CONFIG_API_H__
+#define __MURPHY_RESOURCE_CONFIG_API_H__
+
+#include <murphy/resource/data-types.h>
+
+void mrp_resource_configuration_init(void);
+
+int mrp_zone_definition_create(mrp_attr_def_t *attrdefs);
+uint32_t mrp_zone_create(const char *name, mrp_attr_t *attrs);
+
+mrp_application_class_t *mrp_application_class_create(const char *name,
+                                                  uint32_t priority,
+                                                  bool modal,
+                                                  bool share,
+                                                  mrp_resource_order_t order);
+
+int mrp_application_class_print(char *buf, int len, bool with_resource_sets);
+
+int mrp_resource_owner_print(char *buf, int len);
+
+
+#endif  /* __MURPHY_RESOURCE_CONFIG_API_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/config-lua.c b/src/resource/config-lua.c
new file mode 100644 (file)
index 0000000..5a0a5d7
--- /dev/null
@@ -0,0 +1,1751 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common.h>
+#include <murphy/common/debug.h>
+#include <murphy/core/lua-bindings/murphy.h>
+#include <murphy/core/lua-utils/object.h>
+
+#include "config-lua.h"
+#include "resource-lua.h"
+#include "config-api.h"
+#include "client-api.h"
+#include "manager-api.h"
+#include "zone.h"
+#include "application-class.h"
+#include "resource.h"
+#include "resource-set.h"
+#include "resource-owner.h"
+#include "attribute.h"
+
+#define ZONE_CLASS             MRP_LUA_CLASS_SIMPLE(zone)
+#define APPCLASS_CLASS         MRP_LUA_CLASS_SIMPLE(application_class)
+#define ZONE_ATTR_CLASS        MRP_LUA_CLASS(zone, attributes)
+#define RESCLASS_CLASS         MRP_LUA_CLASS(resource, class)
+#define RESMETHOD_CLASS        MRP_LUA_CLASS_SIMPLE(resource)
+
+#define ATTRIBUTE_CLASSID      MRP_LUA_CLASSID_ROOT "attribute"
+#define RESOURCE_CLASSID       MRP_LUA_CLASSID_ROOT "resource.instance"
+
+typedef enum   field_e         field_t;
+typedef enum   attr_owner_e    attr_owner_t;
+typedef struct appclass_s      appclass_t;
+typedef struct zone_s          zone_t;
+typedef struct resclass_s      resclass_t;
+typedef struct resource_s      resource_t;
+typedef struct attr_def_s      attr_def_t;
+typedef struct attr_s          attr_t;
+
+typedef bool (*attribute_access_t)(attr_t *, int, mrp_attr_t *);
+
+enum field_e {
+    ATTRIBUTES = 1,
+    CLASS,
+    NAME,
+    PRIORITY,
+    SHAREABLE,
+    MANDATORY,
+    MODAL,
+    SHARE,
+    GRANT,
+    ORDER,
+    SHARED,
+    METHOD,
+    OWNERS,
+    RECALC,
+    VETO,
+    ID
+};
+
+enum attr_owner_e {
+    ZONE = 1,
+    RESOURCE
+};
+
+struct appclass_s {
+    const char *name;
+};
+
+struct zone_s {
+    uint32_t  id;
+    const char *name;
+    int attr_tbl;
+};
+
+struct resclass_s {
+    uint32_t id;
+    const char *name;
+    mrp_attr_def_t *attrs;
+};
+
+struct resource_s {
+    uint32_t rsetid;
+    uint32_t resid;
+    const char *name;
+    int attr_tbl;
+};
+
+struct attr_def_s {
+    int nattr;
+    mrp_attr_def_t *attrs;
+};
+
+struct attr_s {
+    struct {
+        attr_owner_t type;
+        void *data;
+    } owner;
+    attr_def_t *def;
+    attribute_access_t fetch;
+    attribute_access_t update;
+};
+
+
+static void attributes_class_create(lua_State *);
+static void appclass_class_create(lua_State *);
+static void zone_class_create(lua_State *);
+static void resclass_class_create(lua_State *);
+static void resource_class_create(lua_State *);
+static void resource_methods_create(lua_State *);
+
+
+static int  attributes_create(lua_State *, attr_owner_t, void *,
+                              attr_def_t *, attribute_access_t,
+                              attribute_access_t);
+static int  attributes_getvalue(lua_State *);
+static int  attributes_setvalue(lua_State *);
+static int  attributes_getlength(lua_State *);
+static attr_t *check_attributes(lua_State *, int);
+static int  push_attributes(lua_State *, int);
+
+static int  appclass_create(lua_State *);
+static int  appclass_getfield(lua_State *);
+static int  appclass_setfield(lua_State *);
+static void appclass_destroy(void *);
+/* static appclass_t *check_appclass(lua_State *, int); */
+static appclass_t *to_appclass(lua_State *, int);
+
+static int  zone_create(lua_State *);
+static int  zone_getfield(lua_State *);
+static int  zone_setfield(lua_State *);
+static void zone_destroy(void *);
+/* static zone_t *check_zone(lua_State *, int); */
+static zone_t *to_zone(lua_State *, int);
+
+static int  zone_attr_create(lua_State *);
+static int  zone_attr_getfield(lua_State *);
+static int  zone_attr_setfield(lua_State *);
+static void zone_attr_destroy(void *);
+/* static attr_def_t *check_zone_attr(lua_State *, int); */
+/* static attr_def_t *to_zone_attr(lua_State *, int); */
+static bool fetch_zone_attribute(attr_t *, int, mrp_attr_t *);
+static bool update_zone_attribute(attr_t *, int, mrp_attr_t *);
+
+static int  resclass_create_from_lua(lua_State *);
+static int  resclass_getfield(lua_State *);
+static int  resclass_setfield(lua_State *);
+static void resclass_destroy(void *);
+/* static resclass_t *check_resclass(lua_State *, int); */
+static resclass_t *to_resclass(lua_State *, int);
+
+static int  resource_getfield(lua_State *);
+static int  resource_setfield(lua_State *);
+static resource_t *check_resource(lua_State *, int);
+static bool fetch_resource_attribute(attr_t *, int, mrp_attr_t *);
+static bool update_resource_attribute(attr_t *, int, mrp_attr_t *);
+
+static mrp_lua_resmethod_t *resmethod_create_from_c(lua_State *);
+static int  resmethod_create_from_lua(lua_State *);
+static int  resmethod_getfield(lua_State *);
+static int  resmethod_setfield(lua_State *);
+static void resmethod_destroy(void *);
+static mrp_lua_resmethod_t *to_resmethod(lua_State *, int);
+
+static mrp_attr_def_t *check_attrdefs(lua_State *, int, int *);
+static void free_attrdefs(mrp_attr_def_t *);
+static mrp_attr_t *check_attrs(lua_State *, int, attr_def_t *);
+static void free_attrs(mrp_attr_t *);
+static int check_attrindex(lua_State *, int, attr_def_t *);
+static int check_boolean(lua_State *, int);
+static mrp_resource_order_t check_order(lua_State *, int);
+static int push_order(lua_State *, mrp_resource_order_t);
+static field_t field_check(lua_State *, int, const char **);
+static field_t field_name_to_type(const char *, size_t);
+
+static int method_recalc(lua_State *);
+
+
+MRP_LUA_METHOD_LIST_TABLE (
+    zone_attr_methods,       /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (zone_attr_create)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    resclass_methods,         /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (resclass_create_from_lua)
+);
+
+
+MRP_LUA_METHOD_LIST_TABLE (
+    attributes_overrides,    /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (attributes_getvalue)
+    MRP_LUA_OVERRIDE_SETFIELD   (attributes_setvalue)
+    MRP_LUA_OVERRIDE_GETLENGTH  (attributes_getlength)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    zone_attr_overrides,     /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (zone_attr_create)
+    MRP_LUA_OVERRIDE_GETFIELD   (zone_attr_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (zone_attr_setfield)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    resclass_overrides,       /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (resclass_create_from_lua)
+    MRP_LUA_OVERRIDE_GETFIELD   (resclass_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (resclass_setfield)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    resource_overrides,       /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (resource_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (resource_setfield)
+);
+
+MRP_LUA_CLASS_DEF_SIMPLE (
+    application_class,          /* main class & constructor name */
+    appclass_t,                 /* userdata type */
+    appclass_destroy,           /* userdata destructor */
+    MRP_LUA_METHOD_LIST (       /* main class methods */
+        MRP_LUA_METHOD_CONSTRUCTOR  (appclass_create)
+    ),
+    MRP_LUA_METHOD_LIST (       /* main class overrides */
+        MRP_LUA_OVERRIDE_CALL       (appclass_create)
+        MRP_LUA_OVERRIDE_GETFIELD   (appclass_getfield)
+        MRP_LUA_OVERRIDE_SETFIELD   (appclass_setfield)
+    )
+);
+
+MRP_LUA_CLASS_DEF_SIMPLE (
+    zone,                       /* main class & constructor name */
+    zone_t,                     /* userdata type */
+    zone_destroy,               /* userdata destructor */
+    MRP_LUA_METHOD_LIST (       /* main class methods */
+        MRP_LUA_METHOD_CONSTRUCTOR  (zone_create)
+    ),
+    MRP_LUA_METHOD_LIST (       /* main class overrides */
+        MRP_LUA_OVERRIDE_CALL       (zone_create)
+        MRP_LUA_OVERRIDE_GETFIELD   (zone_getfield)
+        MRP_LUA_OVERRIDE_SETFIELD   (zone_setfield)
+    )
+);
+
+MRP_LUA_CLASS_DEF (
+    zone,                       /* main class name */
+    attributes,                 /* constructor name */
+    attr_def_t,                 /* userdata type */
+    zone_attr_destroy,          /* userdata destructor */
+    zone_attr_methods,          /* class methods */
+    zone_attr_overrides         /* class overrides */
+);
+
+MRP_LUA_CLASS_DEF (
+    resource,                   /* main class name */
+    class,                      /* constructor name */
+    resclass_t,                 /* userdata type */
+    resclass_destroy,           /* userdata destructor */
+    resclass_methods,           /* class methods */
+    resclass_overrides          /* class overrides */
+);
+
+
+MRP_LUA_CLASS_DEF_SIMPLE (
+    resource,                   /* main class name */
+    mrp_lua_resmethod_t,        /* userdata type */
+    resmethod_destroy,          /* userdata destructor */
+    MRP_LUA_METHOD_LIST (
+        MRP_LUA_METHOD_CONSTRUCTOR  (resmethod_create_from_lua)
+    ),
+    MRP_LUA_METHOD_LIST (
+        MRP_LUA_OVERRIDE_CALL       (resmethod_create_from_lua)
+        MRP_LUA_OVERRIDE_GETFIELD   (resmethod_getfield)
+        MRP_LUA_OVERRIDE_SETFIELD   (resmethod_setfield)
+    )
+);
+
+attr_def_t           *zone_attr_defs;
+attr_def_t           *resource_attr_defs[MRP_RESOURCE_MAX];
+mrp_lua_resmethod_t  *resource_methods;
+
+
+void mrp_resource_configuration_init(void)
+{
+    static bool initialised = false;
+
+    lua_State *L;
+
+    if (!initialised && (L =  mrp_lua_get_lua_state())) {
+
+        appclass_class_create(L);
+        zone_class_create(L);
+        resclass_class_create(L);
+        resource_class_create(L);
+
+        mrp_resource_lua_init(L);
+
+        resource_methods_create(L);
+
+        mrp_debug("lua classes are ready for resource "
+                  "configuration and management");
+
+        initialised = true;
+    }
+}
+
+
+mrp_lua_resmethod_t *mrp_lua_get_resource_methods(void)
+{
+    return resource_methods;
+}
+
+uint32_t mrp_lua_to_resource_id(lua_State *L, int t)
+{
+    resclass_t *rc = to_resclass(L, t);
+
+    return rc ? rc->id : MRP_RESOURCE_ID_INVALID;
+}
+
+void mrp_lua_resclass_create_from_c(uint32_t id)
+{
+    lua_State *L;
+    mrp_resource_def_t *rdef;
+    resclass_t *resclass;
+    attr_def_t *adef;
+    mrp_attr_def_t *attrs;
+    int nattr;
+
+    if ((L = mrp_lua_get_lua_state())) {
+
+        if (!(rdef = mrp_resource_definition_find_by_id(id)))
+            luaL_error(L, "invalid resource definition ID %d", id);
+
+        resclass = (resclass_t *)mrp_lua_create_object(L, RESCLASS_CLASS,
+                                                       rdef->name,0);
+        adef = mrp_allocz(sizeof(attr_def_t));
+        nattr = rdef->nattr;
+        attrs = mrp_allocz(sizeof(mrp_attr_def_t) * (nattr + 1));
+
+        if (!nattr)
+            mrp_attribute_copy_definitions(rdef->attrdefs, attrs);
+
+        if (!resclass)
+            luaL_error(L, "invalid or duplicate name '%s'", rdef->name);
+        if (!adef)
+            luaL_error(L,"can't to allocate memory for attribute definitions");
+
+        resclass->id = id;
+        resclass->name = mrp_strdup(rdef->name);
+        resclass->attrs = attrs;
+
+        adef->nattr = nattr;
+        adef->attrs = attrs;
+
+        resource_attr_defs[id] = adef;
+
+        lua_pop(L, 1);
+
+        mrp_log_info("resource class '%s' created", rdef->name);
+    }
+}
+
+int mrp_lua_resource_create(lua_State *L, mrp_resource_t *res)
+{
+    mrp_resource_def_t *rdef;
+    attr_def_t *adef;
+    resource_t *r;
+
+    MRP_LUA_ENTER;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    if (!(rdef = res->def))
+        lua_pushnil(L);
+    else {
+        adef = resource_attr_defs[rdef->id];
+
+        MRP_ASSERT(resource_attr_defs[rdef->id], "can't find attribute defs");
+
+        r = lua_newuserdata(L, sizeof(resource_t));
+
+        r->rsetid = res->rsetid;
+        r->resid = rdef->id;
+        r->name = rdef->name;
+        r->attr_tbl = attributes_create(L, RESOURCE,r, adef,
+                                        fetch_resource_attribute,
+                                        update_resource_attribute);
+
+        luaL_getmetatable(L, RESOURCE_CLASSID);
+        lua_setmetatable(L, -2);
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+
+static void attributes_class_create(lua_State *L)
+{
+    /* create a metatable for attributes */
+    luaL_newmetatable(L, ATTRIBUTE_CLASSID);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, attributes_overrides, 0);
+}
+
+static void appclass_class_create(lua_State *L)
+{
+    mrp_lua_create_object_class(L, APPCLASS_CLASS);
+}
+
+static void zone_class_create(lua_State *L)
+{
+    mrp_lua_create_object_class(L, ZONE_CLASS);
+    mrp_lua_create_object_class(L, ZONE_ATTR_CLASS);
+
+    attributes_class_create(L);
+
+    zone_attr_defs = mrp_lua_create_object(L, ZONE_ATTR_CLASS, NULL,0);
+    mrp_lua_set_object_name(L, ZONE_ATTR_CLASS, "attributes");
+    lua_pop(L, 1);
+}
+
+static void resclass_class_create(lua_State *L)
+{
+    mrp_lua_create_object_class(L, RESCLASS_CLASS);
+}
+
+static int resource_destructor(lua_State *L)
+{
+    resource_t *r;
+
+    if ((r = lua_touserdata(L, -1)) != NULL) {
+        mrp_debug("destroying Lua resource %p", r);
+        luaL_unref(L, LUA_REGISTRYINDEX, r->attr_tbl);
+        r->attr_tbl = LUA_NOREF;
+    }
+
+    return 0;
+}
+
+static void resource_class_create(lua_State *L)
+{
+    /* create a metatable for resource instances */
+    luaL_newmetatable(L, RESOURCE_CLASSID);
+    lua_pushcfunction(L, resource_destructor);
+    lua_setfield(L, -2, "__gc");
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, resource_overrides, 0);
+}
+
+static void resource_methods_create(lua_State *L)
+{
+    typedef struct {
+        const char *name;
+        lua_CFunction func;
+    } method_def_t;
+
+    static method_def_t method_defs[] = {
+        { "recalc",  method_recalc },
+        {   NULL  ,      NULL      }
+    };
+
+    method_def_t *md;
+
+    mrp_lua_create_object_class(L, RESMETHOD_CLASS);
+    resource_methods = resmethod_create_from_c(L);
+
+    for (md = method_defs;  md->name;   md++) {
+        lua_pushstring(L, md->name);
+        lua_pushcfunction(L, md->func);
+        lua_rawset(L, -3);
+    }
+}
+
+
+static int attributes_create(lua_State *L,
+                             attr_owner_t type, void *data,
+                             attr_def_t *def,
+                             attribute_access_t fetch,
+                             attribute_access_t update)
+{
+    attr_t *attr;
+    int tblref;
+
+    MRP_LUA_ENTER;
+
+    attr = lua_newuserdata(L, sizeof(attr_t));
+
+    attr->owner.type = type;
+    attr->owner.data = data;
+    attr->def = def;
+    attr->fetch = fetch;
+    attr->update = update;
+
+    luaL_getmetatable(L, ATTRIBUTE_CLASSID);
+    lua_setmetatable(L, -2);
+
+    tblref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+    MRP_LUA_LEAVE(tblref);
+}
+
+static int attributes_getvalue(lua_State *L)
+{
+    attr_t *attr = check_attributes(L, 1);
+    int idx = check_attrindex(L, 2, attr->def);
+    mrp_attr_def_t *def = attr->def->attrs + idx;
+    mrp_attr_t av;
+
+    MRP_LUA_ENTER;
+
+    if (idx < 0) {
+        lua_pushnil(L);
+        return 1;
+    }
+
+    if (!(def->access & MRP_RESOURCE_READ)) {
+        luaL_error(L, "attempt to read a non-readable attribute %s",
+                   def->name);
+        MRP_LUA_LEAVE(0);
+    }
+
+    if (!attr->fetch(attr, idx, &av)) {
+        lua_pushnil(L);
+        MRP_LUA_LEAVE(1);
+    }
+
+    switch (def->type) {
+    case mqi_string:
+        if (av.value.string)
+            lua_pushstring(L, av.value.string);
+        else
+            lua_pushnil(L);
+        break;
+    case mqi_integer:
+    case mqi_unsignd:
+        lua_pushinteger(L, av.value.integer);
+        break;
+    case mqi_floating:
+        lua_pushnumber(L, av.value.floating);
+        break;
+    default:
+        lua_pushnil(L);
+        break;
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int attributes_setvalue(lua_State *L)
+{
+    attr_t *attr = check_attributes(L, 1);
+    int idx = check_attrindex(L, 2, attr->def);
+    mrp_attr_def_t *def = attr->def->attrs + idx;
+    mrp_attr_t av;
+
+    MRP_LUA_ENTER;
+
+    if (idx < 0)
+        luaL_error(L, "attribute %s dows not exist", def->name);
+
+    if (!(def->access & MRP_RESOURCE_WRITE))
+        luaL_error(L, "attempt to read a readonly attribute %s", def->name);
+
+    switch (def->type) {
+    case mqi_string:
+        av.value.string = luaL_checkstring(L, 3);
+        break;
+    case mqi_integer:
+        av.value.integer =  luaL_checkinteger(L, 3);
+        break;
+    case mqi_unsignd:
+        if ((av.value.integer = luaL_checkinteger(L, 3)) < 0) {
+            luaL_error(L, "attempt to update an unsigned attribute "
+                       "with negative value");
+        }
+        break;
+    case mqi_floating:
+        av.value.floating = luaL_checknumber(L, 3);
+        break;
+    default:
+        luaL_error(L, "internal error: invalid attribute type");
+        break;
+    }
+
+    if (!attr->update(attr, idx, &av))
+        luaL_error(L, "attribute update failed");
+
+    MRP_LUA_LEAVE(0);
+}
+
+static int attributes_getlength(lua_State *L)
+{
+    attr_t *attr = check_attributes(L, 1);
+    attr_def_t *def = attr->def;
+
+    MRP_LUA_ENTER;
+
+    lua_pushinteger(L, def ? def->nattr : 0);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static attr_t *check_attributes(lua_State *L, int idx)
+{
+    return (attr_t *)luaL_checkudata(L, idx, ATTRIBUTE_CLASSID);
+}
+
+static int push_attributes(lua_State *L, int attr_tbl)
+{
+    lua_rawgeti(L, LUA_REGISTRYINDEX, attr_tbl);
+    return 1;
+}
+
+
+static int appclass_create(lua_State *L)
+{
+    appclass_t *appclass;
+    size_t fldnamlen;
+    const char *fldnam;
+    int priority = 0;
+    int modal = -1;
+    int share = -1;
+    mrp_resource_order_t order = 0;
+    const char *name = NULL;
+
+    MRP_LUA_ENTER;
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case PRIORITY:
+            priority = luaL_checkint(L, -1);
+            break;
+
+        case MODAL:
+            modal = check_boolean(L, -1);
+            break;
+
+        case SHARE:
+            share = check_boolean(L, -1);
+            break;
+
+        case ORDER:
+            order = check_order(L, -1);
+            break;
+
+        default:
+            luaL_error(L, "unexpected field '%s'", fldnam);
+            break;
+        }
+    }
+
+    if (!name)
+        luaL_error(L, "missing or wrong name field");
+    if (modal < 0)
+        luaL_error(L, "missing or wrong modal field");
+    if (modal && share)
+        luaL_error(L, "modal class can't share");
+    if (share < 0)
+        luaL_error(L, "missing or wrong share field");
+    if (!order)
+        luaL_error(L, "missing or wrong order field");
+    if (priority < 0)
+        luaL_error(L, "negative priority");
+    if (!mrp_application_class_create(name, priority, modal, share, order))
+        luaL_error(L, "failed to create application class '%s'", name);
+
+    appclass = (appclass_t *)mrp_lua_create_object(L, APPCLASS_CLASS, name,0);
+
+    if (!appclass)
+        luaL_error(L, "invalid or duplicate name '%s'", name);
+    else {
+        appclass->name = name;
+        mrp_log_info("application class '%s' created", name);
+    }
+    MRP_LUA_LEAVE(1);
+}
+
+static int appclass_getfield(lua_State *L)
+{
+    appclass_t *appclass = to_appclass(L, 1);
+    field_t fld = field_check(L, 2, NULL);
+    mrp_application_class_t *ac;
+
+    MRP_LUA_ENTER;
+
+    lua_pop(L, 1);
+
+    if (!appclass || !(ac = mrp_application_class_find(appclass->name)))
+        lua_pushnil(L);
+    else {
+        switch (fld) {
+        case NAME:       lua_pushstring(L, ac->name);         break;
+        case PRIORITY:   lua_pushinteger(L, ac->priority);    break;
+        case MODAL:      lua_pushboolean(L, ac->modal);       break;
+        case SHARE:      lua_pushboolean(L, ac->share);       break;
+        case ORDER:      push_order(L, ac->order);            break;
+        default:         lua_pushnil(L);                      break;
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int appclass_setfield(lua_State *L)
+{
+    MRP_LUA_ENTER;
+
+    luaL_error(L, "can't modify application classes after definition");
+
+    MRP_LUA_LEAVE(0);
+}
+
+static void appclass_destroy(void *data)
+{
+    appclass_t *appclass = (appclass_t *)data;
+
+    MRP_LUA_ENTER;
+
+    mrp_free((void *)appclass->name);
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+#if 0
+static appclass_t *check_appclass(lua_State *L, int idx)
+{
+    return (appclass_t *)mrp_lua_check_object(L, APPCLASS_CLASS, idx);
+}
+#endif
+
+static appclass_t *to_appclass(lua_State *L, int idx)
+{
+    return (appclass_t *)mrp_lua_to_object(L, APPCLASS_CLASS, idx);
+}
+
+
+static int zone_create(lua_State *L)
+{
+    zone_t *zone;
+    size_t fldnamlen;
+    const char *fldnam;
+    uint32_t id;
+    const char *name = NULL;
+    mrp_attr_t *attrs = NULL;
+
+    MRP_LUA_ENTER;
+
+    MRP_ASSERT(zone_attr_defs, "invocation prior to initialization");
+
+    if (!zone_attr_defs->attrs)
+        luaL_error(L, "attempt to create zone before defining attributes");
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case ATTRIBUTES:
+            attrs = check_attrs(L, -1, zone_attr_defs);
+            break;
+
+        default:
+            luaL_error(L, "unexpected field '%s'", fldnam);
+            break;
+        }
+    }
+
+    if (!name)
+        luaL_error(L, "missing or wrong name field");
+    if ((id = mrp_zone_create(name,attrs)) == MRP_ZONE_ID_INVALID)
+        luaL_error(L, "failed to create zone");
+
+    free_attrs(attrs);
+
+    zone = (zone_t *)mrp_lua_create_object(L, ZONE_CLASS, name,0);
+
+    if (!zone)
+        luaL_error(L, "invalid or duplicate name '%s'", name);
+    else {
+        zone->id = id;
+        zone->name = name;
+        zone->attr_tbl = attributes_create(L, ZONE,zone, zone_attr_defs,
+                                           fetch_zone_attribute,
+                                           update_zone_attribute);
+
+        mrp_log_info("zone '%s' created", name);
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int zone_getfield(lua_State *L)
+{
+    zone_t  *zone = to_zone(L, 1);
+    field_t  fld  = field_check(L, 2, NULL);
+
+    MRP_LUA_ENTER;
+
+    lua_pop(L, 1);
+
+    if (!zone) {
+        /* attempt to access a zone definition field */
+        switch (fld) {
+        case ATTRIBUTES:    mrp_lua_push_object(L, zone_attr_defs);     break;
+        default:            lua_pushnil(L);                             break;
+        }
+    }
+    else {
+        /* attempt to access a zone instance field */
+        switch (fld) {
+        case ATTRIBUTES:     push_attributes(L, zone->attr_tbl);        break;
+        case ID:             lua_pushinteger(L, zone->id + 1);          break;
+        case NAME:           lua_pushstring(L, zone->name);             break;
+        default:             lua_pushnil(L);                            break;
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int zone_setfield(lua_State *L)
+{
+    zone_t *zone = to_zone(L, 1);
+    field_t fld  = field_check(L, 2, NULL);
+
+    MRP_LUA_ENTER;
+
+    if (zone || fld != ATTRIBUTES)
+        luaL_error(L, "zones can't be exetended after definition");
+
+    MRP_LUA_LEAVE(0);
+}
+
+static void zone_destroy(void *data)
+{
+    /* zone_t *zone = (zone_t *)data; */
+
+    MRP_UNUSED(data);
+
+    MRP_LUA_ENTER;
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+#if 0
+static zone_t *check_zone(lua_State *L, int idx)
+{
+    return (zone_t *)mrp_lua_check_object(L, ZONE_CLASS, idx);
+}
+#endif
+
+static zone_t *to_zone(lua_State *L, int idx)
+{
+    return (zone_t *)mrp_lua_to_object(L, ZONE_CLASS, idx);
+}
+
+static int zone_attr_create(lua_State *L)
+{
+    mrp_attr_def_t *attrs;
+    int nattr;
+
+    MRP_LUA_ENTER;
+
+    MRP_ASSERT(zone_attr_defs, "invocation prior to initialization");
+
+    if (zone_attr_defs->attrs)
+        luaL_error(L, "zone attributes already defined");
+    else {
+        attrs = check_attrdefs(L, 2, &nattr);
+
+        mrp_zone_definition_create(attrs);
+
+        zone_attr_defs->nattr = nattr;
+        zone_attr_defs->attrs = attrs;
+    }
+
+    mrp_lua_push_object(L, zone_attr_defs);
+
+    mrp_log_info("zone attributes defined");
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int  zone_attr_getfield(lua_State *L)
+{
+    zone_t *zone;
+    int idx;
+
+    MRP_LUA_ENTER;
+
+    MRP_ASSERT(zone_attr_defs, "invocation prior to initialization");
+
+    if (!(zone = to_zone(L, 1))) {
+        mrp_debug("zone attribute definition => attribute index");
+        if ((idx = check_attrindex(L, 2, zone_attr_defs)) < 0)
+            lua_pushnil(L);
+        else
+            lua_pushinteger(L, idx);
+    }
+    else {
+        mrp_debug("zone attribute => nil");
+        lua_pushnil(L);
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int  zone_attr_setfield(lua_State *L)
+{
+    MRP_UNUSED(L);
+
+    MRP_LUA_ENTER;
+
+    MRP_LUA_LEAVE(0);
+}
+
+
+static void zone_attr_destroy(void *data)
+{
+    /* attr_def_t *attr = (attr_def_t *)data; */
+
+    MRP_UNUSED(data);
+
+    MRP_LUA_ENTER;
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+#if 0
+static attr_def_t *check_zone_attr(lua_State *L, int idx)
+{
+    return (attr_def_t *)mrp_lua_check_object(L, ZONE_ATTR_CLASS, idx);
+}
+#endif
+
+#if 0
+static attr_def_t *to_zone_attr(lua_State *L, int idx)
+{
+    return (attr_def_t *)mrp_lua_to_object(L, ZONE_ATTR_CLASS, idx);
+}
+#endif
+
+static bool fetch_zone_attribute(attr_t *attr, int idx, mrp_attr_t *retval)
+{
+    mrp_zone_t *z;
+    zone_t *zone;
+
+    if (attr->owner.type == ZONE && (zone = (zone_t *)attr->owner.data)) {
+        if ((z = mrp_zone_find_by_id(zone->id))) {
+            if (mrp_zone_read_attribute(z, idx, retval))
+                return true;
+        }
+    }
+
+    return false;
+}
+
+static bool update_zone_attribute(attr_t *attr, int idx, mrp_attr_t *value)
+{
+    mrp_zone_t *z;
+    zone_t *zone;
+
+    MRP_UNUSED(idx);
+    MRP_UNUSED(value);
+
+    if (attr->owner.type == ZONE && (zone = (zone_t *)attr->owner.data)) {
+        if ((z = mrp_zone_find_by_id(zone->id))) {
+#if 0
+            if (mrp_zone_write_attribute(z, idx, value))
+                return true;
+#endif
+        }
+    }
+
+    return false;
+}
+
+static int resclass_create_from_lua(lua_State *L)
+{
+    resclass_t *resclass;
+    size_t fldnamlen;
+    const char *fldnam;
+    int nattr = 0;
+    uint32_t id;
+    const char *name = NULL;
+    mrp_attr_def_t *attrs = NULL;
+    bool shareable = false;
+    mrp_resource_mgr_ftbl_t *ftbl = NULL;
+    void *mgrdata = NULL;
+    attr_def_t *adef;
+
+    MRP_LUA_ENTER;
+
+    MRP_LUA_FOREACH_FIELD(L, 2, fldnam, fldnamlen) {
+
+        switch (field_name_to_type(fldnam, fldnamlen)) {
+
+        case NAME:
+            name = mrp_strdup(luaL_checkstring(L, -1));
+            break;
+
+        case SHAREABLE:
+            luaL_argcheck(L, lua_isboolean(L,-1), 2, "attempt to assign "
+                          "non-boolean value to 'shareable' field");
+            shareable = lua_toboolean(L, -1);
+            break;
+
+        case ATTRIBUTES:
+            attrs = check_attrdefs(L, -1, &nattr);
+            break;
+
+        default:
+            luaL_error(L, "unexpected field '%s'", fldnam);
+            break;
+        }
+    }
+
+    if (!name)
+        luaL_error(L, "missing or wrong name field");
+
+    id = mrp_resource_definition_create(name, shareable, attrs,ftbl,mgrdata);
+
+    MRP_ASSERT(id < MRP_RESOURCE_MAX, "resource id is out of range");
+
+    if (id == MRP_RESOURCE_ID_INVALID)
+        luaL_error(L, "failed to register resource class '%s'", name);
+
+    resclass = (resclass_t *)mrp_lua_create_object(L, RESCLASS_CLASS, name,0);
+    adef = mrp_allocz(sizeof(attr_def_t));
+
+    if (!resclass)
+        luaL_error(L, "invalid or duplicate name '%s'", name);
+    if (!adef)
+        luaL_error(L, "failed to allocate memory for attribute definitions");
+
+    resclass->id = id;
+    resclass->name = name;
+    resclass->attrs = attrs;
+
+    adef->nattr = nattr;
+    adef->attrs = attrs;
+
+    resource_attr_defs[id] = adef;
+
+    mrp_log_info("resource class '%s' created", name);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int resclass_getfield(lua_State *L)
+{
+    resclass_t *rc = to_resclass(L, 1);
+    field_t fld = field_check(L, 2, NULL);
+    mrp_resource_def_t *rd;
+
+    MRP_LUA_ENTER;
+
+    lua_pop(L, 1);
+
+    if (!rc || !(rd = mrp_resource_definition_find_by_name(rc->name)))
+        lua_pushnil(L);
+    else {
+        switch (fld) {
+        case NAME:       lua_pushstring(L, rd->name);         break;
+        case ID:         lua_pushinteger(L, rd->id + 1);      break;
+        case SHAREABLE:  lua_pushboolean(L, rd->shareable);   break;
+        default:         lua_pushnil(L);                      break;
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int resclass_setfield(lua_State *L)
+{
+    MRP_LUA_ENTER;
+
+    luaL_error(L, "can't modify resource classes after definition");
+
+    MRP_LUA_LEAVE(1);
+}
+
+static void resclass_destroy(void *data)
+{
+    resclass_t *rc = (resclass_t *)data;
+
+    MRP_LUA_ENTER;
+
+    mrp_free((void *)rc->name);
+    free_attrdefs(rc->attrs);
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+#if 0
+static resclass_t *check_resclass(lua_State *L, int idx)
+{
+    return (resclass_t *)mrp_lua_check_object(L, RESCLASS_CLASS, idx);
+}
+#endif
+
+static resclass_t *to_resclass(lua_State *L, int idx)
+{
+    return (resclass_t *)mrp_lua_to_object(L, RESCLASS_CLASS, idx);
+}
+
+static int resource_getfield(lua_State *L)
+{
+    const char *name;
+    resource_t *res = check_resource(L, 1);
+    field_t fld = field_check(L, 2, &name);
+    mrp_resource_set_t *s;
+    mrp_resource_t *r;
+    mrp_resource_mask_t m;
+
+    MRP_LUA_ENTER;
+
+    switch (fld) {
+
+    case ATTRIBUTES:
+        push_attributes(L, res->attr_tbl);
+        break;
+
+    case SHARED:
+    case SHARE:
+        if (!(r = mrp_resource_set_find_resource(res->rsetid, res->name)))
+            lua_pushnil(L);
+        else
+            lua_pushboolean(L, r->shared);
+        break;
+
+    default:
+        if (!(s = mrp_resource_set_find_by_id(res->rsetid))) {
+            lua_pushnil(L);
+            break;
+        } else {
+            m = ((mrp_resource_mask_t)1) << res->resid;
+        }
+
+        switch (fld) {
+        case MANDATORY:
+            lua_pushboolean(L, s->resource.mask.mandatory & m ? true : false);
+            break;
+        case GRANT:
+            lua_pushboolean(L, s->resource.mask.grant & m ? true : false);
+            break;
+        default:
+            lua_pushnil(L);
+            break;
+        }
+
+        break;
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int resource_setfield(lua_State *L)
+{
+    MRP_UNUSED(L);
+
+    MRP_LUA_ENTER;
+
+    MRP_LUA_LEAVE(0);
+}
+
+static resource_t *check_resource(lua_State *L, int idx)
+{
+    return (resource_t *)luaL_checkudata(L, idx, RESOURCE_CLASSID);
+}
+
+static bool fetch_resource_attribute(attr_t *attr, int idx, mrp_attr_t *retval)
+{
+    mrp_resource_set_t *rset;
+    resource_t *resource;
+    mrp_attr_t *a;
+
+    if (attr->owner.type == RESOURCE) {
+        if ((resource = (resource_t *)attr->owner.data)) {
+            if ((rset = mrp_resource_set_find_by_id(resource->rsetid))) {
+                a = mrp_resource_set_read_attribute(rset, resource->name,
+                                                    idx, retval);
+                return a ? true : false;
+            }
+        }
+    }
+
+    return false;
+}
+
+
+static bool update_resource_attribute(attr_t *attr, int idx, mrp_attr_t *value)
+{
+    mrp_resource_set_t *rset;
+    resource_t *resource;
+    mrp_attr_t values[2];
+    int sts;
+
+    MRP_UNUSED(idx);
+
+    if (attr->owner.type == RESOURCE) {
+        if ((resource = (resource_t *)attr->owner.data)) {
+            if ((rset = mrp_resource_set_find_by_id(resource->rsetid))) {
+                memcpy(values + 0, value, sizeof(mrp_attr_t));
+                memset(values + 1, 0,     sizeof(mrp_attr_t));
+
+                sts = mrp_resource_set_write_attributes(rset, resource->name,
+                                                        values);
+                return (sts < 0) ? false : true;
+            }
+        }
+    }
+
+    return false;
+}
+
+static mrp_lua_resmethod_t *resmethod_create_from_c(lua_State *L)
+{
+    mrp_lua_resmethod_t *method = mrp_lua_create_object(L, RESMETHOD_CLASS,
+                                                        "method",0);
+
+    if (!method)
+        luaL_error(L, "invalid or duplicate name 'method'");
+
+    return method;
+}
+
+static int resmethod_create_from_lua(lua_State *L)
+{
+    MRP_LUA_ENTER;
+
+    luaL_error(L, "singleton object has already been created");
+
+    lua_pushnil(L);
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int resmethod_getfield(lua_State *L)
+{
+    const char *name;
+    mrp_lua_resmethod_t *method = to_resmethod(L, 1);
+    field_t fld = field_check(L, 2, &name);
+
+    MRP_LUA_ENTER;
+
+    lua_pop(L, 1);
+
+    if (!method) {
+        /* attempt to access a resclass or owners */
+        switch (fld) {
+        case METHOD:   mrp_lua_push_object(L, resource_methods);  break;
+        case OWNERS:   lua_pushstring(L,name); lua_rawget(L,1);   break;
+        default:       lua_pushnil(L);                            break;
+        }
+    }
+    else {
+        /* attempt to access a method member */
+        if (!resource_methods)
+            lua_pushnil(L);
+        else {
+            switch (fld) {
+            case VETO:
+            case RECALC:
+                lua_pushstring(L, name);
+                lua_rawget(L, 1);
+                break;
+            default:
+                lua_pushnil(L);
+                break;
+            }
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int resmethod_setfield(lua_State *L)
+{
+    const char *name;
+    mrp_lua_resmethod_t *method = to_resmethod(L, 1);
+    field_t fld = field_check(L, 2, &name);
+
+    MRP_LUA_ENTER;
+
+    if (method) {
+        switch (fld) {
+        case VETO:
+            lua_pushstring(L, name);
+            lua_pushvalue(L, 3);
+            method->veto = mrp_funcarray_check(L, -1);
+            lua_rawset(L, 1);
+            break;
+        default:
+            luaL_error(L, "invalid method '%s'", name);
+            break;
+        }
+    }
+
+    MRP_LUA_LEAVE(0);
+}
+
+static void resmethod_destroy(void *data)
+{
+    mrp_lua_resmethod_t *method = (mrp_lua_resmethod_t *)data;
+
+    MRP_LUA_ENTER;
+
+    method->veto = NULL;
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static mrp_lua_resmethod_t *to_resmethod(lua_State *L, int idx)
+{
+    return (mrp_lua_resmethod_t *)mrp_lua_to_object(L, RESMETHOD_CLASS, idx);
+}
+
+
+static mrp_attr_def_t *check_attrdefs(lua_State *L, int t, int *ret_len)
+{
+    mrp_attr_def_t attrdefs[128];
+    mrp_attr_def_t *ad, *end, *dup;
+    size_t i;
+    const char *name;
+    const char *string;
+    const char *access;
+    bool value_set;
+    int len;
+    size_t size;
+    size_t namlen;
+
+    t = (t < 0) ? lua_gettop(L) + t + 1 : t;
+
+    luaL_checktype(L, t, LUA_TTABLE);
+
+    end = (ad = attrdefs) + (MRP_ARRAY_SIZE(attrdefs) - 1);
+
+    MRP_LUA_FOREACH_FIELD(L, t, name, namlen) {
+        if (!name[0])
+            luaL_error(L, "invalid attribute definition");
+        if (ad >= end)
+            luaL_error(L, "too many attributes");
+
+        ad->name = mrp_strdup(name);
+        ad->type = mqi_error;
+        ad->access = MRP_RESOURCE_READ;
+
+        value_set = false;
+
+        luaL_checktype(L, -1, LUA_TTABLE);
+
+
+        for (lua_pushnil(L);  lua_next(L, -2);  lua_pop(L, 1)) {
+            if (lua_type(L, -2) != LUA_TNUMBER)
+                goto error;
+
+            i = lua_tointeger(L, -2);
+
+            switch (i) {
+            case 1:
+                ad->type = lua_tointeger(L, -1);
+                break;
+            case 2:
+                switch (ad->type) {
+                case mqi_string:
+                    if ((string = lua_tostring(L, -1))) {
+                        ad->value.string = mrp_strdup(string);
+                        value_set = true;
+                    }
+                    break;
+                case mqi_integer:
+                    ad->value.integer = lua_tointeger(L, -1);
+                    value_set = true;
+                    break;
+                case mqi_unsignd:
+                    ad->value.integer = lua_tointeger(L, -1);
+                    value_set = true;
+                    break;
+                case mqi_floating:
+                    ad->value.floating = lua_tonumber(L, -1);
+                    value_set = true;
+                    break;
+                default:
+                    break;
+                }
+                break;
+            case 3:
+                if (!(access = lua_tostring(L, -1)))
+                    ad->type = mqi_error;
+                else {
+                    if (!strcasecmp(access, "read"))
+                        ad->access = MRP_RESOURCE_READ;
+                    else if (!strcasecmp(access, "write"))
+                        ad->access = MRP_RESOURCE_WRITE;
+                    else if (!strcasecmp(access, "rw"))
+                        ad->access = MRP_RESOURCE_RW;
+                    else
+                        ad->type = mqi_error;
+                }
+                break;
+            default:
+                ad->type = mqi_error;
+                break;
+            }
+        } /* for */
+
+        if (!value_set ||
+            (ad->type != mqi_string  &&
+             ad->type != mqi_integer &&
+             ad->type != mqi_unsignd &&
+             ad->type != mqi_floating ))
+            goto error;
+
+        ad++;
+    } /* foreach */
+
+    memset(ad, 0, sizeof(mrp_attr_def_t));
+
+    len  = ad - attrdefs;
+    size = sizeof(mrp_attr_def_t) * (len+1);
+    dup  = mrp_alloc(size);
+
+    if (!dup)
+        luaL_error(L, "failed to allocate %u byte memory", size);
+
+    memcpy(dup, attrdefs, size);
+
+    if (ret_len)
+        *ret_len = len;
+
+    return dup;
+
+ error:
+    luaL_argerror(L, t, "malformed attribute definition");
+    return NULL;
+}
+
+
+static void free_attrdefs(mrp_attr_def_t *attrdefs)
+{
+    mrp_attr_def_t *ad;
+
+    if (attrdefs) {
+        for (ad = attrdefs;  ad->name;  ad++) {
+            mrp_free((void *)ad->name);
+            if (ad->type == mqi_string)
+                mrp_free((void *)ad->value.string);
+        }
+        mrp_free(attrdefs);
+    }
+}
+
+static int attr_name_to_index(const char *name, attr_def_t *def)
+{
+    mrp_attr_def_t *attrs = def->attrs;
+    int idx;
+
+    for (idx = 0;  idx < def->nattr;  idx++) {
+        if (!strcmp(name, attrs[idx].name))
+            return idx;
+    }
+
+    return -1;
+}
+
+static mrp_attr_t *check_attrs(lua_State *L, int t, attr_def_t *defs)
+{
+    mrp_attr_t attr[128];
+    mrp_attr_t *at, *end, *dup;
+    const char *name;
+    size_t namlen;
+    size_t len;
+    size_t size;
+    int i;
+
+    t = (t < 0) ? lua_gettop(L) + t + 1 : t;
+
+    luaL_checktype(L, t, LUA_TTABLE);
+
+    end = (at = attr) + (MRP_ARRAY_SIZE(attr) - 1);
+
+    MRP_LUA_FOREACH_FIELD(L, t, name, namlen) {
+        if (!name[0])
+            luaL_error(L, "invalid attribute definition");
+        if (at >= end)
+            luaL_error(L, "too many attributes");
+        if ((i = attr_name_to_index(name, defs)) < 0)
+            luaL_error(L, "attribute %s do not exist", name);
+
+        at->name = mrp_strdup(name);
+
+        switch ((at->type = defs->attrs[i].type)) {
+        case mqi_string:
+            at->value.string = mrp_strdup(luaL_checkstring(L,-1));
+            break;
+        case mqi_integer:
+            at->value.integer = luaL_checkinteger(L,-1);
+            break;
+        case mqi_unsignd:
+            if ((at->value.integer = luaL_checkinteger(L,-1)) < 0)
+                luaL_error(L, "attempt to give negative value to an "
+                           "unsigned integer");
+            break;
+        default:
+            luaL_error(L, "Internal error: invalid type for attribute");
+            break;
+        }
+
+        at++;
+    }
+
+    memset(at, 0, sizeof(mrp_attr_t));
+
+    len  = at - attr;
+    size = sizeof(mrp_attr_t) * (len + 1);
+
+    dup = mrp_alloc(size);
+    memcpy(dup, attr, size);
+
+    return dup;
+}
+
+
+static void free_attrs(mrp_attr_t *attrs)
+{
+    mrp_attr_t *at;
+
+    if (attrs) {
+        for (at = attrs;  at->name;  at++) {
+            mrp_free((void *)at->name);
+            if (at->type == mqi_string)
+                mrp_free((void *)at->value.string);
+        }
+        mrp_free(attrs);
+    }
+}
+
+
+static int check_attrindex(lua_State *L, int arg, attr_def_t *def)
+{
+    const char *name;
+    int idx;
+
+    if (!def || !def->attrs)
+        return -1;
+
+    switch (lua_type(L, arg)) {
+    case LUA_TNUMBER:
+        idx = lua_tointeger(L, arg);
+        return (idx >= 0 && idx < def->nattr) ? idx : -1;
+    case LUA_TSTRING:
+        name = lua_tostring(L, arg);
+        return attr_name_to_index(name, def);
+    default:
+        return -1;
+    }
+}
+
+
+static int check_boolean(lua_State *L, int idx)
+{
+    if (!lua_isboolean(L, idx))
+        luaL_argerror(L, idx, "expected boolean");
+    return lua_toboolean(L, idx) ? 1 : 0;
+}
+
+static mrp_resource_order_t check_order(lua_State *L, int idx)
+{
+    const char *str = luaL_checkstring(L, idx);
+
+    if (!strcasecmp(str, "fifo"))
+        return MRP_RESOURCE_ORDER_FIFO;
+
+    if (!strcasecmp(str, "lifo"))
+        return MRP_RESOURCE_ORDER_LIFO;
+
+    luaL_error(L, "invalid value for order ('fifo' or 'lifo' accepted only)");
+
+    return MRP_RESOURCE_ORDER_UNKNOWN;
+}
+
+static int push_order(lua_State *L, mrp_resource_order_t order)
+{
+    const char *str;
+
+    switch (order) {
+    case MRP_RESOURCE_ORDER_FIFO:   str = "fifo";        break;
+    case MRP_RESOURCE_ORDER_LIFO:   str = "lifo";        break;
+    default:                        str = "<unknown>";   break;
+    }
+
+    lua_pushstring(L, str);
+
+    return 1;
+}
+
+
+static field_t field_check(lua_State *L, int idx, const char **ret_fldnam)
+{
+    const char *fldnam;
+    size_t fldnamlen;
+    field_t fldtyp;
+
+    if (!(fldnam = lua_tolstring(L, idx, &fldnamlen)))
+        fldtyp = 0;
+    else
+        fldtyp = field_name_to_type(fldnam, fldnamlen);
+
+    if (ret_fldnam)
+        *ret_fldnam = fldnam;
+
+    return fldtyp;
+}
+
+
+static field_t field_name_to_type(const char *name, size_t len)
+{
+    switch (len) {
+
+    case 2:
+        if (!strcmp(name, "id"))
+            return ID;
+        break;
+
+    case 4:
+        if (!strcmp(name, "name"))
+            return NAME;
+        if (!strcmp(name, "veto"))
+            return VETO;
+        break;
+
+    case 5:
+        if (!strcmp(name, "class"))
+            return CLASS;
+        if (!strcmp(name, "modal"))
+            return MODAL;
+        if (!strcmp(name, "share"))
+            return SHARE;
+        if (!strcmp(name, "grant"))
+            return GRANT;
+        if (!strcmp(name, "order"))
+            return ORDER;
+        break;
+
+    case 6:
+        if (!strcmp(name, "method"))
+            return METHOD;
+        if (!strcmp(name, "owners"))
+            return OWNERS;
+        if (!strcmp(name, "shared"))
+            return SHARED;
+        if (!strcmp(name, "recalc"))
+            return RECALC;
+        break;
+
+    case 8:
+        if (!strcmp(name, "priority"))
+            return PRIORITY;
+        break;
+
+    case 9:
+        if (!strcmp(name, "mandatory"))
+            return MANDATORY;
+        if (!strcmp(name, "shareable"))
+            return SHAREABLE;
+        break;
+
+    case 10:
+        if (!strcmp(name, "attributes"))
+            return ATTRIBUTES;
+        break;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+static int method_recalc(lua_State *L)
+{
+    const char *zone_name;
+    mrp_zone_t *zone;
+
+    if (lua_type(L, 1) == LUA_TSTRING && (zone_name = lua_tostring(L, 1))) {
+
+        if (!(zone = mrp_zone_find_by_name(zone_name))) {
+            luaL_error(L, "can't recalculate resources in zone '%s': "
+                       "no such zone", zone_name);
+        }
+
+        mrp_resource_owner_recalc(zone->id);
+    }
+
+    return 0;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/config-lua.h b/src/resource/config-lua.h
new file mode 100644 (file)
index 0000000..e65ff77
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_CONFIG_LUA_H__
+#define __MURPHY_RESOURCE_CONFIG_LUA_H__
+
+#include <lua.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/resource/data-types.h>
+
+typedef struct mrp_lua_resmethod_s   mrp_lua_resmethod_t;
+
+struct mrp_lua_resmethod_s {
+    mrp_funcarray_t *veto;
+};
+
+
+mrp_lua_resmethod_t *mrp_lua_get_resource_methods(void);
+
+uint32_t mrp_lua_to_resource_id(lua_State *, int);
+
+int mrp_lua_resource_create(lua_State *, mrp_resource_t *);
+
+
+#endif  /* __MURPHY_RESOURCE_CONFIG_LUA_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/data-types.h b/src/resource/data-types.h
new file mode 100644 (file)
index 0000000..7a347a0
--- /dev/null
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_DATA_TYPES_H__
+#define __MURPHY_DATA_TYPES_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <murphy-db/mqi-types.h>
+
+/*
+ * Remember: this should be smaller than
+ * sizeof(mrp_zone_mask_t) * 8
+ */
+#define MRP_ZONE_MAX            8
+#define MRP_ZONE_MASK           (((mrp_zone_mask_t)1 << MRP_ZONE_MAX) - 1)
+
+#define MRP_KEY_STAMP_BITS      27
+#define MRP_KEY_STATE_BITS      1
+#define MRP_KEY_USAGE_BITS      1
+#define MRP_KEY_PRIORITY_BITS   3
+
+#define MRP_ZONE_ID_INVALID        (~(uint32_t)0)
+#define MRP_RESOURCE_ID_INVALID    (~(uint32_t)0)
+#define MRP_RESOURCE_REQNO_INVALID (~(uint32_t)0)
+
+#define MRP_RESOURCE_MAX  (sizeof(mrp_resource_mask_t) * 8)
+#define MRP_ATTRIBUTE_MAX (sizeof(mrp_attribute_mask_t) * 8)
+
+typedef enum   mrp_resource_state_e     mrp_resource_state_t;
+typedef enum   mrp_resource_order_e     mrp_resource_order_t;
+typedef enum   mrp_resource_access_e    mrp_resource_access_t;
+typedef enum   mrp_resource_event_e     mrp_resource_event_t;
+
+typedef struct mrp_resource_client_s    mrp_resource_client_t;
+typedef union  mrp_attr_value_u         mrp_attr_value_t;
+typedef struct mrp_attr_def_s           mrp_attr_def_t;
+typedef struct mrp_attr_s               mrp_attr_t;
+typedef struct mrp_zone_def_s           mrp_zone_def_t;
+typedef struct mrp_zone_s               mrp_zone_t;
+typedef struct mrp_application_class_s  mrp_application_class_t;
+typedef struct mrp_resource_owner_s     mrp_resource_owner_t;
+typedef struct mrp_resource_set_s       mrp_resource_set_t;
+typedef struct mrp_resource_def_s       mrp_resource_def_t;
+typedef struct mrp_resource_s           mrp_resource_t;
+typedef struct mrp_resource_mgr_ftbl_s  mrp_resource_mgr_ftbl_t;
+typedef struct mrp_resource_mgr_s       mrp_resource_mgr_t;
+
+typedef struct mrp_resource_ownersref_s mrp_resource_ownersref_t;
+typedef struct mrp_resource_setref_s    mrp_resource_setref_t;
+
+typedef uint32_t                        mrp_resource_mask_t;
+typedef uint32_t                        mrp_attribute_mask_t;
+typedef uint32_t                        mrp_zone_mask_t;
+
+
+enum mrp_resource_state_e {
+    mrp_resource_no_request = 0,
+    mrp_resource_release,
+    mrp_resource_acquire,
+};
+
+
+enum mrp_resource_access_e {
+    MRP_RESOURCE_ACCESS_NONE  = 0,
+    MRP_RESOURCE_READ  = 1,
+    MRP_RESOURCE_WRITE = 2,
+    MRP_RESOURCE_RW    = (MRP_RESOURCE_READ | MRP_RESOURCE_WRITE)
+};
+
+enum mrp_resource_order_e {
+    MRP_RESOURCE_ORDER_UNKNOWN = 0,
+    MRP_RESOURCE_ORDER_FIFO,
+    MRP_RESOURCE_ORDER_LIFO
+};
+
+union mrp_attr_value_u {
+    const char  *string;
+    int32_t      integer;
+    uint32_t     unsignd;
+    double       floating;
+};
+
+struct mrp_attr_def_s {
+    const char            *name;
+    mrp_resource_access_t  access;
+    mqi_data_type_t        type;
+    mrp_attr_value_t       value;
+};
+
+struct mrp_attr_s {
+    const char      *name;
+    mqi_data_type_t  type;
+    mrp_attr_value_t value;
+};
+
+
+typedef void (*mrp_resource_event_cb_t)(uint32_t, mrp_resource_set_t *, void*);
+
+enum mrp_resource_event_e {
+    MRP_RESOURCE_EVENT_UNKNOWN = 0,
+    MRP_RESOURCE_EVENT_CREATED,
+    MRP_RESOURCE_EVENT_DESTROYED,
+    MRP_RESOURCE_EVENT_ACQUIRE,
+    MRP_RESOURCE_EVENT_RELEASE
+};
+
+typedef void (*mrp_manager_notify_func_t)(mrp_resource_event_t, mrp_zone_t *,
+                                          mrp_application_class_t *,
+                                          mrp_resource_t *, void *);
+typedef void (*mrp_manager_init_func_t)(mrp_zone_t *, void *);
+typedef bool (*mrp_manager_alloc_func_t)(mrp_zone_t *,mrp_resource_t *,void*);
+typedef void (*mrp_manager_free_func_t)(mrp_zone_t *,mrp_resource_t *,void *);
+typedef bool (*mrp_manager_advice_func_t)(mrp_zone_t *,mrp_resource_t*,void *);
+typedef void (*mrp_manager_commit_func_t)(mrp_zone_t *, void *);
+
+struct mrp_resource_mgr_ftbl_s  {
+    mrp_manager_notify_func_t   notify;
+    mrp_manager_init_func_t     init;
+    mrp_manager_alloc_func_t    allocate;
+    mrp_manager_free_func_t     free;
+    mrp_manager_advice_func_t   advice;
+    mrp_manager_commit_func_t   commit;
+};
+
+
+
+#endif  /* __MURPHY_DATA_TYPES_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/lua-resource.c b/src/resource/lua-resource.c
new file mode 100644 (file)
index 0000000..928c362
--- /dev/null
@@ -0,0 +1,1292 @@
+/*
+ * Copyright (c) 2013, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <murphy/common.h>
+#include <murphy/common/debug.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mm.h>
+#include <murphy/common/mainloop.h>
+#include <murphy/core/lua-utils/object.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-bindings/murphy.h>
+
+#include <murphy/resource/client-api.h>
+
+#define MAX_ATTRS 128
+
+/*
+ * Bindings to the resource library
+ */
+
+#define ATTRIBUTE_LUA_CLASS MRP_LUA_CLASS(attribute, lua)
+#define RESOURCE_LUA_CLASS MRP_LUA_CLASS(resource, lua)
+#define RESOURCE_SET_LUA_CLASS MRP_LUA_CLASS(resource_set, lua)
+
+typedef struct resource_lua_s resource_lua_t;
+typedef struct resource_set_lua_s resource_set_lua_t;
+typedef struct attribute_lua_s attribute_lua_t;
+
+struct attribute_lua_s {
+    lua_State *L; /* Lua execution context */
+
+    resource_set_lua_t *resource_set;
+
+    int initialized;
+    resource_lua_t *parent;
+};
+
+struct resource_lua_s {
+    lua_State *L; /* Lua execution context */
+
+    bool available; /* status */
+    bool acquired; /* status*/
+    bool shared; /* bookkeeping */
+    bool mandatory; /* bookkeeping */
+    char *resource_name; /* bookkeeping */
+
+    resource_set_lua_t *parent; /* resource set the resource belongs to */
+
+    int attributes; /* for lua bindings */
+    attribute_lua_t *real_attributes; /* real attributes */
+};
+
+struct resource_set_lua_s {
+    lua_State *L; /* Lua execution context */
+
+    mrp_resource_set_t *resource_set; /* associated murphy resource set */
+
+    int id; /* resource set internal id */
+    int callback; /* callback */
+    bool available; /* status */
+    bool acquired; /* status */
+    bool autorelease; /* input */
+    bool dont_wait; /* input */
+    char *zone; /* input */
+    char *application_class; /* input */
+    int32_t priority; /* input */
+
+    bool committed; /* resource set is locked and cannot be edited anymore */
+    bool initialized; /* resource set was returned to the Lua user */
+
+    mrp_htbl_t *resources;
+};
+
+static mrp_resource_client_t *client = NULL;
+uint32_t n_sets = 0;
+
+/* resource set */
+
+static int resource_set_lua_create(lua_State *L);
+
+static int resource_set_get_resources(void *data, lua_State *L, int member,
+        mrp_lua_value_t *v);
+static int resource_set_get_id(void *data, lua_State *L, int member,
+        mrp_lua_value_t *v);
+static int resource_set_add_resource(lua_State *L);
+static int resource_set_acquire(lua_State *L);
+static int resource_set_release(lua_State *L);
+
+static void resource_set_lua_changed(void *data, lua_State *L, int member);
+static void resource_set_lua_destroy(void *data);
+static int resource_set_lua_stringify(lua_State *L);
+
+#define RS_OFFS(m)   MRP_OFFSET(resource_set_lua_t, m)
+#define RS_RDONLY    MRP_LUA_CLASS_READONLY
+#define RS_NOTIFY    MRP_LUA_CLASS_NOTIFY
+#define RS_NOFLAGS   MRP_LUA_CLASS_NOFLAGS
+#define RS_RAWGETTER MRP_LUA_CLASS_RAWGETTER
+
+MRP_LUA_METHOD_LIST_TABLE(resource_set_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(resource_set_lua_create)
+                          MRP_LUA_METHOD(addResource, resource_set_add_resource)
+                          MRP_LUA_METHOD(acquire, resource_set_acquire)
+                          MRP_LUA_METHOD(release, resource_set_release));
+
+MRP_LUA_METHOD_LIST_TABLE(resource_set_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (resource_set_lua_create)
+                          MRP_LUA_OVERRIDE_STRINGIFY(resource_set_lua_stringify));
+
+MRP_LUA_MEMBER_LIST_TABLE(resource_set_lua_members,
+    MRP_LUA_CLASS_INTEGER("id", RS_OFFS(id), NULL, resource_set_get_id, RS_RDONLY)
+    MRP_LUA_CLASS_STRING("application_class", RS_OFFS(application_class), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_STRING("zone", RS_OFFS(zone), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_ANY("resources", RS_OFFS(resources), NULL, resource_set_get_resources, RS_RDONLY | RS_RAWGETTER)
+    MRP_LUA_CLASS_BOOLEAN("dont_wait", RS_OFFS(dont_wait), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("autorelease", RS_OFFS(autorelease), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("available", RS_OFFS(available), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("acquired", RS_OFFS(acquired), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_INTEGER("priority", RS_OFFS(priority), NULL, NULL, RS_RDONLY)
+    MRP_LUA_CLASS_LFUNC("callback", RS_OFFS(callback), NULL, NULL, RS_NOTIFY));
+
+/* be careful! needs to be in the same order as the member list table */
+typedef enum {
+    RESOURCE_SET_MEMBER_ID,
+    RESOURCE_SET_MEMBER_APPLICATION_CLASS,
+    RESOURCE_SET_MEMBER_ZONE,
+    RESOURCE_SET_MEMBER_RESOURCES,
+    RESOURCE_SET_MEMBER_DONT_WAIT,
+    RESOURCE_SET_MEMBER_AUTORELEASE,
+    RESOURCE_SET_MEMBER_AVAILABLE,
+    RESOURCE_SET_MEMBER_ACQUIRED,
+    RESOURCE_SET_MEMBER_PRIORITY,
+    RESOURCE_SET_MEMBER_CALLBACK,
+} resource_set_member_t;
+
+MRP_LUA_DEFINE_CLASS(resource_set, lua, resource_set_lua_t, resource_set_lua_destroy,
+                          resource_set_lua_methods, resource_set_lua_overrides,
+                          resource_set_lua_members, NULL, resource_set_lua_changed, NULL, NULL,
+                          MRP_LUA_CLASS_EXTENSIBLE | MRP_LUA_CLASS_DYNAMIC);
+
+/* resource */
+
+static int resource_lua_create(lua_State *L);
+static void resource_lua_destroy(void *data);
+static int resource_lua_stringify(lua_State *L);
+static void resource_lua_changed(void *data, lua_State *L, int member);
+
+/* get the whole attribute table */
+static int resource_get_attributes(void *data, lua_State *L, int member, mrp_lua_value_t *v);
+static int resource_set_attributes(void *data, lua_State *L, int member, mrp_lua_value_t *v);
+
+#define R_OFFS(m)   MRP_OFFSET(resource_lua_t, m)
+#define R_RDONLY    MRP_LUA_CLASS_READONLY
+#define R_NOTIFY    MRP_LUA_CLASS_NOTIFY
+#define R_NOFLAGS   MRP_LUA_CLASS_NOFLAGS
+#define R_RAWGETTER MRP_LUA_CLASS_RAWGETTER
+#define R_RAWSETTER MRP_LUA_CLASS_RAWSETTER
+
+MRP_LUA_METHOD_LIST_TABLE(resource_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(resource_lua_create));
+
+MRP_LUA_METHOD_LIST_TABLE(resource_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (resource_lua_create)
+                          MRP_LUA_OVERRIDE_STRINGIFY(resource_lua_stringify));
+
+MRP_LUA_MEMBER_LIST_TABLE(resource_lua_members,
+    MRP_LUA_CLASS_ANY("attributes", R_OFFS(attributes), resource_set_attributes, resource_get_attributes, R_RAWGETTER | R_RAWSETTER)
+    MRP_LUA_CLASS_STRING("resource_name", R_OFFS(resource_name), NULL, NULL, R_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("available", R_OFFS(available), NULL, NULL, R_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("acquired", R_OFFS(acquired), NULL, NULL, R_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("shared", R_OFFS(shared), NULL, NULL, R_RDONLY)
+    MRP_LUA_CLASS_BOOLEAN("mandatory", R_OFFS(mandatory), NULL, NULL, R_RDONLY));
+
+/* be careful! needs to be in the same order as the member list table */
+typedef enum {
+    RESOURCE_MEMBER_ATTRIBUTES,
+    RESOURCE_MEMBER_RESOURCE_NAME,
+    RESOURCE_MEMBER_AVAILABLE,
+    RESOURCE_MEMBER_ACQUIRED,
+    RESOURCE_MEMBER_SHARED,
+    RESOURCE_MEMBER_MANDATORY,
+} resource_member_t;
+
+MRP_LUA_DEFINE_CLASS(resource, lua, resource_lua_t, resource_lua_destroy,
+                          resource_lua_methods, resource_lua_overrides,
+                          resource_lua_members, NULL, resource_lua_changed, NULL, NULL,
+                          MRP_LUA_CLASS_NOFLAGS);
+
+
+/* attribute table */
+
+/* The problem is that attribute table is a member of resource, meaning that
+   we will get events when the full attribute table is changed but won't get
+   any when a unique element of the attribute table is accessed. The solution
+   is to have the attributes handled as an object, whose constructor initializes
+   the attributes from the resource library. Any access to an attribute will
+   be seen as an access to a member and intercepted with "changed". */
+
+static int attribute_lua_create(lua_State *L);
+static void attribute_lua_destroy(void *data);
+static int attribute_lua_stringify(lua_State *L);
+static void attribute_lua_changed(void *data, lua_State *L, int member);
+static int attribute_lua_getfield(lua_State *L);
+static int attribute_lua_setfield(lua_State *L);
+
+#define A_OFFS(m)   MRP_OFFSET(attribute_lua_t, m)
+#define A_RDONLY    MRP_LUA_CLASS_READONLY
+#define A_NOTIFY    MRP_LUA_CLASS_NOTIFY
+#define A_NOFLAGS   MRP_LUA_CLASS_NOFLAGS
+#define A_RAWGETTER MRP_LUA_CLASS_RAWGETTER
+#define A_RAWSETTER MRP_LUA_CLASS_RAWSETTER
+
+MRP_LUA_METHOD_LIST_TABLE(attribute_lua_methods,
+                          MRP_LUA_METHOD_CONSTRUCTOR(attribute_lua_create));
+
+MRP_LUA_METHOD_LIST_TABLE(attribute_lua_overrides,
+                          MRP_LUA_OVERRIDE_CALL     (attribute_lua_create)
+                          MRP_LUA_OVERRIDE_STRINGIFY(attribute_lua_stringify)
+                          MRP_LUA_OVERRIDE_GETFIELD (attribute_lua_getfield)
+                          MRP_LUA_OVERRIDE_SETFIELD (attribute_lua_setfield));
+
+/* "initialized" is a placeholder */
+
+MRP_LUA_MEMBER_LIST_TABLE(attribute_lua_members,
+    MRP_LUA_CLASS_BOOLEAN("initialized", A_OFFS(initialized), NULL, NULL, A_RDONLY));
+
+/* be careful! needs to be in the same order as the member list table */
+typedef enum {
+    ATTRIBUTE_MEMBER_INITIALIZED,
+} attribute_member_t;
+
+MRP_LUA_DEFINE_CLASS(attribute, lua, attribute_lua_t, attribute_lua_destroy,
+                          attribute_lua_methods, attribute_lua_overrides,
+                          attribute_lua_members, NULL, attribute_lua_changed, NULL, NULL,
+                          MRP_LUA_CLASS_NOFLAGS);
+
+/* resource set */
+
+static inline resource_set_lua_t *resource_set_lua_check(lua_State *L, int idx)
+{
+    return (resource_set_lua_t *) mrp_lua_check_object(L, RESOURCE_SET_LUA_CLASS, idx);
+}
+
+static int resource_set_add_resource(lua_State *L)
+{
+    int narg;
+    resource_set_lua_t *rset;
+    resource_lua_t *resource;
+    const char *resource_name;
+    bool shared = FALSE;
+    bool mandatory = TRUE;
+    mrp_attr_t attribute_list[MAX_ATTRS], *attrs;
+
+    mrp_debug("> add_resource");
+
+    narg = lua_gettop(L);
+
+    if (narg != 2)
+        return luaL_error(L, "expecting one argument");
+
+    rset = resource_set_lua_check(L, 1);
+
+    if (!rset)
+        goto error;
+
+    /* the argument should be a table with at least "resource_name" index */
+
+    if (!lua_istable(L, -1))
+        return luaL_error(L, "argument error -- not a table");
+
+    lua_pushstring(L, "resource_name");
+    lua_gettable(L, -2);
+
+    if (!lua_isstring(L, -1))
+        return luaL_error(L, "'resource_name' is a mandatory field");
+
+    resource_name = lua_tostring(L, -1);
+    lua_pop(L, 1);
+
+    lua_pushstring(L, "mandatory");
+    lua_gettable(L, -2);
+
+    if (lua_isboolean(L, -1)) {
+        mandatory = lua_toboolean(L, -1);
+    }
+    lua_pop(L, 1);
+
+    lua_pushstring(L, "shared");
+    lua_gettable(L, -2);
+
+    if (lua_isboolean(L, -1)) {
+        shared = lua_toboolean(L, -1);
+    }
+    lua_pop(L, 1);
+
+    /* create resource object and add it to the resource table in the resource
+     * set object */
+
+    resource = (resource_lua_t *) mrp_lua_create_object(L, RESOURCE_LUA_CLASS,
+            NULL, 0);
+
+    if (!resource)
+        goto error;
+
+    /* mrp_lua_object_ref_value(resource, L, 0); */
+
+    resource->mandatory = mandatory;
+    resource->shared = shared;
+    resource->acquired = FALSE;
+    resource->available = FALSE;
+    resource->resource_name = mrp_strdup(resource_name);
+
+    if (!resource->resource_name)
+        goto error;
+
+    resource->parent = rset;
+    resource->L = L;
+
+    resource->real_attributes = (attribute_lua_t *) mrp_lua_create_object(L,
+            ATTRIBUTE_LUA_CLASS, NULL, 0);
+
+    if (!resource->real_attributes)
+        goto error;
+
+    /* mrp_lua_object_ref_value(resource->real_attributes, L, 0); */
+
+    resource->real_attributes->L = L;
+    resource->real_attributes->parent = resource;
+    resource->real_attributes->resource_set = rset;
+    resource->real_attributes->initialized = TRUE;
+
+    attrs = mrp_resource_set_read_all_attributes(rset->resource_set,
+            resource->resource_name, MAX_ATTRS-1, attribute_list);
+
+    if (mrp_resource_set_add_resource(rset->resource_set,
+            resource->resource_name, shared, attrs, mandatory) < 0)
+        goto error;
+
+    /* add to resource map */
+
+    mrp_debug("inserted resource %s to %p", resource->resource_name, rset);
+    mrp_htbl_insert(rset->resources, resource->resource_name, resource);
+
+    return 0;
+
+error:
+    /* TODO: clean up the already allocated objects */
+
+    return luaL_error(L, "internal resource library error");
+}
+
+static int resource_set_acquire(lua_State *L)
+{
+    resource_set_lua_t *rset;
+
+    mrp_debug("acquire");
+
+    rset = resource_set_lua_check(L, 1);
+
+    if (!rset)
+        return luaL_error(L, "internal error");
+
+    if (!rset->committed) {
+
+        /* Application backend requires us to "commit" the resource set before
+         * we can use the resource set. It can be done only after all resources
+         * have been added to the resource set and all the attributes
+         * configured. */
+
+        if (mrp_application_class_add_resource_set(rset->application_class,
+                rset->zone, rset->resource_set, 0) < 0)
+                return luaL_error(L, "failed to commit the resource set");
+
+        rset->committed = TRUE;
+    }
+
+    mrp_resource_set_acquire(rset->resource_set, 0);
+
+    return 0;
+}
+
+static int resource_set_release(lua_State *L)
+{
+    resource_set_lua_t *rset;
+
+    mrp_debug("> release");
+
+    rset = resource_set_lua_check(L, 1);
+
+    if (!rset)
+        return luaL_error(L, "internal error");
+
+    if (!rset->committed) {
+
+        /* Committing the resource set here means that the resource set stays
+         * in released state but already receives events. */
+
+        if (mrp_application_class_add_resource_set(rset->application_class,
+            rset->zone, rset->resource_set, 0) < 0)
+            return luaL_error(L, "failed to commit the resource set");
+
+        rset->committed = TRUE;
+    }
+
+    mrp_resource_set_release(rset->resource_set, 0);
+
+    return 0;
+}
+
+
+void event_cb(uint32_t request_id, mrp_resource_set_t *resource_set, void *user_data)
+{
+    resource_set_lua_t *rset = (resource_set_lua_t *) user_data;
+    mrp_resource_mask_t grant, advice;
+    int                 top;
+
+    MRP_UNUSED(request_id);
+    MRP_UNUSED(resource_set);
+
+    mrp_debug("> event_cb");
+
+    top = lua_gettop(rset->L);
+
+    grant = mrp_get_resource_set_grant(rset->resource_set);
+    advice = mrp_get_resource_set_advice(rset->resource_set);
+
+    /* update resource set */
+    rset->acquired = !!grant;
+    rset->available = !!advice;
+
+    if (mrp_lua_object_deref_value(rset, rset->L, rset->callback, false)) {
+        mrp_lua_push_object(rset->L, rset);
+
+        if (lua_pcall(rset->L, 1, 0, 0) != 0)
+            mrp_log_error("failed to invoke Lua resource set callback: %s",
+                    lua_tostring(rset->L, -1));
+    }
+
+    lua_settop(rset->L, top);
+}
+
+static void htbl_free_resource(void *key, void *object)
+{
+    resource_lua_t *res = (resource_lua_t *) object;
+    MRP_UNUSED(key);
+
+    mrp_lua_destroy_object(res->L, NULL, 0, res);
+#if 0
+
+    mrp_debug("lua-resource: htbl_free_resource %p, res: '%s'", res,
+            res->resource_name);
+
+    MRP_UNUSED(key);
+#endif
+}
+
+static int resource_set_lua_create(lua_State *L)
+{
+    char e[128] = "";
+    resource_set_lua_t *rset;
+    int narg;
+    mrp_htbl_config_t conf;
+
+    mrp_debug("create");
+
+    narg = lua_gettop(L);
+
+    rset = (resource_set_lua_t *) mrp_lua_create_object(L,
+            RESOURCE_SET_LUA_CLASS, NULL, 0);
+
+    if (!rset)
+        return luaL_error(L, "could not create Lua object");
+
+    rset->L = L;
+
+    /* user can affect these values */
+    rset->zone = mrp_strdup("default");
+    rset->application_class = NULL;
+    rset->autorelease = FALSE;
+    rset->dont_wait = FALSE;
+    rset->priority = 0;
+    rset->committed = FALSE;
+    rset->initialized = FALSE;
+
+    switch (narg) {
+    case 2:
+        /* argument table */
+        if (mrp_lua_init_members(rset, L, -2, e, sizeof(e)) != 1)
+            return luaL_error(L, "failed to initialize resource members (%s)",
+                    e);
+        break;
+    default:
+        return luaL_error(L, "expecting a constructor argument, "
+                          "got %d", narg);
+    }
+
+    if (rset->application_class == NULL)
+        return luaL_error(L, "application_class is a mandatory parameter");
+
+    if (rset->priority < 0)
+        rset->priority = 0;
+
+    /* initial state, these cannot be set by user */
+    rset->available = FALSE;
+    rset->acquired = FALSE;
+
+    /* initialize resource map */
+    conf.nbucket = 0;
+    conf.nentry = 10;
+    conf.comp = mrp_string_comp;
+    conf.hash = mrp_string_hash;
+    conf.free = htbl_free_resource;
+
+    rset->resources = mrp_htbl_create(&conf);
+    if (!rset->resources)
+        goto error;
+
+    /* do the actual resource work */
+
+    if (!client) {
+        /* create the resource client */
+
+        client = mrp_resource_client_create("lua", NULL);
+
+        if (!client)
+            goto error;
+    }
+
+    rset->resource_set = mrp_resource_set_create(client, rset->autorelease,
+            rset->dont_wait, rset->priority, event_cb, rset);
+
+    if (rset->resource_set)
+        n_sets++;
+    else
+        goto error;
+
+    rset->initialized = TRUE;
+
+    mrp_lua_push_object(L, rset);
+
+    return 1;
+
+error:
+    return luaL_error(L, "internal resource library error");
+}
+
+static int resource_set_get_id(void *data, lua_State *L, int member,
+        mrp_lua_value_t *v)
+{
+    resource_set_lua_t *rset;
+    MRP_UNUSED(L);
+    MRP_UNUSED(member);
+
+    mrp_debug("> resource_set_get_id");
+
+    if (!v)
+        return 0;
+
+    rset = (resource_set_lua_t *) data;
+
+    v->s32 = mrp_get_resource_set_id(rset->resource_set);
+
+    return 1;
+}
+
+static int resource_set_get_resources(void *data, lua_State *L, int member, mrp_lua_value_t *v)
+{
+    resource_set_lua_t *rset;
+    void *iter = NULL;
+    mrp_resource_t *resource;
+    mrp_resource_mask_t grant, advice;
+
+    MRP_UNUSED(member);
+    MRP_UNUSED(v);
+
+    mrp_debug("> resource_set_get_resources");
+
+    rset = (resource_set_lua_t *) data;
+
+    if (!rset)
+        return luaL_error(L, "internal error");
+
+    grant = mrp_get_resource_set_grant(rset->resource_set);
+    advice = mrp_get_resource_set_advice(rset->resource_set);
+
+    lua_newtable(L);
+
+    /* push all resource objects to a table and return it */
+
+    while ((resource = mrp_resource_set_iterate_resources(rset->resource_set, &iter))) {
+        const char *name = mrp_resource_get_name(resource);
+        mrp_resource_mask_t mask = mrp_resource_get_mask(resource);
+
+        /* fetch and update the resource object */
+
+        resource_lua_t *res =
+                (resource_lua_t *) mrp_htbl_lookup(rset->resources,
+                (void *) name);
+
+        if (!res) {
+            mrp_log_error("resources out of sync: %s not found", name);
+            continue;
+        }
+
+        /* mrp_lua_object_ref_value(res, L, 0); */
+
+        res->acquired = !!(mask & grant);
+        res->available = !!(mask & advice);
+
+        /* TODO: update attributes */
+
+        /* push the resource to the table */
+        lua_pushstring(L, res->resource_name);
+        mrp_lua_push_object(L, res);
+        lua_settable(L, -3);
+    }
+
+    return 1;
+}
+
+static int resource_set_lua_stringify(lua_State *L)
+{
+    resource_set_lua_t *rset;
+
+    mrp_debug("> stringify");
+
+    rset = resource_set_lua_check(L, 1);
+
+    if (!rset) {
+        lua_pushstring(L, "invalid resource set");
+        return 1;
+    }
+
+    lua_pushfstring(L, "resource set '%s', acquired: %s, available: %s",
+            rset->application_class,
+            rset->acquired ? "yes" : "no",
+            rset->available ? "yes" : "no");
+
+    return 1;
+}
+
+static void resource_set_lua_destroy(void *data)
+{
+    resource_set_lua_t *rset = (resource_set_lua_t *) data;
+
+    mrp_debug("lua destructor for rset %p", rset);
+
+    /* remove resources from the resource set -- they are finally cleaned from
+     * their own lua destructors */
+
+    if (rset->resource_set)
+        mrp_resource_set_destroy(rset->resource_set);
+
+    if (rset->resources) {
+        mrp_debug("deleting htbl at %p", rset->resources);
+        mrp_htbl_destroy(rset->resources, TRUE);
+        rset->resources = NULL;
+    }
+
+    mrp_free(rset->zone);
+    mrp_free(rset->application_class);
+
+    if (rset->initialized) {
+       n_sets--;
+
+        if (n_sets == 0) {
+            mrp_resource_client_destroy(client);
+            client = NULL;
+        }
+    }
+
+    return;
+}
+
+static void resource_set_lua_changed(void *data, lua_State *L, int member)
+{
+    resource_set_lua_t *rset = (resource_set_lua_t *) data;
+
+    MRP_UNUSED(L);
+    MRP_UNUSED(member);
+
+    mrp_debug("> changed");
+
+    switch (member) {
+        case RESOURCE_SET_MEMBER_CALLBACK:
+            /* no special handling needed */
+            break;
+        default:
+            /* trying to change readonly properties, should trigger an error */
+            mrp_log_error("Trying to change a readonly property for resource set %s",
+                    rset->application_class);
+            break;
+    }
+
+    return;
+}
+
+/* resource */
+
+static inline resource_lua_t *resource_lua_check(lua_State *L, int idx)
+{
+    return (resource_lua_t *) mrp_lua_check_object(L, RESOURCE_LUA_CLASS, idx);
+}
+
+static int resource_lua_create(lua_State *L)
+{
+    mrp_debug("> resource_lua_create");
+
+    return luaL_error(L, "Resource objects are created with ResourceSet:addResource()");
+}
+
+static int resource_lua_stringify(lua_State *L)
+{
+    resource_lua_t *res;
+
+    mrp_debug("> stringify");
+
+    res = resource_lua_check(L, 1);
+
+    if (!res) {
+        lua_pushstring(L, "invalid resource set");
+        return 1;
+    }
+
+    lua_pushfstring(L, "resource '%s', acquired: %s, available: %s, mandatory: %s, shared: %s",
+            res->resource_name,
+            res->acquired ? "yes" : "no",
+            res->available ? "yes" : "no",
+            res->mandatory ? "yes" : "no",
+            res->shared ? "yes" : "no");
+
+    return 1;
+}
+
+static void resource_lua_destroy(void *data)
+{
+    resource_lua_t *res = (resource_lua_t *) data;
+
+    mrp_debug("lua destructor for resource %p (%s)", res, res->resource_name);
+
+    mrp_free(res->resource_name);
+
+    /* TODO: unref res->real_attributes ? */
+
+    mrp_lua_destroy_object(res->L, NULL, 0, res->real_attributes);
+
+    return;
+}
+
+static void resource_lua_changed(void *data, lua_State *L, int member)
+{
+    MRP_UNUSED(data);
+    MRP_UNUSED(L);
+    MRP_UNUSED(member);
+
+    mrp_debug("> resource_changed");
+}
+
+#if 0
+static int resource_get_attributes(void *data, lua_State *L, int member, mrp_lua_value_t *v)
+{
+    resource_lua_t *res = (resource_lua_t *) data;
+    resource_set_lua_t *rset = res->parent;
+    mrp_attr_t attribute_list[MAX_ATTRS], *attrs;
+
+    mrp_debug("> resource_get_attributes");
+
+    lua_newtable(L);
+
+    attrs = mrp_resource_set_read_all_attributes(rset->resource_set,
+                                     res->resource_name,
+                                     MAX_ATTRS-1,
+                                     attribute_list);
+
+    while (attrs->name != NULL) {
+
+        switch (attrs->type) {
+            case mqi_string:
+                lua_pushstring(L, attrs->name);
+                lua_pushstring(L, attrs->value.string);
+                lua_settable(L, -3);
+                break;
+            case mqi_integer:
+                lua_pushstring(L, attrs->name);
+                lua_pushinteger(L, attrs->value.integer);
+                lua_settable(L, -3);
+                break;
+            case mqi_unsignd:
+                if (attrs->value.unsignd > INT_MAX) {
+                    /* too big! */
+                    mrp_log_error("Sorry, we don't support big unsigned values right now");
+                }
+                else {
+                    lua_pushstring(L, attrs->name);
+                    lua_pushinteger(L, attrs->value.unsignd);
+                    lua_settable(L, -3);
+                }
+                break;
+            case mqi_floating:
+                lua_pushstring(L, attrs->name);
+                lua_pushnumber(L, attrs->value.floating);
+                lua_settable(L, -3);
+                break;
+            default:
+                mrp_log_error("Unhandled attribute type");
+                break;
+        }
+
+        attrs++;
+    }
+
+    return 1;
+}
+#else
+static int resource_get_attributes(void *data, lua_State *L, int member, mrp_lua_value_t *v)
+{
+    resource_lua_t *res = (resource_lua_t *) data;
+
+    MRP_UNUSED(member);
+    MRP_UNUSED(v);
+
+    mrp_debug("> resource_get_attributes");
+
+    mrp_lua_push_object(L, res->real_attributes);
+
+    return 1;
+}
+#endif
+
+static int resource_set_attributes(void *data, lua_State *L, int member, mrp_lua_value_t *v)
+{
+    resource_lua_t *res = (resource_lua_t *) data;
+    resource_set_lua_t *rset = res->parent;
+    mrp_attr_t attribute_list[MAX_ATTRS], *attrs, *orig;
+
+    MRP_UNUSED(member);
+    MRP_UNUSED(v);
+
+    mrp_debug("> resource_set_attributes");
+
+    if (!lua_istable(L, -1))
+        return luaL_error(L, "argument error -- not a table");
+
+    mrp_resource_set_read_all_attributes(rset->resource_set,
+            res->resource_name, MAX_ATTRS-1, attribute_list);
+
+    attrs = orig = attribute_list;
+
+    while (attrs->name != NULL) {
+        /* get the attribute from the lua table by name */
+
+        lua_pushstring(L, attrs->name);
+        lua_gettable(L, -2);
+
+        /* update the attribute */
+
+        switch (attrs->type) {
+            case mqi_string:
+                if (lua_isstring(L, -1)) {
+                    attrs->value.string = lua_tostring(L, -1);
+                    mrp_debug("updated attr '%s' to '%s'", attrs->name, attrs->value.string);
+                }
+                break;
+            case mqi_integer:
+                if (lua_isnumber(L, -1)) {
+                    attrs->value.integer = lua_tointeger(L, -1);
+                    mrp_debug("updated attr '%s' to '%i'", attrs->name, attrs->value.integer);
+                }
+                break;
+            case mqi_unsignd:
+                if (lua_isnumber(L, -1)) {
+                    int val = lua_tointeger(L, -1);
+                    if (val >= 0) {
+                        attrs->value.unsignd = val;
+                        mrp_debug("updated attr '%s' to '%u'", attrs->name, attrs->value.unsignd);
+                    }
+                }
+                break;
+            case mqi_floating:
+                if (lua_isnumber(L, -1)) {
+                    attrs->value.floating = lua_tonumber(L, -1);
+                    mrp_debug("updated attr '%s' to '%f'", attrs->name, attrs->value.floating);
+                }
+                break;
+            default:
+                break;
+        }
+
+        lua_pop(L, 1);
+        attrs++;
+    }
+
+    /* write the attributes back */
+    mrp_resource_set_write_attributes(rset->resource_set, res->resource_name, orig);
+
+    return 1;
+}
+
+static inline attribute_lua_t *attribute_lua_check(lua_State *L, int idx)
+{
+    return (attribute_lua_t *) mrp_lua_check_object(L, ATTRIBUTE_LUA_CLASS, idx);
+}
+
+static int attribute_lua_create(lua_State *L)
+{
+    mrp_debug("> attribute_create");
+
+    return luaL_error(L, "Attribute objects are created with ResourceSet:addResource()");
+}
+
+static void attribute_lua_destroy(void *data)
+{
+    attribute_lua_t *attribute = (attribute_lua_t *) data;
+
+    mrp_debug("lua destructor for attribute table %p", attribute);
+
+    return;
+}
+
+static int attribute_lua_stringify(lua_State *L)
+{
+    attribute_lua_t *attribute = attribute_lua_check(L, 1);
+    resource_lua_t *res = attribute->parent;
+    resource_set_lua_t *rset = res->parent;
+    mrp_attr_t attribute_list[MAX_ATTRS], *attrs;
+
+    int available = 4095;
+    char buf[available+1], *p;
+    char numbuf[16];
+    int vallen;
+
+    mrp_debug("> attribute_stringify");
+
+    memset(buf, 0, sizeof(buf));
+    p = buf;
+
+    mrp_resource_set_read_all_attributes(rset->resource_set,
+            res->resource_name, MAX_ATTRS-1, attribute_list);
+
+    attrs = attribute_list;
+
+    while (attrs->name != NULL) {
+
+        int keylen = strlen(attrs->name);
+
+        /* we need space for 2 + null */
+        available -= keylen + 3;
+
+        if (available < 0)
+            goto outofspace;
+
+        strncpy(p, attrs->name, keylen);
+        p += keylen;
+
+        /*
+         * we copy ": \0" and then proceed to only
+         * move the pointer by two, thus we can
+         * add one to the amount of available
+         * space.
+         */
+        strncpy(p, ": ", 3);
+        p += 2;
+        available += 1;
+
+        switch (attrs->type) {
+            case mqi_string:
+                vallen = strlen(attrs->value.string);
+                available -= vallen + 1;
+                if (available < 0)
+                    goto outofspace;
+                strncpy(p, attrs->value.string, vallen);
+                p += vallen;
+                *p = '\n';
+                p += 1;
+                break;
+            case mqi_integer:
+                snprintf(numbuf, sizeof(numbuf), "%d",
+                        (int) attrs->value.integer);
+                vallen = strlen(numbuf);
+                available -= vallen + 1;
+                if (available < 0)
+                    goto outofspace;
+                strncpy(p, numbuf, vallen);
+                p += vallen;
+                *p = '\n';
+                p += 1;
+                break;
+            case mqi_unsignd:
+                snprintf(numbuf, sizeof(numbuf), "%u", attrs->value.unsignd);
+                vallen = strlen(numbuf);
+                available -= vallen + 1;
+                if (available < 0)
+                    goto outofspace;
+                strncpy(p, numbuf, vallen);
+                p += vallen;
+                *p = '\n';
+                p += 1;
+                break;
+            case mqi_floating:
+                snprintf(numbuf, sizeof(numbuf), "%f", attrs->value.floating);
+                vallen = strlen(numbuf);
+                available -= vallen + 1;
+                if (available < 0)
+                    goto outofspace;
+                strncpy(p, numbuf, vallen);
+                p += vallen;
+                *p = '\n';
+                p += 1;
+                break;
+            default:
+                break;
+        }
+
+        attrs++;
+    }
+    lua_pushstring(L, buf);
+
+    return 1;
+
+outofspace:
+    return luaL_error(L, "out of string buffer space");
+}
+
+static void attribute_lua_changed(void *data, lua_State *L, int member)
+{
+    MRP_UNUSED(data);
+    MRP_UNUSED(L);
+    MRP_UNUSED(member);
+
+    mrp_debug("> attribute_changed");
+    return;
+}
+
+static int attribute_lua_getfield(lua_State *L)
+{
+    attribute_lua_t *attribute = attribute_lua_check(L, 1);
+    resource_lua_t *res = attribute->parent;
+    resource_set_lua_t *rset = res->parent;
+    mrp_attr_t attribute_list[MAX_ATTRS], *attrs;
+    const char *key;
+
+    mrp_debug("> attribute_lua_getfield");
+
+    /* attributes are indexed by string */
+
+    if (lua_type(L, 2) != LUA_TSTRING)
+        return luaL_error(L, "invalid attribute index type (needs to be string)");
+
+    key = lua_tostring(L, 2);
+
+    attrs = mrp_resource_set_read_all_attributes(rset->resource_set,
+            res->resource_name, MAX_ATTRS-1, attribute_list);
+
+    if (!attrs)
+        return luaL_error(L, "internal resource library error");
+
+    while (attrs->name != NULL) {
+
+        if (strcmp(attrs->name, key) == 0) {
+
+            switch (attrs->type) {
+                case mqi_string:
+                    lua_pushstring(L, attrs->value.string);
+                    return 1;
+                case mqi_integer:
+                    lua_pushinteger(L, attrs->value.integer);
+                    return 1;
+                case mqi_unsignd:
+                    if (attrs->value.unsignd > INT_MAX) {
+                        /* too big! */
+                        mrp_log_error("Sorry, we don't support big unsigned values right now");
+                        return luaL_error(L, "too big value in attribute");
+                    }
+                    else {
+                        lua_pushinteger(L, attrs->value.unsignd);
+                    }
+                    return 1;
+                case mqi_floating:
+                    lua_pushnumber(L, attrs->value.floating);
+                    return 1;
+                default:
+                    mrp_log_error("Unhandled attribute type");
+                    return 1;
+            }
+        }
+
+        attrs++;
+    }
+
+    return luaL_error(L, "trying to get a non-existing attribute");
+}
+
+static int attribute_lua_setfield(lua_State *L)
+{
+    attribute_lua_t *attribute = attribute_lua_check(L, 1);
+    resource_lua_t *res = attribute->parent;
+    resource_set_lua_t *rset = res->parent;
+    mrp_attr_t attribute_list[MAX_ATTRS], *attrs, *orig;
+    const char *key;
+    int new_type;
+
+    mrp_debug("> attribute_lua_setfield");
+
+    /* attributes are indexed by string */
+
+    if (lua_type(L, 2) != LUA_TSTRING)
+        return luaL_error(L, "invalid attribute index type (needs to be string)");
+
+    key = lua_tostring(L, 2);
+    new_type = lua_type(L, 3);
+
+    attrs = mrp_resource_set_read_all_attributes(rset->resource_set,
+            res->resource_name, MAX_ATTRS-1, attribute_list);
+
+    if (!attrs)
+        return luaL_error(L, "internal resource library error");
+
+    orig = attrs;
+
+    while (attrs->name != NULL) {
+
+        if (strcmp(attrs->name, key) == 0) {
+
+            switch (attrs->type) {
+                case mqi_string:
+                    if (new_type != LUA_TSTRING)
+                        return luaL_error(L, "type mismatch");
+                    attrs->value.string = lua_tostring(L, 3);
+                    break;
+                case mqi_integer:
+                {
+                    int32_t i;
+                    double dbl;
+
+                    if (new_type != LUA_TNUMBER)
+                        return luaL_error(L, "type mismatch");
+
+                    if ((i = lua_tointeger(L, 3)) == (dbl = lua_tonumber(L, 3)))
+                        attrs->value.integer = i;
+                    else
+                        return luaL_error(L, "type mismatch");
+
+                    break;
+                }
+                case mqi_unsignd:
+                {
+                    int32_t i;
+                    double dbl;
+
+                    if (new_type != LUA_TNUMBER)
+                        return luaL_error(L, "type mismatch");
+
+                    if ((i = lua_tointeger(L, 3)) == (dbl = lua_tonumber(L, 3)) && i >= 0)
+                        attrs->value.unsignd = i;
+                    else
+                        return luaL_error(L, "type mismatch");
+
+                    break;
+                }
+                case mqi_floating:
+                {
+                    if (new_type != LUA_TNUMBER)
+                        return luaL_error(L, "type mismatch");
+                    attrs->value.floating = lua_tonumber(L, 3);
+                    break;
+                }
+                default:
+                    return luaL_error(L, "unhandled attribute type");
+            }
+            break; /* while */
+        }
+
+        attrs++;
+    }
+
+    mrp_resource_set_write_attributes(rset->resource_set, res->resource_name, orig);
+
+    return 1;
+}
+
+#if 0
+MURPHY_REGISTER_LUA_BINDINGS(murphy, RESOURCE_LUA_CLASS,
+                             { "Resource", resource_lua_create });
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, RESOURCE_SET_LUA_CLASS,
+                             { "ResourceSet", resource_set_lua_create });
+
+MURPHY_REGISTER_LUA_BINDINGS(murphy, ATTRIBUTE_LUA_CLASS,
+                             { "Attribute", attribute_lua_create });
+#else
+
+static void register_murphy_bindings(void) MRP_INIT;
+
+static void register_murphy_bindings(void) {
+    static struct luaL_Reg resource_methods[] = {
+        { "Resource", resource_lua_create },
+        { NULL, NULL }
+    };
+
+    static mrp_lua_bindings_t resource_bindings = {
+        .meta = "murphy",
+        .methods = resource_methods,
+        .classdef = &resource_lua_class_def,
+    };
+
+    static struct luaL_Reg resource_set_methods[] = {
+        { "ResourceSet", resource_set_lua_create },
+        { NULL, NULL }
+    };
+
+    static mrp_lua_bindings_t resource_set_bindings = {
+        .meta = "murphy",
+        .methods = resource_set_methods,
+        .classdef = &resource_set_lua_class_def,
+    };
+
+    static struct luaL_Reg attribute_methods[] = {
+        { "Attribute", attribute_lua_create },
+        { NULL, NULL }
+    };
+
+    static mrp_lua_bindings_t attribute_bindings = {
+        .meta = "murphy",
+        .methods = attribute_methods,
+        .classdef = &attribute_lua_class_def,
+    };
+
+    mrp_list_init(&attribute_bindings.hook);
+    mrp_lua_register_murphy_bindings(&attribute_bindings);
+
+    mrp_list_init(&resource_bindings.hook);
+    mrp_lua_register_murphy_bindings(&resource_bindings);
+
+    mrp_list_init(&resource_set_bindings.hook);
+    mrp_lua_register_murphy_bindings(&resource_set_bindings);
+}
+#endif
+
+
+/*
+
+resourcehandler = function (rset)
+    if rset.resources.screen.acquired == true then
+        print("got it")
+    else
+        print("didn't get it")
+    end
+end
+
+resourcehandler = function (rset) print("bar") end
+
+rset = m:ResourceSet ( { zone = "driver", callback = resourceHandler, application_class = "player" } )
+
+rset:addResource({ resource_name = "audio_playback", mandatory = true })
+
+rset.resources.audio_playback.attributes.pid = "500"
+
+rset:acquire()
+
+rset:release()
+
+*/
diff --git a/src/resource/manager-api.h b/src/resource/manager-api.h
new file mode 100644 (file)
index 0000000..c91f054
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_MANAGER_API_H__
+#define __MURPHY_RESOURCE_MANAGER_API_H__
+
+#include <murphy/resource/common-api.h>
+
+uint32_t mrp_zone_get_id(mrp_zone_t *zone);
+const char *mrp_zone_get_name(mrp_zone_t *zone);
+mrp_attr_t *mrp_zone_read_attribute(mrp_zone_t *zone,
+                                    uint32_t attribute_index,
+                                    mrp_attr_t *buf);
+mrp_attr_t *mrp_zone_read_all_attributes(mrp_zone_t *zone,
+                                         uint32_t buflen,
+                                         mrp_attr_t *buf);
+
+
+const char *mrp_application_class_get_name(mrp_application_class_t *class);
+uint32_t mrp_application_class_get_priority(mrp_application_class_t *class);
+
+
+uint32_t mrp_resource_definition_create(const char *name,
+                                        bool shareable,
+                                        mrp_attr_def_t *attrdefs,
+                                        mrp_resource_mgr_ftbl_t *manager,
+                                        void *manager_data);
+void mrp_lua_resclass_create_from_c(uint32_t id);
+
+
+const char *mrp_resource_get_application_class(mrp_resource_t *resource);
+
+void mrp_resource_owner_recalc(uint32_t zoneid);
+
+#endif  /* __MURPHY_RESOURCE_MANAGER_API_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/protocol.h b/src/resource/protocol.h
new file mode 100644 (file)
index 0000000..fa03484
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_PROTOCOL_H__
+#define __MURPHY_RESOURCE_PROTOCOL_H__
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <murphy/common/msg.h>
+
+#define RESPROTO_DEFAULT_ADDRESS      "unxs:@murphy-resource-native"
+#define RESPROTO_DEFAULT_ADDRVAR      "MURPHY_RESOURCE_ADDRESS"
+
+
+#define RESPROTO_BIT(n)               ((uint32_t)1 << (n))
+
+#define RESPROTO_RSETFLAG_AUTORELEASE RESPROTO_BIT(0)
+#define RESPROTO_RSETFLAG_AUTOACQUIRE RESPROTO_BIT(1)
+#define RESPROTO_RSETFLAG_NOEVENTS    RESPROTO_BIT(2)
+#define RESPROTO_RSETFLAG_DONTWAIT    RESPROTO_BIT(3)
+
+#define RESPROTO_RESFLAG_MANDATORY    RESPROTO_BIT(0)
+#define RESPROTO_RESFLAG_SHARED       RESPROTO_BIT(1)
+
+#define RESPROTO_TAG(x)               ((uint16_t)(x))
+
+#define RESPROTO_MESSAGE_END          MRP_MSG_FIELD_END
+#define RESPROTO_SECTION_END          RESPROTO_TAG(1)
+#define RESPROTO_ARRAY_DIMENSION      RESPROTO_TAG(2)
+#define RESPROTO_SEQUENCE_NO          RESPROTO_TAG(3)
+#define RESPROTO_REQUEST_TYPE         RESPROTO_TAG(4)
+#define RESPROTO_REQUEST_STATUS       RESPROTO_TAG(5)
+#define RESPROTO_RESOURCE_SET_ID      RESPROTO_TAG(6)
+#define RESPROTO_RESOURCE_STATE       RESPROTO_TAG(7)
+#define RESPROTO_RESOURCE_GRANT       RESPROTO_TAG(8)
+#define RESPROTO_RESOURCE_ADVICE      RESPROTO_TAG(9)
+#define RESPROTO_RESOURCE_ID          RESPROTO_TAG(10)
+#define RESPROTO_RESOURCE_NAME        RESPROTO_TAG(11)
+#define RESPROTO_RESOURCE_FLAGS       RESPROTO_TAG(12)
+#define RESPROTO_RESOURCE_PRIORITY    RESPROTO_TAG(13)
+#define RESPROTO_CLASS_NAME           RESPROTO_TAG(14)
+#define RESPROTO_ZONE_NAME            RESPROTO_TAG(15)
+#define RESPROTO_ATTRIBUTE_INDEX      RESPROTO_TAG(16)
+#define RESPROTO_ATTRIBUTE_NAME       RESPROTO_TAG(17)
+#define RESPROTO_ATTRIBUTE_VALUE      RESPROTO_TAG(18)
+
+typedef enum {
+    RESPROTO_QUERY_RESOURCES,
+    RESPROTO_QUERY_CLASSES,
+    RESPROTO_QUERY_ZONES,
+    RESPROTO_CREATE_RESOURCE_SET,
+    RESPROTO_DESTROY_RESOURCE_SET,
+    RESPROTO_ACQUIRE_RESOURCE_SET,
+    RESPROTO_RELEASE_RESOURCE_SET,
+    RESPROTO_RESOURCES_EVENT,
+} mrp_resproto_request_t;
+
+typedef enum {
+    RESPROTO_RELEASE,
+    RESPROTO_ACQUIRE,
+} mrp_resproto_state_t;
+
+
+static inline const char *mrp_resource_get_default_address(void)
+{
+    const char *addr;
+
+    if ((addr = getenv(RESPROTO_DEFAULT_ADDRVAR)) == NULL)
+        return RESPROTO_DEFAULT_ADDRESS;
+    else
+        return addr;
+}
+
+#endif  /* __MURPHY_RESOURCE_PROTOCOL_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-client.c b/src/resource/resource-client.c
new file mode 100644 (file)
index 0000000..2292b0f
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include <resource/client-api.h>
+
+#include "resource-client.h"
+#include "resource-set.h"
+
+
+
+static MRP_LIST_HOOK(client_list);
+
+
+mrp_resource_client_t *mrp_resource_client_create(const char *name,
+                                                  void *user_data)
+{
+    mrp_resource_client_t *client;
+    const char *dup_name;
+
+    MRP_ASSERT(name, "invalid argument");
+
+    if (!(client = mrp_allocz(sizeof(*client))) ||
+        !(dup_name = mrp_strdup(name)))
+    {
+        mrp_log_error("Memory alloc failure. Can't create client '%s'", name);
+        return NULL;
+    }
+
+    client->name = dup_name;
+    client->user_data = user_data;
+    mrp_list_init(&client->resource_sets);
+
+    mrp_list_append(&client_list, &client->list);
+
+    return client;
+}
+
+void mrp_resource_client_destroy(mrp_resource_client_t *client)
+{
+    mrp_list_hook_t *entry, *n;
+    mrp_resource_set_t *rset;
+
+    if (client) {
+        mrp_list_delete(&client->list);
+
+        mrp_list_foreach(&client->resource_sets, entry, n) {
+            rset = mrp_list_entry(entry, mrp_resource_set_t, client.list);
+            mrp_resource_set_destroy(rset);
+        }
+
+        mrp_free((void *) client->name);
+        mrp_free(client);
+    }
+}
+
+
+mrp_resource_set_t *mrp_resource_client_find_set(mrp_resource_client_t *client,
+                                                 uint32_t resource_set_id)
+{
+    mrp_list_hook_t *entry, *n;
+    mrp_resource_set_t *rset;
+
+    if (client) {
+        mrp_list_foreach(&client->resource_sets, entry, n) {
+            rset = mrp_list_entry(entry, mrp_resource_set_t, client.list);
+
+            if (resource_set_id == rset->id)
+                return rset;
+        }
+    }
+
+    return NULL;
+}
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-client.h b/src/resource/resource-client.h
new file mode 100644 (file)
index 0000000..7d8369b
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_CLIENT_H__
+#define __MURPHY_RESOURCE_CLIENT_H__
+
+#include <murphy/common/list.h>
+
+#include "data-types.h"
+
+
+struct mrp_resource_client_s {
+    mrp_list_hook_t  list;
+    const char      *name;
+    void            *user_data;
+    mrp_list_hook_t  resource_sets;
+};
+
+
+
+#endif  /* __MURPHY_RESOURCE_CLIENT_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-lua.c b/src/resource/resource-lua.c
new file mode 100644 (file)
index 0000000..3b5b9df
--- /dev/null
@@ -0,0 +1,711 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <murphy/common.h>
+#include <murphy/common/debug.h>
+#include <murphy/core/lua-bindings/murphy.h>
+#include <murphy/core/lua-utils/funcbridge.h>
+#include <murphy/core/lua-utils/object.h>
+
+#include "resource-lua.h"
+#include "config-lua.h"
+#include "zone.h"
+#include "resource.h"
+#include "resource-set.h"
+#include "resource-owner.h"
+#include "application-class.h"
+#include "client-api.h"
+
+#define OWNERS_CLASS         MRP_LUA_CLASS(resource, owners)
+#define SETREF_CLASS         MRP_LUA_CLASS(resource, sets)
+
+#define OWNERREF_CLASSID     MRP_LUA_CLASSID_ROOT "resource.ownerref"
+#define OWNERREF_USERDATA    MRP_LUA_CLASSID_ROOT "resource.ownerref.userdata"
+
+typedef enum   field_e       field_t;
+typedef struct ownerref_s    ownerref_t;
+
+enum field_e {
+    APPLICATION_CLASS = 1,
+    AUTO_RELEASE,
+    RESOURCE_SET,
+    ATTRIBUTES,
+    DONT_WAIT,
+    RESOURCE,
+    STATE,
+    ID
+};
+
+struct ownerref_s {
+    uint32_t zoneid;
+    uint32_t resid;
+};
+
+static void owners_class_create(lua_State *);
+static void ownerref_class_create(lua_State *);
+static void setref_class_create(lua_State *);
+
+static mrp_resource_ownersref_t *owners_get(lua_State *, uint32_t);
+static int  owners_create(lua_State *);
+static int  owners_getfield(lua_State *);
+static int  owners_setfield(lua_State *);
+static void owners_destroy(void *);
+static mrp_resource_ownersref_t *owners_check(lua_State *, int);
+/* static mrp_resource_owners_s *to_owners(lua_State *, int); */
+
+static ownerref_t *ownerref_create(lua_State *, uint32_t, uint32_t);
+static int ownerref_getfield(lua_State *);
+static int ownerref_setfield(lua_State *);
+static mrp_resource_owner_t *ownerref_check(lua_State *, int);
+
+static int  setref_getfield(lua_State *);
+static int  setref_setfield(lua_State *);
+static void setref_destroy(void *);
+static mrp_resource_setref_t *setref_check(lua_State *, int);
+
+static void init_id_hash(void);
+static int  add_to_id_hash(mrp_resource_setref_t *);
+static mrp_resource_setref_t *remove_from_id_hash(uint32_t);
+static mrp_resource_setref_t *find_in_id_hash(uint32_t);
+
+static field_t field_check(lua_State *, int, const char **);
+static field_t field_name_to_type(const char *, size_t);
+
+
+MRP_LUA_METHOD_LIST_TABLE (
+    owners_methods,          /* methodlist name */
+    MRP_LUA_METHOD_CONSTRUCTOR  (owners_create)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    setref_methods            /* methodlist name */
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    owners_overrides,         /* methodlist name */
+    MRP_LUA_OVERRIDE_CALL       (owners_create)
+    MRP_LUA_OVERRIDE_GETFIELD   (owners_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (owners_setfield)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    ownerref_overrides,       /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (ownerref_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (ownerref_setfield)
+);
+
+MRP_LUA_METHOD_LIST_TABLE (
+    setref_overrides,         /* methodlist name */
+    MRP_LUA_OVERRIDE_GETFIELD   (setref_getfield)
+    MRP_LUA_OVERRIDE_SETFIELD   (setref_setfield)
+);
+
+MRP_LUA_CLASS_DEF (
+    resource,                 /* main class name */
+    owners,                   /* constructor name */
+    mrp_resource_ownersref_t, /* userdata type */
+    owners_destroy,           /* userdata destructor */
+    owners_methods,           /* class methods */
+    owners_overrides          /* class overrides */
+);
+
+MRP_LUA_CLASS_DEF (
+    resource,                 /* main class name */
+    sets,                     /* constructor name */
+    mrp_resource_setref_t,    /* userdata type */
+    setref_destroy,           /* userdata destructor */
+    setref_methods,           /* class methods */
+    setref_overrides          /* class overrides */
+);
+
+static mrp_resource_ownersref_t *resource_owners[MRP_ZONE_MAX];
+static mrp_htbl_t *id_hash;
+
+void mrp_resource_lua_init(lua_State *L)
+{
+    static bool initialised = false;
+
+    if (!initialised) {
+        owners_class_create(L);
+        ownerref_class_create(L);
+        setref_class_create(L);
+
+        init_id_hash();
+    }
+}
+
+bool mrp_resource_lua_veto(mrp_zone_t *zone,
+                           mrp_resource_set_t *rset,
+                           mrp_resource_owner_t *owners,
+                           mrp_resource_mask_t grant,
+                           mrp_resource_set_t *reqset)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_lua_resmethod_t *methods = mrp_lua_get_resource_methods();
+    mrp_funcarray_t *veto;
+    mrp_resource_setref_t *sref, *rref;
+    mrp_resource_ownersref_t *oref;
+    mrp_funcbridge_value_t args[16];
+    int i, top;
+    bool success;
+
+    if (L == NULL)
+        return true;
+
+    success = true;
+    top = lua_gettop(L);
+
+    if (zone && rset && owners && methods &&
+        (sref = find_in_id_hash(rset->id)) &&
+        (oref = owners_get(L, zone->id)))
+    {
+        rref = reqset ? find_in_id_hash(reqset->id) : NULL;
+        oref->owners = owners;
+
+        if ((veto = methods->veto)) {
+            args[i=0].string  = zone->name;
+            args[++i].pointer = sref;
+            args[++i].integer = grant;
+            args[++i].pointer = oref;
+            args[++i].pointer = rref;
+
+            success = mrp_funcarray_call_from_c(L, veto, "sodoo", args);
+
+            goto out;
+        }
+    }
+
+ out:
+    lua_settop(L, top);
+
+    return success;
+}
+
+void mrp_resource_lua_set_owners(mrp_zone_t *zone,mrp_resource_owner_t *owners)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_resource_ownersref_t *ref;
+
+    if (L && zone && owners && (ref = owners_get(L, zone->id)))
+        ref->owners = owners;
+}
+
+void mrp_resource_lua_register_resource_set(mrp_resource_set_t *rset)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_resource_setref_t *ref;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    if ((ref = mrp_lua_create_object(L, SETREF_CLASS, NULL,rset->id))) {
+        ref->rset = rset;
+        add_to_id_hash(ref);
+    }
+}
+
+void mrp_resource_lua_unregister_resource_set(mrp_resource_set_t *rset)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_resource_setref_t *ref;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    if ((ref = remove_from_id_hash(rset->id))) {
+        MRP_ASSERT(rset == ref->rset, "confused with data structures");
+        mrp_lua_destroy_object(L, NULL,rset->id, ref);
+        ref->rset = NULL;
+    }
+}
+
+void mrp_resource_lua_add_resource_to_resource_set(mrp_resource_set_t *rset,
+                                                   mrp_resource_t *res)
+{
+    lua_State *L = mrp_lua_get_lua_state();
+    mrp_resource_setref_t *ref;
+    mrp_resource_def_t *def;
+
+    MRP_ASSERT(rset && res, "invalid argument");
+
+    ref = find_in_id_hash(rset->id);
+    def = res->def;
+
+    if (ref && def) {
+        MRP_ASSERT(rset == ref->rset, "confused with data structures");
+
+        mrp_lua_push_object(L, ref);
+
+        lua_pushstring(L, def->name);
+        mrp_lua_resource_create(L, res);
+
+        lua_rawset(L, -3);
+    }
+}
+
+static void owners_class_create(lua_State *L)
+{
+    mrp_lua_create_object_class(L, OWNERS_CLASS);
+}
+
+static void ownerref_class_create(lua_State *L)
+{
+    luaL_newmetatable(L, OWNERREF_USERDATA);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    lua_pop(L, 1);
+
+    luaL_newmetatable(L, OWNERREF_CLASSID);
+    lua_pushliteral(L, "__index");
+    lua_pushvalue(L, -2);
+    lua_settable(L, -3);        /* metatable.__index = metatable */
+    luaL_openlib(L, NULL, ownerref_overrides, 0);
+    lua_pop(L, 1);
+}
+
+static void setref_class_create(lua_State *L)
+{
+    mrp_lua_create_object_class(L, SETREF_CLASS);
+}
+
+static mrp_resource_ownersref_t *owners_get(lua_State *L, uint32_t zoneid)
+{
+    mrp_resource_ownersref_t *owner = NULL;
+    mrp_zone_t *zone;
+
+    if (zoneid < MRP_ZONE_MAX) {
+        if (!(owner = resource_owners[zoneid])) {
+            if ((zone = mrp_zone_find_by_id(zoneid))) {
+                owner = mrp_lua_create_object(L, OWNERS_CLASS, zone->name,0);
+                owner->zoneid = zoneid;
+                resource_owners[zoneid] = owner;
+            }
+        }
+    }
+
+    return owner;
+}
+
+static int owners_create(lua_State *L)
+{
+    luaL_error(L, "can't create resource owner from LUA");
+    return 0;
+}
+
+static int owners_getfield(lua_State *L)
+{
+    mrp_resource_ownersref_t *ref = owners_check(L, 1);
+    uint32_t resid;
+
+    MRP_LUA_ENTER;
+
+    switch (lua_type(L, 2)) {
+
+    case LUA_TSTRING:
+        if (mrp_lua_findtable(L, MRP_LUA_GLOBALTABLE, "resource.class", 0)) {
+            lua_pushnil(L);
+            break;
+        }
+        lua_pushvalue(L, 2);
+        lua_gettable(L, -2);
+        if (lua_isnil(L, -1))
+            break;
+        resid = mrp_lua_to_resource_id(L, -1);
+        goto create_reference;
+
+    case LUA_TNUMBER:
+        resid = lua_tointeger(L, 2) - 1;
+
+    create_reference:
+        if (resid >= MRP_RESOURCE_MAX || !ref->owners[resid].class)
+            lua_pushnil(L);
+        else
+            ownerref_create(L, ref->zoneid, resid);
+        break;
+
+    default:
+        lua_pushnil(L);
+        break;
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int owners_setfield(lua_State *L)
+{
+    MRP_LUA_ENTER;
+
+    luaL_error(L, "attempt to write read-only resource owners");
+
+    MRP_LUA_LEAVE(0);
+}
+
+static void owners_destroy(void *data)
+{
+    mrp_resource_ownersref_t *owners = (mrp_resource_ownersref_t *)data;
+
+    MRP_LUA_ENTER;
+
+    if (owners->zoneid < MRP_ZONE_MAX)
+        resource_owners[owners->zoneid] = NULL;
+
+    memset(owners, 0, sizeof(mrp_resource_ownersref_t));
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static mrp_resource_ownersref_t *owners_check(lua_State *L, int t)
+{
+    return (mrp_resource_ownersref_t*)mrp_lua_check_object(L, OWNERS_CLASS, t);
+}
+
+
+static ownerref_t *ownerref_create(lua_State *L,uint32_t zoneid,uint32_t resid)
+{
+    int table;
+    ownerref_t *or;
+
+    lua_createtable(L, 0, 0);
+    table = lua_gettop(L);
+
+    luaL_getmetatable(L, OWNERREF_CLASSID);
+    lua_setmetatable(L, table);
+
+    lua_pushliteral(L, "userdata");
+
+    or = (ownerref_t *)lua_newuserdata(L, sizeof(ownerref_t));
+    memset(or, 0, sizeof(ownerref_t));
+
+    luaL_getmetatable(L, OWNERREF_USERDATA);
+    lua_setmetatable(L, -2);
+
+    lua_rawset(L, table);
+
+    or->zoneid = zoneid;
+    or->resid = resid;
+
+    return or;
+}
+
+static int ownerref_getfield(lua_State *L)
+{
+    mrp_resource_owner_t *owner = ownerref_check(L, 1);
+    const char *name;
+    field_t field;
+
+    MRP_LUA_ENTER;
+
+    if (!owner || lua_type(L, 2) != LUA_TSTRING)
+        lua_pushnil(L);
+    else {
+        field = field_check(L, 2, &name);
+
+        switch (field) {
+        case APPLICATION_CLASS:  lua_pushstring(L, owner->class->name);  break;
+        case RESOURCE_SET:       lua_pushinteger(L, owner->rset->id);    break;
+        case RESOURCE:           lua_pushnil(L);                         break;
+        default:                 lua_pushnil(L);                         break;
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int ownerref_setfield(lua_State *L)
+{
+    /* mrp_resource_owner_t *owner = ownerref_check(L, 1); */
+
+    MRP_UNUSED(L);
+
+    MRP_LUA_ENTER;
+
+    printf("*** ownerref setfield\n");
+
+    MRP_LUA_LEAVE(1);
+}
+
+static mrp_resource_owner_t *ownerref_check(lua_State *L, int t)
+{
+    ownerref_t *or;
+    mrp_resource_ownersref_t *ro;
+    mrp_resource_owner_t *owner = NULL;
+
+    t = (t < 0) ? lua_gettop(L) + t + 1 : t;
+
+    luaL_checktype(L, t, LUA_TTABLE);
+
+    lua_pushliteral(L, "userdata");
+    lua_rawget(L, t);
+
+    or = luaL_checkudata(L, -1, OWNERREF_USERDATA);
+    luaL_argcheck(L, or != NULL, t, "'resource owner' expected");
+
+    lua_pop(L, 1);
+
+    if ((ro = resource_owners[or->zoneid]))
+        owner = ro->owners + or->resid;
+
+    return owner;
+}
+
+static int setref_getfield(lua_State *L)
+{
+    mrp_resource_setref_t *ref = setref_check(L, 1);
+    mrp_resource_set_t *rset;
+    field_t field;
+    const char *state;
+
+    MRP_LUA_ENTER;
+
+    if (!ref || !(rset = ref->rset))
+        lua_pushnil(L);
+    else {
+        field = field_check(L, 2, NULL);
+
+        switch (field) {
+
+        case ID:
+            lua_pushinteger(L, rset->id);
+            break;
+
+        case STATE:
+            switch (rset->state) {
+            case mrp_resource_no_request:  state = "no_request";  break;
+            case mrp_resource_release:     state = "release";     break;
+            case mrp_resource_acquire:     state = "acquire";     break;
+            default:                       state = "<invalid>";   break;
+            }
+            lua_pushstring(L, state);
+            break;
+
+        case DONT_WAIT:
+            lua_pushboolean(L, rset->dont_wait.current);
+            break;
+
+        case AUTO_RELEASE:
+            lua_pushboolean(L, rset->auto_release.current);
+            break;
+
+        case APPLICATION_CLASS:
+            lua_pushstring(L, rset->class.ptr->name);
+            break;
+
+        default:
+            lua_pushnil(L);
+            break;
+        }
+    }
+
+    MRP_LUA_LEAVE(1);
+}
+
+static int setref_setfield(lua_State *L)
+{
+    mrp_resource_setref_t *ref = setref_check(L, 1);
+    mrp_resource_set_t *rset;
+    field_t field;
+
+    MRP_LUA_ENTER;
+
+    if (ref && (rset = ref->rset)) {
+        field = field_check(L, 2, NULL);
+
+        switch (field) {
+
+        case DONT_WAIT:
+            rset->dont_wait.current = lua_toboolean(L, 3);
+            break;
+
+        case AUTO_RELEASE:
+            rset->auto_release.current = lua_toboolean(L, 3);
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    MRP_LUA_LEAVE(0);
+}
+
+static void setref_destroy(void *data)
+{
+    mrp_resource_setref_t *ref = (mrp_resource_setref_t *)data;
+    mrp_resource_set_t *rset;
+
+    MRP_LUA_ENTER;
+
+    if (ref && (rset = ref->rset))
+        remove_from_id_hash(rset->id);
+
+    MRP_LUA_LEAVE_NOARG;
+}
+
+static mrp_resource_setref_t *setref_check(lua_State *L, int idx)
+{
+    return (mrp_resource_setref_t *)mrp_lua_check_object(L, SETREF_CLASS, idx);
+}
+
+
+static uint32_t ref_hash(const void *key)
+{
+    return (uint32_t)(key - NULL);
+}
+
+static int ref_comp(const void *key1, const void *key2)
+{
+    uint32_t k1 = key1 - NULL;
+    uint32_t k2 = key2 - NULL;
+
+    return (k1 == k2) ? 0 : ((k1 > k2) ? 1 : -1);
+}
+
+static void init_id_hash(void)
+{
+    mrp_htbl_config_t cfg;
+
+    cfg.nentry  = 32;
+    cfg.comp    = ref_comp;
+    cfg.hash    = ref_hash;
+    cfg.free    = NULL;
+    cfg.nbucket = cfg.nentry;
+
+    id_hash = mrp_htbl_create(&cfg);
+
+    MRP_ASSERT(id_hash, "failed to make id_hash for resource set refs");
+}
+
+static int add_to_id_hash(mrp_resource_setref_t *ref)
+{
+    mrp_resource_set_t *rset;
+
+    MRP_ASSERT(ref, "invalid argument");
+
+    rset = ref->rset;
+
+    MRP_ASSERT(rset, "confused with data structures");
+
+    if (!mrp_htbl_insert(id_hash, NULL + rset->id, ref))
+        return -1;
+
+    return 0;
+}
+
+static mrp_resource_setref_t *remove_from_id_hash(uint32_t id)
+{
+    return id_hash ? mrp_htbl_remove(id_hash, NULL + id, false) : NULL;
+}
+
+static mrp_resource_setref_t *find_in_id_hash(uint32_t id)
+{
+    return id_hash ? mrp_htbl_lookup(id_hash, NULL + id) : NULL;
+}
+
+
+static field_t field_check(lua_State *L, int idx, const char **ret_fldnam)
+{
+    const char *fldnam;
+    size_t fldnamlen;
+    field_t fldtyp;
+
+    if (!(fldnam = lua_tolstring(L, idx, &fldnamlen)))
+        fldtyp = 0;
+    else
+        fldtyp = field_name_to_type(fldnam, fldnamlen);
+
+    if (ret_fldnam)
+        *ret_fldnam = fldnam;
+
+    return fldtyp;
+}
+
+
+static field_t field_name_to_type(const char *name, size_t len)
+{
+    switch (len) {
+
+    case 2:
+        if (!strcmp(name, "id"))
+            return ID;
+        break;
+
+    case 5:
+        if (!strcmp(name, "state"))
+            return STATE;
+        break;
+
+    case 8:
+        if (!strcmp(name, "resource"))
+            return RESOURCE;
+        break;
+
+    case 9:
+        if (!strcmp(name, "dont_wait"))
+            return DONT_WAIT;
+        break;
+
+    case 10:
+        if (!strcmp(name, "attributes"))
+            return ATTRIBUTES;
+        break;
+
+    case 12:
+        if (!strcmp(name, "auto_release"))
+            return AUTO_RELEASE;
+        if (!strcmp(name, "resource_set"))
+            return RESOURCE_SET;
+        break;
+
+    case 17:
+        if (!strcmp(name, "application_class"))
+            return APPLICATION_CLASS;
+        break;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-lua.h b/src/resource/resource-lua.h
new file mode 100644 (file)
index 0000000..0a7203c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_LUA_H__
+#define __MURPHY_RESOURCE_LUA_H__
+
+#include <lua.h>
+
+#include <murphy/common/hashtbl.h>
+
+#include "data-types.h"
+
+struct mrp_resource_ownersref_s {
+    uint32_t              zoneid;
+    mrp_resource_owner_t *owners;
+};
+
+struct mrp_resource_setref_s {
+    mrp_resource_set_t   *rset;
+};
+
+
+void mrp_resource_lua_init(lua_State *);
+
+bool mrp_resource_lua_veto(mrp_zone_t *, mrp_resource_set_t *,
+                           mrp_resource_owner_t *, mrp_resource_mask_t,
+                           mrp_resource_set_t *);
+void mrp_resource_lua_set_owners(mrp_zone_t *, mrp_resource_owner_t *);
+
+void mrp_resource_lua_register_resource_set(mrp_resource_set_t *);
+void mrp_resource_lua_unregister_resource_set(mrp_resource_set_t *);
+void mrp_resource_lua_add_resource_to_resource_set(mrp_resource_set_t *,
+                                                   mrp_resource_t *);
+
+#endif  /* __MURPHY_RESOURCE_LUA_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-owner.c b/src/resource/resource-owner.c
new file mode 100644 (file)
index 0000000..aa5d5c0
--- /dev/null
@@ -0,0 +1,770 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/log.h>
+
+#include <murphy-db/mqi.h>
+
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/config-api.h>
+
+#include "resource-owner.h"
+#include "application-class.h"
+#include "resource-set.h"
+#include "resource.h"
+#include "zone.h"
+#include "resource-lua.h"
+
+#define NAME_LENGTH          24
+
+#define ZONE_ID_IDX          0
+#define ZONE_NAME_IDX        1
+#define CLASS_NAME_IDX       2
+#define RSET_ID_IDX          3
+#define FIRST_ATTRIBUTE_IDX  4
+
+typedef struct {
+    uint32_t          zone_id;
+    const char       *zone_name;
+    const char       *class_name;
+    uint32_t          rset_id;
+    mrp_attr_value_t  attrs[MQI_COLUMN_MAX];
+} owner_row_t;
+
+static mrp_resource_owner_t  resource_owners[MRP_ZONE_MAX * MRP_RESOURCE_MAX];
+static mqi_handle_t          owner_tables[MRP_RESOURCE_MAX];
+
+static mrp_resource_owner_t *get_owner(uint32_t, uint32_t);
+static void reset_owners(uint32_t, mrp_resource_owner_t *);
+static bool grant_ownership(mrp_resource_owner_t *, mrp_zone_t *,
+                            mrp_application_class_t *, mrp_resource_set_t *,
+                            mrp_resource_t *);
+static bool advice_ownership(mrp_resource_owner_t *, mrp_zone_t *,
+                             mrp_application_class_t *, mrp_resource_set_t *,
+                             mrp_resource_t *);
+
+static void manager_start_transaction(mrp_zone_t *);
+static void manager_end_transaction(mrp_zone_t *);
+
+static void delete_resource_owner(mrp_zone_t *, mrp_resource_t *);
+static void insert_resource_owner(mrp_zone_t *, mrp_application_class_t *,
+                                  mrp_resource_set_t *, mrp_resource_t *);
+static void update_resource_owner(mrp_zone_t *, mrp_application_class_t *,
+                                  mrp_resource_set_t *, mrp_resource_t *);
+static void set_attr_descriptors(mqi_column_desc_t *, mrp_resource_t *);
+
+
+int mrp_resource_owner_create_database_table(mrp_resource_def_t *rdef)
+{
+    MQI_COLUMN_DEFINITION_LIST(base_coldefs,
+        MQI_COLUMN_DEFINITION( "zone_id"          , MQI_UNSIGNED             ),
+        MQI_COLUMN_DEFINITION( "zone_name"        , MQI_VARCHAR(NAME_LENGTH) ),
+        MQI_COLUMN_DEFINITION( "application_class", MQI_VARCHAR(NAME_LENGTH) ),
+        MQI_COLUMN_DEFINITION( "resource_set_id"  , MQI_UNSIGNED             )
+    );
+
+    MQI_INDEX_DEFINITION(indexdef,
+        MQI_INDEX_COLUMN( "zone_id" )
+    );
+
+    static bool initialized = false;
+
+    char name[256];
+    mqi_column_def_t coldefs[MQI_COLUMN_MAX + 1];
+    mqi_column_def_t *col;
+    mrp_attr_def_t *atd;
+    mqi_handle_t table;
+    char c, *p;
+    size_t i,j;
+
+    if (!initialized) {
+        mqi_open();
+        for (i = 0;  i < MRP_RESOURCE_MAX;  i++)
+            owner_tables[i] = MQI_HANDLE_INVALID;
+        initialized = true;
+    }
+
+    MRP_ASSERT(sizeof(base_coldefs) < sizeof(coldefs),"too many base columns");
+    MRP_ASSERT(rdef, "invalid argument");
+    MRP_ASSERT(rdef->id < MRP_RESOURCE_MAX, "confused with data structures");
+    MRP_ASSERT(owner_tables[rdef->id] == MQI_HANDLE_INVALID,
+               "owner table already exist");
+
+    snprintf(name, sizeof(name), "%s_owner", rdef->name);
+    for (p = name; (c = *p);  p++) {
+        if (!isascii(c) || (!isalnum(c) && c != '_'))
+            *p = '_';
+    }
+
+    j = MQI_DIMENSION(base_coldefs) - 1;
+    memcpy(coldefs, base_coldefs, j * sizeof(mqi_column_def_t));
+
+    for (i = 0;  i < rdef->nattr && j < MQI_COLUMN_MAX;  i++, j++) {
+        col = coldefs + j;
+        atd = rdef->attrdefs + i;
+
+        col->name   = atd->name;
+        col->type   = atd->type;
+        col->length = (col->type == mqi_string) ? NAME_LENGTH : 0;
+        col->flags  = 0;
+    }
+
+    memset(coldefs + j, 0, sizeof(mqi_column_def_t));
+
+    table = MQI_CREATE_TABLE(name, MQI_TEMPORARY, coldefs, indexdef);
+
+    if (table == MQI_HANDLE_INVALID) {
+        mrp_log_error("Can't create table '%s': %s", name, strerror(errno));
+        return -1;
+    }
+
+    owner_tables[rdef->id] = table;
+
+    return 0;
+}
+
+void mrp_resource_owner_recalc(uint32_t zoneid)
+{
+    mrp_resource_owner_update_zone(zoneid, NULL, 0);
+}
+
+void mrp_resource_owner_update_zone(uint32_t zoneid,
+                                    mrp_resource_set_t *reqset,
+                                    uint32_t reqid)
+{
+    typedef struct {
+        uint32_t replyid;
+        mrp_resource_set_t *rset;
+        bool move;
+    } event_t;
+
+    mrp_resource_owner_t oldowners[MRP_RESOURCE_MAX];
+    mrp_resource_owner_t backup[MRP_RESOURCE_MAX];
+    mrp_zone_t *zone;
+    mrp_application_class_t *class;
+    mrp_resource_set_t *rset;
+    mrp_resource_t *res;
+    mrp_resource_def_t *rdef;
+    mrp_resource_mgr_ftbl_t *ftbl;
+    mrp_resource_owner_t *owner, *old, *owners;
+    mrp_resource_mask_t mask;
+    mrp_resource_mask_t mandatory;
+    mrp_resource_mask_t grant;
+    mrp_resource_mask_t advice;
+    void *clc, *rsc, *rc;
+    uint32_t rid;
+    uint32_t rcnt;
+    bool force_release;
+    bool changed;
+    bool move;
+    mrp_resource_event_t notify;
+    uint32_t replyid;
+    uint32_t nevent, maxev;
+    event_t *events, *ev, *lastev;
+
+    MRP_ASSERT(zoneid < MRP_ZONE_MAX, "invalid argument");
+
+    zone = mrp_zone_find_by_id(zoneid);
+
+    MRP_ASSERT(zone, "zone is not defined");
+
+    if (!(maxev = mrp_get_resource_set_count()))
+        return;
+
+    nevent = 0;
+    events = mrp_alloc(sizeof(event_t) * maxev);
+
+    MRP_ASSERT(events, "Memory alloc failure. Can't update zone");
+
+    reset_owners(zoneid, oldowners);
+    manager_start_transaction(zone);
+
+    rcnt = mrp_resource_definition_count();
+    clc  = NULL;
+
+    while ((class = mrp_application_class_iterate_classes(&clc))) {
+        rsc = NULL;
+
+        while ((rset=mrp_application_class_iterate_rsets(class,zoneid,&rsc))) {
+            force_release = false;
+            mandatory = rset->resource.mask.mandatory;
+            grant = 0;
+            advice = 0;
+            rc = NULL;
+
+            switch (rset->state) {
+
+            case mrp_resource_acquire:
+                while ((res = mrp_resource_set_iterate_resources(rset, &rc))) {
+                    rdef  = res->def;
+                    rid   = rdef->id;
+                    owner = get_owner(zoneid, rid);
+
+                    backup[rid] = *owner;
+
+                    if (grant_ownership(owner, zone, class, rset, res))
+                        grant |= ((mrp_resource_mask_t)1 << rid);
+                    else {
+                        if (owner->rset != rset)
+                            force_release |= owner->modal;
+                    }
+                }
+                owners = get_owner(zoneid, 0);
+                if ((grant & mandatory) == mandatory &&
+                    mrp_resource_lua_veto(zone, rset, owners, grant, reqset))
+                {
+                    advice = grant;
+                }
+                else {
+                    /* rollback, ie. restore the backed up state */
+                    rc = NULL;
+                    while ((res=mrp_resource_set_iterate_resources(rset,&rc))){
+                        rdef = res->def;
+                        rid = rdef->id;
+                        mask = (mrp_resource_mask_t)1 << rid;
+                        owner = get_owner(zoneid, rid);
+                        *owner = backup[rid];
+
+                        if ((grant & mask)) {
+                            if ((ftbl = rdef->manager.ftbl) && ftbl->free)
+                                ftbl->free(zone, res, rdef->manager.userdata);
+                        }
+
+                        if (advice_ownership(owner, zone, class, rset, res))
+                            advice |= mask;
+                    }
+
+                    grant = 0;
+
+                    if ((advice & mandatory) != mandatory)
+                        advice = 0;
+
+                    mrp_resource_lua_set_owners(zone, owners);
+                }
+                break;
+
+            case mrp_resource_release:
+                while ((res = mrp_resource_set_iterate_resources(rset, &rc))) {
+                    rdef  = res->def;
+                    rid   = rdef->id;
+                    owner = get_owner(zoneid, rid);
+
+                    if (advice_ownership(owner, zone, class, rset, res))
+                        advice |= ((mrp_resource_mask_t)1 << rid);
+                }
+                if ((advice & mandatory) != mandatory)
+                    advice = 0;
+                break;
+
+            default:
+                break;
+            }
+
+            changed = false;
+            move    = false;
+            notify  = 0;
+            replyid = (reqset == rset && reqid == rset->request.id) ? reqid:0;
+
+
+            if (force_release) {
+                move = (rset->state != mrp_resource_release);
+                notify = move ? MRP_RESOURCE_EVENT_RELEASE : 0;
+                changed = move || rset->resource.mask.grant;
+                rset->state = mrp_resource_release;
+                rset->resource.mask.grant = 0;
+            }
+            else {
+                if (grant == rset->resource.mask.grant) {
+                    if (rset->state == mrp_resource_acquire &&
+                        !grant && rset->dont_wait.current)
+                    {
+                        rset->state = mrp_resource_release;
+                        rset->dont_wait.current = rset->dont_wait.client;
+
+                        notify = MRP_RESOURCE_EVENT_RELEASE;
+                        move = true;
+                    }
+                }
+                else {
+                    rset->resource.mask.grant = grant;
+                    changed = true;
+
+                    if (rset->state != mrp_resource_release &&
+                        !grant && rset->auto_release.current)
+                    {
+                        rset->state = mrp_resource_release;
+                        rset->auto_release.current = rset->auto_release.client;
+
+                        notify = MRP_RESOURCE_EVENT_RELEASE;
+                        move = true;
+                    }
+                }
+            }
+
+            if (notify) {
+                mrp_resource_set_notify(rset, notify);
+            }
+
+            if (advice != rset->resource.mask.advice) {
+                rset->resource.mask.advice = advice;
+                changed = true;
+            }
+
+            if (replyid || changed) {
+                ev = events + nevent++;
+
+                ev->replyid = replyid;
+                ev->rset    = rset;
+                ev->move    = move;
+            }
+        } /* while rset */
+    } /* while class */
+
+    manager_end_transaction(zone);
+
+    for (lastev = (ev = events) + nevent;     ev < lastev;     ev++) {
+        rset = ev->rset;
+
+        if (ev->move)
+            mrp_application_class_move_resource_set(rset);
+
+        mrp_resource_set_updated(rset);
+
+        /* first we send out the revoke/deny events
+         * followed by the grants (in the next for loop)
+         */
+        if (rset->event && !rset->resource.mask.grant)
+            rset->event(ev->replyid, rset, rset->user_data);
+    }
+
+    for (lastev = (ev = events) + nevent;     ev < lastev;     ev++) {
+        rset = ev->rset;
+
+        if (rset->event && rset->resource.mask.grant)
+            rset->event(ev->replyid, rset, rset->user_data);
+    }
+
+    mrp_free(events);
+
+    for (rid = 0;  rid < rcnt;  rid++) {
+        owner = get_owner(zoneid, rid);
+        old   = oldowners + rid;
+
+        if (owner->class != old->class ||
+            owner->rset  != old->rset  ||
+            owner->res   != old->res     )
+        {
+            if (!owner->res)
+               delete_resource_owner(zone,old->res);
+            else if (!old->res)
+               insert_resource_owner(zone,owner->class,owner->rset,owner->res);
+            else
+               update_resource_owner(zone,owner->class,owner->rset,owner->res);
+        }
+    }
+}
+
+int mrp_resource_owner_print(char *buf, int len)
+{
+#define PRINT(fmt, args...)  if (p<e) { p += snprintf(p, e-p, fmt , ##args); }
+
+    mrp_zone_t *zone;
+    mrp_resource_owner_t *owner;
+    mrp_application_class_t *class;
+    mrp_resource_set_t *rset;
+    mrp_resource_t *res;
+    mrp_resource_def_t *rdef;
+    uint32_t rcnt, rid;
+    uint32_t zcnt, zid;
+    char *p, *e;
+
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(buf, "invalid argument");
+
+    rcnt = mrp_resource_definition_count();
+    zcnt = mrp_zone_count();
+
+    e = (p = buf) + len;
+
+    PRINT("Resource owners:\n");
+
+    for (zid = 0;  zid < zcnt;  zid++) {
+        zone = mrp_zone_find_by_id(zid);
+
+        if (!zone) {
+            PRINT("   Zone %u:\n", zid);
+        }
+        else {
+            PRINT("   Zone %s:", zone->name);
+            p += mrp_zone_attribute_print(zone, p, e-p);
+            PRINT("\n");
+        }
+
+        for (rid = 0;   rid < rcnt;   rid++) {
+            if (!(rdef = mrp_resource_definition_find_by_id(rid)))
+                continue;
+
+            PRINT("      %-15s: ", rdef->name);
+
+            owner = get_owner(zid, rid);
+
+            if (!(class = owner->class) ||
+                !(rset  = owner->rset ) ||
+                !(res   = owner->res  )    )
+            {
+                PRINT("<nobody>");
+            }
+            else {
+                MRP_ASSERT(rdef == res->def, "confused with data structures");
+
+                PRINT("%-15s", class->name);
+
+                p += mrp_resource_attribute_print(res, p, e-p);
+            }
+
+            PRINT("\n");
+        }
+    }
+
+    return p - buf;
+
+#undef PRINT
+}
+
+
+static mrp_resource_owner_t *get_owner(uint32_t zone, uint32_t resid)
+{
+    MRP_ASSERT(zone < MRP_ZONE_MAX && resid < MRP_RESOURCE_MAX,
+               "invalid argument");
+
+    return resource_owners + (zone * MRP_RESOURCE_MAX + resid);
+}
+
+static void reset_owners(uint32_t zone, mrp_resource_owner_t *oldowners)
+{
+    mrp_resource_owner_t *owners = get_owner(zone, 0);
+    size_t size = sizeof(mrp_resource_owner_t) * MRP_RESOURCE_MAX;
+    size_t i;
+
+    if (oldowners)
+        memcpy(oldowners, owners, size);
+
+    memset(owners, 0, size);
+
+    for (i = 0;   i < MRP_RESOURCE_MAX;   i++)
+        owners[i].share = true;
+}
+
+static bool grant_ownership(mrp_resource_owner_t    *owner,
+                            mrp_zone_t              *zone,
+                            mrp_application_class_t *class,
+                            mrp_resource_set_t      *rset,
+                            mrp_resource_t          *res)
+{
+    mrp_resource_def_t      *rdef = res->def;
+    mrp_resource_mgr_ftbl_t *ftbl = rdef->manager.ftbl;
+    bool                     set_owner = false;
+
+    /*
+      if (forbid_grant())
+        return false;
+     */
+
+    if (owner->modal)
+        return false;
+
+    do { /* not a loop */
+        if (!owner->class && !owner->rset) {
+            /* nobody owns this, so grab it */
+            set_owner = true;
+            break;
+        }
+
+        if (owner->class == class && owner->rset == rset) {
+            /* we happen to already own it */
+            break;
+        }
+
+        if (rdef->shareable && owner->share) {
+            /* OK, someone else owns it but
+               the owner is ready to share it with us */
+            break;
+        }
+
+        return false;
+
+    } while(0);
+
+    if (ftbl && ftbl->allocate) {
+        if (!ftbl->allocate(zone, res, rdef->manager.userdata))
+            return false;
+    }
+
+    if (set_owner) {
+        owner->class = class;
+        owner->rset  = rset;
+        owner->res   = res;
+        owner->modal = class->modal;
+    }
+
+    owner->share = class->share && res->shared;
+
+    return true;
+}
+
+static bool advice_ownership(mrp_resource_owner_t    *owner,
+                             mrp_zone_t              *zone,
+                             mrp_application_class_t *class,
+                             mrp_resource_set_t      *rset,
+                             mrp_resource_t          *res)
+{
+    mrp_resource_def_t      *rdef = res->def;
+    mrp_resource_mgr_ftbl_t *ftbl = rdef->manager.ftbl;
+
+    (void)zone;
+
+    /*
+      if (forbid_grant())
+        return false;
+     */
+
+    if (owner->modal)
+        return false;
+
+    do { /* not a loop */
+        if (!owner->class && !owner->rset)
+            /* nobody owns this */
+            break;
+
+        if (owner->share)
+            /* someone else owns it but it can be shared */
+            break;
+
+        if (owner->class == class) {
+            if (owner->rset->class.priority == rset->class.priority &&
+                    class->order == MRP_RESOURCE_ORDER_LIFO)
+                /* same class and resource goes to the last one who asks it */
+                break;
+        }
+
+        return false;
+
+    } while(0);
+
+    if (ftbl && ftbl->advice) {
+        if (!ftbl->advice(zone, res, rdef->manager.userdata))
+            return false;
+    }
+
+    return true;
+}
+
+static void manager_start_transaction(mrp_zone_t *zone)
+{
+    mrp_resource_def_t *rdef;
+    mrp_resource_mgr_ftbl_t *ftbl;
+    void *cursor = NULL;
+
+    while ((rdef = mrp_resource_definition_iterate_manager(&cursor))) {
+        ftbl = rdef->manager.ftbl;
+
+        MRP_ASSERT(ftbl, "confused with data structures");
+
+        if (ftbl->init)
+            ftbl->init(zone, rdef->manager.userdata);
+    }
+}
+
+static void manager_end_transaction(mrp_zone_t *zone)
+{
+    mrp_resource_def_t *rdef;
+    mrp_resource_mgr_ftbl_t *ftbl;
+    void *cursor = NULL;
+
+    while ((rdef = mrp_resource_definition_iterate_manager(&cursor))) {
+        ftbl = rdef->manager.ftbl;
+
+        MRP_ASSERT(ftbl, "confused with data structures");
+
+        if (ftbl->commit)
+            ftbl->commit(zone, rdef->manager.userdata);
+    }
+}
+
+
+static void delete_resource_owner(mrp_zone_t *zone, mrp_resource_t *res)
+{
+    static uint32_t zone_id;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(0), MQI_UNSIGNED_VAR(zone_id) )
+    );
+
+    mrp_resource_def_t *rdef;
+    int n;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    rdef = res->def;
+    zone_id = zone->id;
+
+    if ((n = MQI_DELETE(owner_tables[rdef->id], where)) != 1)
+        mrp_log_error("Could not delete resource owner");
+}
+
+static void insert_resource_owner(mrp_zone_t *zone,
+                                  mrp_application_class_t *class,
+                                  mrp_resource_set_t *rset,
+                                  mrp_resource_t *res)
+{
+    mrp_resource_def_t *rdef = res->def;
+    uint32_t i;
+    int n;
+    owner_row_t row;
+    owner_row_t *rows[2];
+    mqi_column_desc_t cdsc[FIRST_ATTRIBUTE_IDX + MQI_COLUMN_MAX + 1];
+
+    MRP_ASSERT(FIRST_ATTRIBUTE_IDX + rdef->nattr <= MQI_COLUMN_MAX,
+               "too many attributes for a table");
+
+    row.zone_id    = zone->id;
+    row.zone_name  = zone->name;
+    row.class_name = class->name;
+    row.rset_id    = rset->id;
+    memcpy(row.attrs, res->attrs, rdef->nattr * sizeof(mrp_attr_value_t));
+
+    i = 0;
+    cdsc[i].cindex = ZONE_ID_IDX;
+    cdsc[i].offset = MQI_OFFSET(owner_row_t, zone_id);
+
+    i++;
+    cdsc[i].cindex = ZONE_NAME_IDX;
+    cdsc[i].offset = MQI_OFFSET(owner_row_t, zone_name);
+
+    i++;
+    cdsc[i].cindex = CLASS_NAME_IDX;
+    cdsc[i].offset = MQI_OFFSET(owner_row_t, class_name);
+
+    i++;
+    cdsc[i].cindex = RSET_ID_IDX;
+    cdsc[i].offset = MQI_OFFSET(owner_row_t, rset_id);
+    
+    set_attr_descriptors(cdsc + (i+1), res);
+
+    rows[0] = &row;
+    rows[1] = NULL;
+
+    if ((n = MQI_INSERT_INTO(owner_tables[rdef->id], cdsc, rows)) != 1)
+        mrp_log_error("can't insert row into owner table");
+}
+
+static void update_resource_owner(mrp_zone_t *zone,
+                                  mrp_application_class_t *class,
+                                  mrp_resource_set_t *rset,
+                                  mrp_resource_t *res)
+{
+    static uint32_t zone_id;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(0), MQI_UNSIGNED_VAR(zone_id) )
+    );
+
+    mrp_resource_def_t *rdef = res->def;
+    uint32_t i;
+    int n;
+    owner_row_t row;
+    mqi_column_desc_t cdsc[FIRST_ATTRIBUTE_IDX + MQI_COLUMN_MAX + 1];
+
+    zone_id = zone->id;
+
+    MRP_ASSERT(1 + rdef->nattr <= MQI_COLUMN_MAX,
+               "too many attributes for a table");
+
+    row.class_name = class->name;
+    row.rset_id    = rset->id;
+    memcpy(row.attrs, res->attrs, rdef->nattr * sizeof(mrp_attr_value_t));
+
+    i = 0;
+    cdsc[i].cindex = CLASS_NAME_IDX;
+    cdsc[i].offset = MQI_OFFSET(owner_row_t, class_name);
+
+    i++;
+    cdsc[i].cindex = RSET_ID_IDX;
+    cdsc[i].offset = MQI_OFFSET(owner_row_t, rset_id);
+
+    set_attr_descriptors(cdsc + (i+1), res);
+
+
+    if ((n = MQI_UPDATE(owner_tables[rdef->id], cdsc, &row, where)) != 1)
+        mrp_log_error("can't update row in owner table");
+}
+
+
+static void set_attr_descriptors(mqi_column_desc_t *cdsc, mrp_resource_t *res)
+{
+    mrp_resource_def_t *rdef = res->def;
+    uint32_t i,j;
+    int o;
+
+    for (i = j = 0;  j < rdef->nattr;  j++) {
+        switch (rdef->attrdefs[j].type) {
+        case mqi_string:   o = MQI_OFFSET(owner_row_t,attrs[j].string);  break;
+        case mqi_integer:  o = MQI_OFFSET(owner_row_t,attrs[j].integer); break;
+        case mqi_unsignd:  o = MQI_OFFSET(owner_row_t,attrs[j].unsignd); break;
+        case mqi_floating: o = MQI_OFFSET(owner_row_t,attrs[j].floating);break;
+        default:           /* skip this */                            continue;
+        }
+
+        cdsc[i].cindex = FIRST_ATTRIBUTE_IDX + j;
+        cdsc[i].offset = o;
+        i++;
+    }
+
+    cdsc[i].cindex = -1;
+    cdsc[i].offset =  1;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-owner.h b/src/resource/resource-owner.h
new file mode 100644 (file)
index 0000000..2d00380
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_OWNER_H__
+#define __MURPHY_RESOURCE_OWNER_H__
+
+#include "data-types.h"
+
+
+
+struct mrp_resource_owner_s {
+    mrp_application_class_t *class;    /**< owner application class */
+    mrp_resource_set_t      *rset;     /**< owner resource set  */
+    mrp_resource_t          *res;      /**< owner resource */
+    /* the following fields are for
+       internal use only. They used during
+       update and have no meaningful values
+       outside of the update procedure */
+    bool                     modal;
+    bool                     share;
+    bool                     release;
+};
+
+
+int  mrp_resource_owner_create_database_table(mrp_resource_def_t *);
+void mrp_resource_owner_update_zone(uint32_t, mrp_resource_set_t *, uint32_t);
+
+
+#endif  /* __MURPHY_RESOURCE_OWNER_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-set.c b/src/resource/resource-set.c
new file mode 100644 (file)
index 0000000..12e56d7
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/hashtbl.h>
+#include <murphy/common/utils.h>
+#include <murphy/common/log.h>
+#include <murphy/common/mainloop.h>
+
+#include <murphy-db/mqi.h>
+
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/common-api.h>
+
+#include "resource-set.h"
+#include "application-class.h"
+#include "resource.h"
+#include "resource-client.h"
+#include "resource-owner.h"
+#include "resource-lua.h"
+
+
+#define STAMP_MAX     ((uint32_t)1 << MRP_KEY_STAMP_BITS)
+#define PRIORITY_MAX  ((uint32_t)1 << MRP_KEY_PRIORITY_BITS)
+
+
+static MRP_LIST_HOOK(resource_set_list);
+static uint32_t resource_set_count;
+static mrp_htbl_t *id_hash;
+
+static int add_to_id_hash(mrp_resource_set_t *);
+static void remove_from_id_hash(mrp_resource_set_t *);
+
+static mrp_resource_t *find_resource_by_name(mrp_resource_set_t *,const char*);
+#if 0
+static mrp_resource_t *find_resource_by_id(mrp_resource_set_t *, uint32_t);
+#endif
+
+static uint32_t get_request_stamp(void);
+static const char *state_str(mrp_resource_state_t);
+static void send_rset_event(mrp_resource_set_t *rset,
+        mrp_resource_event_t ev);
+
+uint32_t mrp_get_resource_set_count(void)
+{
+    return resource_set_count;
+}
+
+mrp_resource_set_t *mrp_resource_set_create(mrp_resource_client_t *client,
+                                            bool auto_release,
+                                            bool dont_wait,
+                                            uint32_t priority,
+                                            mrp_resource_event_cb_t event_cb,
+                                            void *user_data)
+{
+    static uint32_t our_id;
+
+    mrp_resource_set_t *rset;
+
+    MRP_ASSERT(client, "invalid argument");
+
+    if (priority >= PRIORITY_MAX)
+        priority = PRIORITY_MAX - 1;
+
+    if (!(rset = mrp_allocz(sizeof(mrp_resource_set_t))))
+        mrp_log_error("Memory alloc failure. Can't create resource set");
+    else {
+        rset->id = ++our_id;
+
+        rset->dont_wait.current = dont_wait;
+        rset->dont_wait.client  = dont_wait;
+
+        rset->auto_release.current = auto_release;
+        rset->auto_release.client  = auto_release;
+
+        mrp_list_init(&rset->resource.list);
+        rset->resource.share = false;
+
+        mrp_list_append(&client->resource_sets, &rset->client.list);
+        rset->client.ptr = client;
+        rset->client.reqno = MRP_RESOURCE_REQNO_INVALID;
+
+        mrp_list_init(&rset->class.list);
+        rset->class.priority = priority;
+
+        mrp_list_append(&resource_set_list, &rset->list);
+
+        rset->event = event_cb;
+        rset->user_data = user_data;
+
+        resource_set_count++;
+
+        add_to_id_hash(rset);
+        mrp_resource_lua_register_resource_set(rset);
+
+        send_rset_event(rset, MRP_RESOURCE_EVENT_CREATED);
+    }
+
+    return rset;
+}
+
+void mrp_resource_set_destroy(mrp_resource_set_t *rset)
+{
+    mrp_resource_state_t state;
+    mrp_list_hook_t *entry, *n;
+    mrp_resource_t *res;
+
+    if (rset) {
+        state = rset->state;
+
+        rset->event = NULL; /* make sure nothing is sent any more */
+
+        send_rset_event(rset, MRP_RESOURCE_EVENT_DESTROYED);
+
+        mrp_resource_lua_unregister_resource_set(rset);
+        remove_from_id_hash(rset);
+
+        if (state == mrp_resource_acquire)
+            mrp_resource_set_release(rset, MRP_RESOURCE_REQNO_INVALID);
+
+        mrp_list_foreach(&rset->resource.list, entry, n) {
+            res = mrp_list_entry(entry, mrp_resource_t, list);
+            mrp_resource_notify(res, rset, MRP_RESOURCE_EVENT_DESTROYED);
+            mrp_resource_destroy(res);
+        }
+
+        mrp_list_delete(&rset->list);
+        mrp_list_delete(&rset->client.list);
+        mrp_list_delete(&rset->class.list);
+
+        mrp_free(rset);
+
+        if (resource_set_count > 0)
+            resource_set_count--;
+    }
+}
+
+mrp_resource_set_t *mrp_resource_set_find_by_id(uint32_t id)
+{
+    return id_hash ? mrp_htbl_lookup(id_hash, NULL + id) : NULL;
+}
+
+uint32_t mrp_get_resource_set_id(mrp_resource_set_t *rset)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    return rset->id;
+}
+
+mrp_resource_state_t mrp_get_resource_set_state(mrp_resource_set_t *rset)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    return rset->state;
+}
+
+mrp_resource_mask_t mrp_get_resource_set_grant(mrp_resource_set_t *rset)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    return rset->resource.mask.grant;
+}
+
+mrp_resource_mask_t mrp_get_resource_set_advice(mrp_resource_set_t *rset)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    return rset->resource.mask.advice;
+}
+
+mrp_resource_client_t *mrp_get_resource_set_client(mrp_resource_set_t *rset)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    return rset->client.ptr;
+}
+
+mrp_resource_t *mrp_resource_set_find_resource(uint32_t rsetid,
+                                               const char *resnam)
+{
+    mrp_resource_set_t *rset;
+    mrp_resource_t *res;
+
+    MRP_ASSERT(resnam, "invalid argument");
+
+    if (!(rset = mrp_resource_set_find_by_id(rsetid)))
+        res = NULL;
+    else
+        res = find_resource_by_name(rset, resnam);
+
+    return res;
+}
+
+
+mrp_resource_t *mrp_resource_set_iterate_resources(mrp_resource_set_t *rset,
+                                                   void **cursor)
+{
+    mrp_list_hook_t *list, *entry;
+
+    MRP_ASSERT(rset && cursor, "invalid argument");
+
+    list  = &rset->resource.list;
+    entry = (*cursor == NULL) ? list->next : (mrp_list_hook_t *)*cursor;
+
+    if (entry == list)
+        return NULL;
+
+    *cursor = entry->next;
+
+    return mrp_list_entry(entry, mrp_resource_t, list);
+}
+
+
+int mrp_resource_set_add_resource(mrp_resource_set_t *rset,
+                                  const char         *name,
+                                  bool                shared,
+                                  mrp_attr_t         *attrs,
+                                  bool                mandatory)
+{
+    uint32_t mask;
+    mrp_resource_t *res;
+    uint32_t rsetid;
+    bool autorel;
+
+    MRP_ASSERT(rset && name, "invalid argument");
+
+    rsetid  = rset->id;
+    autorel = rset->auto_release.client;
+
+    if (!(res = mrp_resource_create(name, rsetid, autorel, shared, attrs))) {
+        mrp_log_error("Can't add resource '%s' name to resource set %u",
+                      name, rset->id);
+        return -1;
+    }
+
+    mask = mrp_resource_get_mask(res);
+
+    rset->resource.mask.all       |= mask;
+    rset->resource.mask.mandatory |= mandatory ? mask : 0;
+    rset->resource.share          |= mrp_resource_is_shared(res);
+
+
+    mrp_list_append(&rset->resource.list, &res->list);
+
+    mrp_resource_lua_add_resource_to_resource_set(rset, res);
+
+    return 0;
+}
+
+mrp_attr_t *mrp_resource_set_read_attribute(mrp_resource_set_t *rset,
+                                            const char *resnam,
+                                            uint32_t attridx,
+                                            mrp_attr_t *buf)
+{
+    mrp_resource_t *res;
+
+    MRP_ASSERT(rset && resnam, "invalid argument");
+
+    if (!(res = find_resource_by_name(rset, resnam)))
+        return NULL;
+
+    return mrp_resource_read_attribute(res, attridx, buf);
+}
+
+mrp_attr_t *mrp_resource_set_read_all_attributes(mrp_resource_set_t *rset,
+                                                 const char *resnam,
+                                                 uint32_t buflen,
+                                                 mrp_attr_t *buf)
+{
+    mrp_resource_t *res;
+
+    MRP_ASSERT(rset && resnam, "invalid argument");
+
+    if (!(res = find_resource_by_name(rset, resnam)))
+        return NULL;
+
+    return mrp_resource_read_all_attributes(res, buflen, buf);
+}
+
+int mrp_resource_set_write_attributes(mrp_resource_set_t *rset,
+                                      const char *resnam,
+                                      mrp_attr_t *attrs)
+{
+    mrp_resource_t *res;
+
+    MRP_ASSERT(rset && resnam && attrs, "invalid argument");
+
+    if (!(res = find_resource_by_name(rset, resnam)))
+        return -1;
+
+    if (mrp_resource_write_attributes(res, attrs) < 0)
+        return -1;
+
+    return 0;
+}
+
+void mrp_resource_set_acquire(mrp_resource_set_t *rset, uint32_t reqid)
+{
+    mrp_resource_state_t old_state;
+    mqi_handle_t trh;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    mrp_debug("acquiring resource set #%d", rset->id);
+
+    old_state = rset->state;
+    rset->state = mrp_resource_acquire;
+
+    if (rset->class.ptr) {
+        rset->request.id = reqid;
+        rset->request.stamp = get_request_stamp();
+
+        mrp_application_class_move_resource_set(rset);
+
+        if (old_state != mrp_resource_acquire)
+            mrp_resource_set_notify(rset, MRP_RESOURCE_EVENT_ACQUIRE);
+
+        trh = mqi_begin_transaction();
+        mrp_resource_owner_update_zone(rset->zone, rset, reqid);
+        mqi_commit_transaction(trh);
+    }
+}
+
+void mrp_resource_set_release(mrp_resource_set_t *rset, uint32_t reqid)
+{
+    mqi_handle_t trh;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    mrp_debug("releasing resource set #%d", rset->id);
+
+    if (!rset->class.ptr)
+        rset->state = mrp_resource_release;
+    else {
+        if (rset->state == mrp_resource_release) {
+            if (rset->event)
+                rset->event(reqid, rset, rset->user_data);
+        }
+        else {
+            rset->state = mrp_resource_release;
+            rset->request.id = reqid;
+            rset->request.stamp = get_request_stamp();
+
+            mrp_application_class_move_resource_set(rset);
+
+            mrp_resource_set_notify(rset, MRP_RESOURCE_EVENT_RELEASE);
+
+            trh = mqi_begin_transaction();
+            mrp_resource_owner_update_zone(rset->zone, rset, reqid);
+            mqi_commit_transaction(trh);
+        }
+    }
+}
+
+void mrp_resource_set_updated(mrp_resource_set_t *rset)
+{
+    mrp_resource_t *res;
+    mrp_resource_def_t *def;
+    mrp_list_hook_t *resen, *n;
+    mrp_resource_mask_t mask;
+    bool grant;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    mrp_debug("resource set got #%d updated", rset->id);
+
+    mrp_list_foreach(&rset->resource.list, resen, n) {
+        res = mrp_list_entry(resen, mrp_resource_t, list);
+        def = res->def;
+
+        mask = ((mrp_resource_mask_t)1) << def->id;
+        grant =  (mask & rset->resource.mask.grant) ? true : false;
+
+        mrp_debug("    %s now %sgranted", def->name, grant ? "" : "not ");
+
+        mrp_resource_user_update(res, rset->state, grant);
+    }
+}
+
+
+MRP_REGISTER_EVENTS(resource_events,
+       MRP_EVENT(MURPHY_RESOURCE_EVENT_CREATED  , MRP_RESOURCE_EVENT_CREATED  ),
+       MRP_EVENT(MURPHY_RESOURCE_EVENT_DESTROYED, MRP_RESOURCE_EVENT_DESTROYED),
+       MRP_EVENT(MURPHY_RESOURCE_EVENT_ACQUIRE  , MRP_RESOURCE_EVENT_ACQUIRE  ),
+       MRP_EVENT(MURPHY_RESOURCE_EVENT_RELEASE  , MRP_RESOURCE_EVENT_RELEASE  ));
+
+
+
+static void send_rset_event(mrp_resource_set_t *rset, mrp_resource_event_t ev)
+{
+    mrp_event_bus_t *bus   = MRP_GLOBAL_BUS;
+    uint32_t         id    = resource_events[ev].id;
+    int              flags = MRP_EVENT_SYNCHRONOUS;;
+    uint16_t          tag  = MRP_RESOURCE_TAG_RSET_ID;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    /* The resource set id is enough information, because the full resource
+     * set can be found by making a query with the id. */
+
+
+    mrp_debug("emit event %d for rset %u", id, rset->id);
+
+    switch (ev) {
+    case MRP_RESOURCE_EVENT_CREATED:
+        mrp_event_emit_msg(bus, id, flags, MRP_MSG_TAG_UINT32(tag, rset->id));
+        break;
+    case MRP_RESOURCE_EVENT_ACQUIRE:
+        mrp_event_emit_msg(bus, id, flags, MRP_MSG_TAG_UINT32(tag, rset->id));
+        break;
+    case MRP_RESOURCE_EVENT_RELEASE:
+        mrp_event_emit_msg(bus, id, flags, MRP_MSG_TAG_UINT32(tag, rset->id));
+        break;
+    case MRP_RESOURCE_EVENT_DESTROYED:
+        mrp_event_emit_msg(bus, id, flags, MRP_MSG_TAG_UINT32(tag, rset->id));
+        break;
+    default:
+        break;
+    }
+}
+
+void mrp_resource_set_notify(mrp_resource_set_t *rset, mrp_resource_event_t ev)
+{
+    mrp_resource_t *res;
+    void *cursor = NULL;
+
+    MRP_ASSERT(rset, "invalid argument");
+
+    send_rset_event(rset, ev);
+
+    while ((res = mrp_resource_set_iterate_resources(rset, &cursor)))
+        mrp_resource_notify(res, rset, ev);
+}
+
+void mrp_resource_set_request_auto_release(mrp_resource_set_t *rset,
+                                           bool auto_release)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    rset->auto_release.current = auto_release;
+}
+
+void mrp_resource_set_request_dont_wait(mrp_resource_set_t *rset,
+                                        bool dont_wait)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    rset->dont_wait.current = dont_wait;
+}
+
+int mrp_resource_set_print(mrp_resource_set_t *rset, size_t indent,
+                           char *buf, int len)
+{
+#define PRINT(fmt, args...)  if (p<e) { p += snprintf(p, e-p, fmt , ##args); }
+
+    mrp_resource_t *res;
+    mrp_list_hook_t *resen, *n;
+    uint32_t mandatory;
+    char gap[] = "                         ";
+    char *p, *e;
+
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(rset && indent < sizeof(gap)-1 && buf,
+               "invalid argument");
+
+    gap[indent] = '\0';
+
+    e = (p = buf) + len;
+
+    mandatory = rset->resource.mask.mandatory;
+
+    PRINT("%s%3u - 0x%02x/0x%02x 0x%02x/0x%02x 0x%08x %d %s%s%s %s\n",
+          gap, rset->id,
+          rset->resource.mask.all, mandatory,
+          rset->resource.mask.grant, rset->resource.mask.advice,
+          mrp_application_class_get_sorting_key(rset), rset->class.priority,
+          rset->resource.share ? "shared   ":"exclusive",
+          rset->auto_release.client ? ",autorelease" : "",
+          rset->dont_wait.client ? ",dontwait" : "",
+          state_str(rset->state));
+
+    mrp_list_foreach(&rset->resource.list, resen, n) {
+        res = mrp_list_entry(resen, mrp_resource_t, list);
+        p  += mrp_resource_print(res, mandatory, indent+6, p, e-p);
+    }
+
+    if (p >= e) {
+        if (len >= 5) {
+            e[-5] = e[-4] = e[-3] = '.';
+            e[-2] = '\n';
+            e[-1] = '\0';
+        }
+    }
+
+    return p - buf;
+
+#undef PRINT
+}
+
+static uint32_t rset_hash(const void *key)
+{
+    return (uint32_t)(key - NULL);
+}
+
+static int rset_comp(const void *key1, const void *key2)
+{
+    uint32_t k1 = key1 - NULL;
+    uint32_t k2 = key2 - NULL;
+
+    return (k1 == k2) ? 0 : ((k1 > k2) ? 1 : -1);
+}
+
+static void init_id_hash(void)
+{
+    mrp_htbl_config_t cfg;
+
+    if (!id_hash) {
+        cfg.nentry  = 32;
+        cfg.comp    = rset_comp;
+        cfg.hash    = rset_hash;
+        cfg.free    = NULL;
+        cfg.nbucket = cfg.nentry;
+
+        id_hash = mrp_htbl_create(&cfg);
+
+        MRP_ASSERT(id_hash, "failed to make id_hash for resource sets");
+    }
+}
+
+static int add_to_id_hash(mrp_resource_set_t *rset)
+{
+    MRP_ASSERT(rset, "invalid argument");
+
+    init_id_hash();
+
+    if (!mrp_htbl_insert(id_hash, NULL + rset->id, rset))
+        return -1;
+
+    return 0;
+}
+
+static void remove_from_id_hash(mrp_resource_set_t *rset)
+{
+    mrp_resource_set_t *deleted;
+
+    if (id_hash && rset) {
+        deleted = mrp_htbl_remove(id_hash, NULL + rset->id, false);
+
+        MRP_ASSERT(!deleted || deleted == rset, "confused with data "
+                   "structures when deleting resource-set from id hash");
+
+        /* in case we were not compiled with debug enabled */
+        if (deleted != rset) {
+            mrp_log_error("confused with data structures when deleting "
+                          "resource-set '%u' from id hash", rset->id);
+        }
+    }
+}
+
+static mrp_resource_t *find_resource_by_name(mrp_resource_set_t *rset,
+                                             const char *name)
+{
+    mrp_list_hook_t *entry, *n;
+    mrp_resource_t *res;
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(rset && name, "invalid_argument");
+
+    mrp_list_foreach(&rset->resource.list, entry, n) {
+        res = mrp_list_entry(entry, mrp_resource_t, list);
+        rdef = res->def;
+
+        MRP_ASSERT(rdef, "confused with data structures");
+
+        if (!strcasecmp(name, rdef->name))
+            return res;
+    }
+
+    return NULL;
+}
+
+#if 0
+static mrp_resource_t *find_resource_by_id(mrp_resource_set_t *rset,
+                                           uint32_t id)
+{
+    mrp_list_hook_t *entry, *n;
+    mrp_resource_t *res;
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(rset, "invalid_argument");
+
+    mrp_list_foreach(&rset->resource.list, entry, n) {
+        res = mrp_list_entry(entry, mrp_resource_t, list);
+        rdef = res->def;
+
+        MRP_ASSERT(rdef, "confused with data structures");
+
+        if (id == rdef->id)
+            return res;
+    }
+
+    return NULL;
+}
+#endif
+
+static uint32_t get_request_stamp(void)
+{
+    static uint32_t  stamp;
+
+    mrp_list_hook_t *entry, *n;
+    mrp_resource_set_t *rset;
+    uint32_t min;
+
+    if ((min = stamp) >= STAMP_MAX) {
+        mrp_log_info("rebasing resource set stamps");
+
+        mrp_list_foreach(&resource_set_list, entry, n) {
+            rset = mrp_list_entry(entry, mrp_resource_set_t, list);
+            if (rset->request.stamp < min)
+                min = rset->request.stamp;
+        }
+
+        stamp -= min;
+
+        mrp_list_foreach(&resource_set_list, entry, n) {
+            rset = mrp_list_entry(entry, mrp_resource_set_t, list);
+            rset->request.stamp -= min;
+        }
+    }
+
+    MRP_ASSERT(stamp < STAMP_MAX, "Request stamp overflow");
+
+    return stamp++;
+}
+
+static const char *state_str(mrp_resource_state_t state)
+{
+    switch(state) {
+    case mrp_resource_no_request:     return "no-request";
+    case mrp_resource_release:        return "release";
+    case mrp_resource_acquire:        return "acquire";
+    default:                          return "< ??? >";
+    }
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource-set.h b/src/resource/resource-set.h
new file mode 100644 (file)
index 0000000..088aad1
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_SET_H__
+#define __MURPHY_RESOURCE_SET_H__
+
+#include <murphy/common/list.h>
+
+#include "data-types.h"
+
+/* event protocol */
+
+#define MURPHY_RESOURCE_EVENT_CREATED   "resource_set_created"
+#define MURPHY_RESOURCE_EVENT_DESTROYED "resource_set_destroyed"
+#define MURPHY_RESOURCE_EVENT_ACQUIRE   "resource_set_acquire"
+#define MURPHY_RESOURCE_EVENT_RELEASE   "resource_set_release"
+
+#define MRP_RESOURCE_TAG_RSET_ID ((uint16_t) 1)
+
+struct mrp_resource_set_s {
+    mrp_list_hook_t                 list;
+    uint32_t                        id;
+    mrp_resource_state_t            state;
+    struct {
+        bool current;
+        bool client;
+    }                               auto_release;
+    struct {
+        bool current;
+        bool client;
+    }                               dont_wait;
+    struct {
+        struct {
+            mrp_resource_mask_t all;
+            mrp_resource_mask_t mandatory;
+            mrp_resource_mask_t grant;
+            mrp_resource_mask_t advice;
+        } mask;
+        mrp_list_hook_t list;
+        bool share;
+    }                               resource;
+    struct {
+        mrp_list_hook_t list;
+        mrp_resource_client_t *ptr;
+        uint32_t reqno;
+    }                               client;
+    struct {
+        mrp_list_hook_t list;
+        mrp_application_class_t *ptr;
+        uint32_t priority;
+    }                               class;
+    uint32_t                        zone;
+    struct {
+        uint32_t id;
+        uint32_t stamp;
+    }                               request;
+    mrp_resource_event_cb_t         event;
+    void                           *user_data;
+};
+
+
+mrp_resource_set_t *mrp_resource_set_find_by_id(uint32_t);
+mrp_resource_t     *mrp_resource_set_find_resource(uint32_t, const char *);
+uint32_t            mrp_get_resource_set_count(void);
+void                mrp_resource_set_updated(mrp_resource_set_t *);
+void                mrp_resource_set_notify(mrp_resource_set_t *,
+                                            mrp_resource_event_t);
+void                mrp_resource_set_request_auto_release(mrp_resource_set_t *,
+                                                          bool);
+void                mrp_resource_set_request_dont_wait(mrp_resource_set_t *,
+                                                       bool);
+int                 mrp_resource_set_print(mrp_resource_set_t *, size_t,
+                                           char *, int);
+
+
+#endif  /* __MURPHY_RESOURCE_SET_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource.c b/src/resource/resource.c
new file mode 100644 (file)
index 0000000..25eb865
--- /dev/null
@@ -0,0 +1,848 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include <murphy-db/mqi.h>
+
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/manager-api.h>
+
+#include "resource.h"
+#include "resource-owner.h"
+#include "resource-set.h"
+#include "application-class.h"
+#include "zone.h"
+
+
+#define RESOURCE_MAX        (sizeof(mrp_resource_mask_t) * 8)
+#define ATTRIBUTE_MAX       (sizeof(mrp_attribute_mask_t) * 8)
+#define NAME_LENGTH          24
+
+#define RSETID_IDX           0
+#define AUTOREL_IDX          1
+#define STATE_IDX            2
+#define GRANT_IDX            3
+#define FIRST_ATTRIBUTE_IDX  4
+
+
+#define VALID_TYPE(t) ((t) == mqi_string  || \
+                       (t) == mqi_integer || \
+                       (t) == mqi_unsignd || \
+                       (t) == mqi_floating  )
+
+typedef struct {
+    uint32_t          rsetid;
+    int32_t           autorel;
+    int32_t           state;
+    int32_t           grant;
+    mrp_attr_value_t  attrs[MQI_COLUMN_MAX];
+} user_row_t;
+
+
+static uint32_t            resource_def_count;
+static mrp_resource_def_t *resource_def_table[RESOURCE_MAX];
+static MRP_LIST_HOOK(manager_list);
+static mqi_handle_t        resource_user_table[RESOURCE_MAX];
+
+static uint32_t add_resource_definition(const char *, bool, uint32_t,
+                                        mrp_resource_mgr_ftbl_t *, void *);
+
+#if 0
+static uint32_t find_resource_attribute_id(mrp_resource_t *, const char *);
+
+static mqi_data_type_t   get_resource_attribute_value_type(mrp_resource_t *,
+                                                           uint32_t);
+
+static mrp_attr_value_t *get_resource_attribute_default_value(mrp_resource_t*,
+                                                              uint32_t);
+#endif
+
+static int  resource_user_create_table(mrp_resource_def_t *);
+static void resource_user_insert(mrp_resource_t *, bool);
+static void resource_user_delete(mrp_resource_t *);
+
+static void set_attr_descriptors(mqi_column_desc_t *, mrp_resource_t *);
+
+
+
+uint32_t mrp_resource_definition_create(const char *name, bool shareable,
+                                        mrp_attr_def_t *attrdefs,
+                                        mrp_resource_mgr_ftbl_t *manager,
+                                        void *mgrdata)
+{
+    uint32_t nattr;
+    uint32_t id;
+    mrp_resource_def_t *def;
+
+    MRP_ASSERT(name, "invalid argument");
+
+    if (mrp_resource_definition_find_by_name(name)) {
+        mrp_log_error("attmpt to redefine resource '%s'", name);
+        return MRP_RESOURCE_ID_INVALID;
+    }
+
+    for (nattr = 0;  attrdefs && attrdefs[nattr].name;  nattr++)
+        ;
+
+    id = add_resource_definition(name, shareable, nattr, manager, mgrdata);
+
+    if (id != MRP_RESOURCE_ID_INVALID) {
+        def = mrp_resource_definition_find_by_id(id);
+
+        MRP_ASSERT(def, "got confused with data structures");
+
+        if (mrp_attribute_copy_definitions(attrdefs, def->attrdefs) < 0)
+            return MRP_RESOURCE_ID_INVALID;
+
+        resource_user_create_table(def);
+        mrp_resource_owner_create_database_table(def);
+    }
+
+    return id;
+}
+
+uint32_t mrp_resource_definition_count(void)
+{
+    return resource_def_count;
+}
+
+mrp_resource_def_t *mrp_resource_definition_find_by_name(const char *name)
+{
+    mrp_resource_def_t *def;
+    uint32_t            i;
+
+    for (i = 0;  i < resource_def_count;  i++) {
+        def = resource_def_table[i];
+
+        if (def && !strcasecmp(name, def->name))
+            return def;
+    }
+
+    return NULL;
+}
+
+uint32_t mrp_resource_definition_get_resource_id_by_name(const char *name)
+{
+    mrp_resource_def_t *def = mrp_resource_definition_find_by_name(name);
+
+    if (!def) {
+        return MRP_RESOURCE_ID_INVALID;
+    }
+
+    return def->id;
+}
+
+mrp_resource_def_t *mrp_resource_definition_find_by_id(uint32_t id)
+{
+    if (id < resource_def_count)
+        return resource_def_table[id];
+
+    return NULL;
+}
+
+mrp_resource_def_t *mrp_resource_definition_iterate_manager(void **cursor)
+{
+    mrp_list_hook_t *entry;
+
+    MRP_ASSERT(cursor, "invalid argument");
+
+    entry = (*cursor == NULL) ? manager_list.next : (mrp_list_hook_t *)*cursor;
+
+    if (entry == &manager_list)
+        return NULL;
+
+    *cursor = entry->next;
+
+    return mrp_list_entry(entry, mrp_resource_def_t, manager.list);
+}
+
+const char **mrp_resource_definition_get_all_names(uint32_t buflen,
+                                                   const char **buf)
+{
+    uint32_t i;
+
+    MRP_ASSERT(!buf || (buf && buflen > 1), "invlaid argument");
+
+    if (buf) {
+        if (buflen < resource_def_count + 1)
+            return NULL;
+    }
+    else {
+        buflen = resource_def_count + 1;
+        if (!(buf = mrp_allocz(sizeof(const char *) * buflen))) {
+            mrp_log_error("Memory alloc failure. Can't get resource names");
+            return NULL;
+        }
+    }
+
+    for (i = 0;  i < resource_def_count;  i++)
+        buf[i] = resource_def_table[i]->name;
+
+    buf[i] = NULL;
+
+    return buf;
+}
+
+mrp_attr_t *mrp_resource_definition_read_all_attributes(uint32_t resid,
+                                                        uint32_t buflen,
+                                                        mrp_attr_t *buf)
+{
+    mrp_resource_def_t *rdef   = mrp_resource_definition_find_by_id(resid);
+    mrp_attr_t         *retval;
+
+
+    if (!rdef)
+        retval = mrp_attribute_get_all_values(buflen, buf, 0, NULL, 0);
+    else {
+        retval = mrp_attribute_get_all_values(buflen, buf, rdef->nattr,
+                                              rdef->attrdefs, 0);
+    }
+
+    if (!retval) {
+        mrp_log_error("Memory alloc failure. Can't get all "
+                      "attributes of resource definition");
+    }
+
+    return retval;
+}
+
+
+
+mrp_resource_t *mrp_resource_create(const char *name,
+                                    uint32_t    rsetid,
+                                    bool        autorel,
+                                    bool        shared,
+                                    mrp_attr_t *attrs)
+{
+    mrp_resource_t *res = NULL;
+    mrp_resource_def_t *rdef;
+    size_t base_size;
+    size_t attr_size;
+    size_t total_size;
+    int sts;
+
+    MRP_ASSERT(name, "invalid argument");
+
+    if (!(rdef = mrp_resource_definition_find_by_name(name))) {
+        mrp_log_warning("Can't find resource definition '%s'. "
+                        "No resource created", name);
+    }
+    else {
+        base_size  = sizeof(mrp_resource_t);
+        attr_size  = sizeof(mrp_attr_value_t) * rdef->nattr;
+        total_size = base_size + attr_size;
+
+        if (!(res = mrp_allocz(total_size))) {
+            mrp_log_error("Memory alloc failure. Can't create "
+                          "resource '%s'", name);
+        }
+        else {
+            mrp_list_init(&res->list);
+
+            res->rsetid = rsetid;
+            res->def = rdef;
+            res->shared = rdef->shareable ?  shared : false;
+
+            sts = mrp_attribute_set_values(attrs, rdef->nattr,
+                                           rdef->attrdefs, res->attrs);
+            if (sts < 0) {
+                mrp_log_error("Memory alloc failure. No '%s' "
+                              "resource created", name);
+                return NULL;
+            }
+
+            resource_user_insert(res, autorel);
+        }
+    }
+
+    return res;
+}
+
+void mrp_resource_destroy(mrp_resource_t *res)
+{
+    mrp_resource_def_t *rdef;
+    mqi_data_type_t type;
+    uint32_t id;
+
+    if (res) {
+        rdef = res->def;
+
+        MRP_ASSERT(rdef, "invalid_argument");
+
+        resource_user_delete(res);
+
+        mrp_list_delete(&res->list);
+
+        for (id = 0;  id < rdef->nattr;  id++) {
+            type = rdef->attrdefs[id].type;
+
+            if (type == mqi_string)
+                mrp_free((void *)res->attrs[id].string);
+        }
+
+        mrp_free(res);
+    }
+}
+
+uint32_t mrp_resource_get_id(mrp_resource_t *res)
+{
+    mrp_resource_def_t *def;
+
+    if (res) {
+        def = res->def;
+        MRP_ASSERT(def, "confused with internal data structures");
+        return def->id;
+    }
+
+    return MRP_RESOURCE_ID_INVALID;
+}
+
+const char *mrp_resource_get_name(mrp_resource_t *res)
+{
+    mrp_resource_def_t *def;
+
+    if (res) {
+        def = res->def;
+
+        MRP_ASSERT(def && def->name, "confused with internal data structures");
+
+        return def->name;
+    }
+
+    return "<unknown resource>";
+}
+
+mrp_resource_mask_t mrp_resource_get_mask(mrp_resource_t *res)
+{
+    mrp_resource_def_t *def;
+    mrp_resource_mask_t mask = 0;
+
+    if (res) {
+        def = res->def;
+
+        MRP_ASSERT(def, "confused with internal data structures");
+
+        mask = (mrp_resource_mask_t)1 << def->id;
+    }
+
+    return mask;
+}
+
+bool mrp_resource_is_shared(mrp_resource_t *res)
+{
+    if (res)
+        return res->shared;
+
+    return false;
+}
+
+mrp_attr_t *mrp_resource_read_attribute(mrp_resource_t *res,
+                                        uint32_t        idx,
+                                        mrp_attr_t     *value)
+{
+    mrp_attr_t *retval;
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "confused with data structures");
+
+    retval = mrp_attribute_get_value(idx, value, rdef->nattr,
+                                     rdef->attrdefs, res->attrs);
+
+    if (!retval) {
+        mrp_log_error("Memory alloc failure. Can't get "
+                      "resource '%s' attribute %u", rdef->name, idx);
+    }
+
+    return retval;
+}
+
+
+mrp_attr_t *mrp_resource_read_all_attributes(mrp_resource_t *res,
+                                             uint32_t nvalue,
+                                             mrp_attr_t *values)
+{
+    mrp_attr_t *retval;
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "confused with data structures");
+
+    retval = mrp_attribute_get_all_values(nvalue, values, rdef->nattr,
+                                          rdef->attrdefs, res->attrs);
+
+    if (!retval) {
+        mrp_log_error("Memory alloc failure. Can't get all "
+                      "attributes of resource '%s'", rdef->name);
+    }
+
+    return retval;
+}
+
+int mrp_resource_write_attributes(mrp_resource_t *res, mrp_attr_t *values)
+{
+    int sts;
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(res && values, "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "confused with data structures");
+
+    sts = mrp_attribute_set_values(values, rdef->nattr,
+                                   rdef->attrdefs, res->attrs);
+
+    if (sts < 0) {
+        mrp_log_error("Memory alloc failure. Can't set attributes "
+                      "of resource '%s'", rdef->name);
+    }
+
+    return sts;
+}
+
+const char *mrp_resource_get_application_class(mrp_resource_t *res)
+{
+    mrp_resource_set_t *rset;
+    mrp_application_class_t *class;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    if (!(rset = mrp_resource_set_find_by_id(res->rsetid)))
+        return NULL;
+
+    if (!(class = rset->class.ptr))
+        return NULL;
+
+    return class->name;
+}
+
+void mrp_resource_notify(mrp_resource_t *res,
+                         mrp_resource_set_t *rset,
+                         mrp_resource_event_t event)
+{
+    mrp_resource_def_t *rdef;
+    mrp_resource_mgr_ftbl_t *ftbl;
+    mrp_manager_notify_func_t notify;
+    mrp_application_class_t *class;
+    mrp_zone_t *zone;
+
+    MRP_ASSERT(res && rset, "inavlid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "confused with data structures");
+
+    if ((ftbl = rdef->manager.ftbl) &&
+        (notify = ftbl->notify) &&
+        (zone = mrp_zone_find_by_id(rset->zone)) &&
+        (class = rset->class.ptr))
+    {
+        notify(event, zone, class, res, rdef->manager.userdata);
+    }
+}
+
+int mrp_resource_print(mrp_resource_t *res, uint32_t mandatory,
+                       size_t indent, char *buf, int len)
+{
+#define PRINT(fmt, args...)  if (p<e) { p += snprintf(p, e-p, fmt , ##args); }
+
+    mrp_resource_def_t *rdef;
+    char gap[] = "                         ";
+    char *p, *e;
+    uint32_t m;
+
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(res && indent < sizeof(gap)-1 && buf,
+               "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "Confused with data structures");
+
+    gap[indent] = '\0';
+
+    e = (p = buf) + len;
+    m = ((mrp_resource_mask_t)1 << rdef->id);
+
+    PRINT("%s%s: 0x%02x %s %s", gap, rdef->name, m,
+          (m & mandatory) ? "mandatory":"optional ",
+          res->shared ? "shared  ":"exlusive");
+
+    p += mrp_resource_attribute_print(res, p, e-p);
+
+    PRINT("\n");
+
+
+    return p - buf;
+
+#undef PRINT
+}
+
+int mrp_resource_attribute_print(mrp_resource_t *res, char *buf, int len)
+{
+    mrp_resource_def_t *rdef;
+
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(res && buf, "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "Confused with data structures");
+
+    return mrp_attribute_print(rdef->nattr,rdef->attrdefs,res->attrs, buf,len);
+}
+
+
+static uint32_t add_resource_definition(const char *name,
+                                        bool        shareable,
+                                        uint32_t    nattr,
+                                        mrp_resource_mgr_ftbl_t *mgrftbl,
+                                        void       *mgrdata)
+{
+    mrp_resource_def_t *def;
+    const char         *dup_name;
+    size_t              size;
+    uint32_t            id;
+
+    MRP_ASSERT(name && nattr < ATTRIBUTE_MAX, "invalid argument");
+
+    if (resource_def_count >= RESOURCE_MAX) {
+        mrp_log_error("Resource table overflow. Can't add resource '%s'",name);
+        return MRP_RESOURCE_ID_INVALID;
+    }
+
+    size = sizeof(mrp_resource_def_t) + sizeof(mrp_attr_def_t) * nattr;
+
+    if (!(def = mrp_allocz(size)) || !(dup_name = mrp_strdup(name))) {
+        mrp_log_error("Memory alloc failure. Can't add resource '%s'", name);
+        return MRP_RESOURCE_ID_INVALID;
+    }
+
+    id = resource_def_count++;
+
+    def->id        = id;
+    def->name      = dup_name;
+    def->shareable = shareable;
+    def->nattr     = nattr;
+
+    if (mgrftbl) {
+        def->manager.ftbl = mrp_alloc(sizeof(mrp_resource_mgr_ftbl_t));
+        def->manager.userdata = mgrdata;
+
+        if (def->manager.ftbl)
+            memcpy(def->manager.ftbl, mgrftbl,sizeof(mrp_resource_mgr_ftbl_t));
+        else {
+            mrp_log_error("Memory alloc failure. No manager for resource '%s'",
+                          name);
+        }
+    }
+
+    resource_def_table[id] = def;
+
+    if (!mgrftbl)
+        mrp_list_init(&def->manager.list);
+    else
+        mrp_list_append(&manager_list, &def->manager.list);
+
+    return id;
+}
+
+
+#if 0
+static uint32_t find_resource_attribute_id(mrp_resource_t *res,
+                                           const char *attrnam)
+{
+    mrp_resource_def_t *rdef;
+    mrp_attr_def_t *adef;
+    uint32_t id;
+
+    if (res && (rdef = res->def) && attrnam) {
+        for (id = 0;  id < rdef->nattr;  id++) {
+            adef = rdef->attrdefs + id;
+
+            if (!strcasecmp(attrnam, adef->name))
+                return id;
+        }
+    }
+
+    return MRP_RESOURCE_ID_INVALID;
+}
+
+static mqi_data_type_t
+get_resource_attribute_value_type(mrp_resource_t *res, uint32_t id)
+{
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "confused with data structures");
+    MRP_ASSERT(id < rdef->nattr, "invalid argument");
+
+    return rdef->attrdefs[id].type;
+}
+
+static mrp_attr_value_t *
+get_resource_attribute_default_value(mrp_resource_t *res, uint32_t id)
+{
+    mrp_resource_def_t *rdef;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    rdef = res->def;
+
+    MRP_ASSERT(rdef, "confused with data structures");
+    MRP_ASSERT(id < rdef->nattr, "invalid argument");
+
+    return &rdef->attrdefs[id].value;
+}
+#endif
+
+
+static int resource_user_create_table(mrp_resource_def_t *rdef)
+{
+    MQI_COLUMN_DEFINITION_LIST(base_coldefs,
+        MQI_COLUMN_DEFINITION( "rsetid" , MQI_UNSIGNED ),
+        MQI_COLUMN_DEFINITION( "autorel", MQI_INTEGER  ),
+        MQI_COLUMN_DEFINITION( "state"  , MQI_INTEGER  ),
+        MQI_COLUMN_DEFINITION( "grant"  , MQI_INTEGER  )
+    );
+
+    MQI_INDEX_DEFINITION(indexdef,
+        MQI_INDEX_COLUMN( "rsetid" )
+    );
+
+    static bool initialized = false;
+
+    char name[256];
+    mqi_column_def_t  coldefs[MQI_COLUMN_MAX + 1];
+    mqi_column_def_t *col;
+    mrp_attr_def_t *atd;
+    mqi_handle_t table;
+    char c, *p;
+    size_t i,j;
+
+    if (!initialized) {
+        mqi_open();
+        for (i = 0;  i < RESOURCE_MAX;  i++)
+            resource_user_table[i] = MQI_HANDLE_INVALID;
+        initialized = true;
+    }
+
+    MRP_ASSERT(sizeof(base_coldefs) < sizeof(coldefs),"too many base columns");
+    MRP_ASSERT(rdef, "invalid argument");
+    MRP_ASSERT(rdef->id < RESOURCE_MAX, "confused with data structures");
+    MRP_ASSERT(resource_user_table[rdef->id] == MQI_HANDLE_INVALID,
+               "resource user table already exist");
+
+    snprintf(name, sizeof(name), "%s_users", rdef->name);
+    for (p = name; (c = *p);  p++) {
+        if (!isascii(c) || (!isalnum(c) && c != '_'))
+            *p = '_';
+    }
+
+    j = MQI_DIMENSION(base_coldefs) - 1;
+    memcpy(coldefs, base_coldefs, j * sizeof(mqi_column_def_t));
+
+    for (i = 0;  i < rdef->nattr && j < MQI_COLUMN_MAX;  i++, j++) {
+        col = coldefs + j;
+        atd = rdef->attrdefs + i;
+
+        col->name   = atd->name;
+        col->type   = atd->type;
+        col->length = (col->type == mqi_string) ? NAME_LENGTH : 0;
+        col->flags  = 0;
+    }
+
+    memset(coldefs + j, 0, sizeof(mqi_column_def_t));
+
+    table = MQI_CREATE_TABLE(name, MQI_TEMPORARY, coldefs, indexdef);
+
+    if (table == MQI_HANDLE_INVALID) {
+        mrp_log_error("Can't create table '%s': %s", name, strerror(errno));
+        return -1;
+    }
+
+    resource_user_table[rdef->id] = table;
+
+    return 0;
+}
+
+static void resource_user_insert(mrp_resource_t *res, bool autorel)
+{
+    mrp_resource_def_t *rdef = res->def;
+    uint32_t i;
+    int n;
+    user_row_t row;
+    user_row_t *rows[2];
+    mqi_column_desc_t cdsc[FIRST_ATTRIBUTE_IDX + MQI_COLUMN_MAX + 1];
+
+    MRP_ASSERT(FIRST_ATTRIBUTE_IDX + rdef->nattr <= MQI_COLUMN_MAX,
+               "too many attributes for a table");
+
+    row.rsetid   = res->rsetid;
+    row.autorel  = autorel;
+    row.grant    = 0;
+    row.state    = mrp_resource_no_request;
+    memcpy(row.attrs, res->attrs, rdef->nattr * sizeof(mrp_attr_value_t));
+
+    i = 0;
+    cdsc[i].cindex = RSETID_IDX;
+    cdsc[i].offset = MQI_OFFSET(user_row_t, rsetid);
+
+    i++;
+    cdsc[i].cindex = AUTOREL_IDX;
+    cdsc[i].offset = MQI_OFFSET(user_row_t, autorel);
+
+    i++;
+    cdsc[i].cindex = STATE_IDX;
+    cdsc[i].offset = MQI_OFFSET(user_row_t, state);
+
+    i++;
+    cdsc[i].cindex = GRANT_IDX;
+    cdsc[i].offset = MQI_OFFSET(user_row_t, grant);
+
+    set_attr_descriptors(cdsc + (i+1), res);
+
+    rows[0] = &row;
+    rows[1] = NULL;
+
+    if ((n = MQI_INSERT_INTO(resource_user_table[rdef->id], cdsc, rows)) != 1)
+        mrp_log_error("can't insert row into resource user table");
+}
+
+static void resource_user_delete(mrp_resource_t *res)
+{
+    static uint32_t rsetid;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(RSETID_IDX), MQI_UNSIGNED_VAR(rsetid) )
+    );
+
+    mrp_resource_def_t *rdef;
+    int n;
+
+    MRP_ASSERT(res, "invalid argument");
+
+    rdef = res->def;
+    rsetid = res->rsetid;
+
+    if ((n = MQI_DELETE(resource_user_table[rdef->id], where)) != 1)
+        mrp_log_error("Could not delete resource user");
+}
+
+void mrp_resource_user_update(mrp_resource_t *res, int state, bool grant)
+{
+    static uint32_t rsetid;
+
+    MQI_WHERE_CLAUSE(where,
+        MQI_EQUAL( MQI_COLUMN(RSETID_IDX), MQI_UNSIGNED_VAR(rsetid) )
+    );
+
+    mrp_resource_def_t *rdef = res->def;
+    uint32_t i;
+    int n;
+    user_row_t row;
+    mqi_column_desc_t cdsc[FIRST_ATTRIBUTE_IDX + MQI_COLUMN_MAX + 1];
+
+    rsetid = res->rsetid;
+
+    MRP_ASSERT(1 + rdef->nattr <= MQI_COLUMN_MAX,
+               "too many attributes for a table");
+
+    row.state = state;
+    row.grant = grant;
+    memcpy(row.attrs, res->attrs, rdef->nattr * sizeof(mrp_attr_value_t));
+
+    i = 0;
+    cdsc[i].cindex = STATE_IDX;
+    cdsc[i].offset = MQI_OFFSET(user_row_t, state);
+
+    i++;
+    cdsc[i].cindex = GRANT_IDX;
+    cdsc[i].offset = MQI_OFFSET(user_row_t, grant);
+
+    set_attr_descriptors(cdsc + (i+1), res);
+
+    if ((n = MQI_UPDATE(resource_user_table[rdef->id], cdsc,&row, where)) != 1)
+        mrp_log_error("can't update row in resource user table");
+}
+
+static void set_attr_descriptors(mqi_column_desc_t *cdsc, mrp_resource_t *res)
+{
+    mrp_resource_def_t *rdef = res->def;
+    uint32_t i,j;
+    int o;
+
+    for (i = j = 0;  j < rdef->nattr;  j++) {
+        switch (rdef->attrdefs[j].type) {
+        case mqi_string:   o = MQI_OFFSET(user_row_t,attrs[j].string);  break;
+        case mqi_integer:  o = MQI_OFFSET(user_row_t,attrs[j].integer); break;
+        case mqi_unsignd:  o = MQI_OFFSET(user_row_t,attrs[j].unsignd); break;
+        case mqi_floating: o = MQI_OFFSET(user_row_t,attrs[j].floating);break;
+        default:           /* skip this */                            continue;
+        }
+
+        cdsc[i].cindex = FIRST_ATTRIBUTE_IDX + j;
+        cdsc[i].offset = o;
+        i++;
+    }
+
+    cdsc[i].cindex = -1;
+    cdsc[i].offset =  1;
+}
+
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/resource.h b/src/resource/resource.h
new file mode 100644 (file)
index 0000000..3c01fd9
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_RESOURCE_H__
+#define __MURPHY_RESOURCE_H__
+
+#include <murphy/common/list.h>
+
+#include "attribute.h"
+
+
+struct mrp_resource_def_s {
+    uint32_t            id;
+    const char         *name;
+    bool                shareable;
+    struct {
+        mrp_list_hook_t list;
+        mrp_resource_mgr_ftbl_t *ftbl;
+        void *userdata;
+    }                   manager;
+    uint32_t            nattr;
+    mrp_attr_def_t      attrdefs[0];
+};
+
+struct mrp_resource_s {
+    mrp_list_hook_t     list;
+    uint32_t            rsetid;
+    mrp_resource_def_t *def;
+    bool                shared;
+    mrp_attr_value_t    attrs[0];
+};
+
+
+
+uint32_t            mrp_resource_definition_count(void);
+mrp_resource_def_t *mrp_resource_definition_find_by_name(const char *);
+mrp_resource_def_t *mrp_resource_definition_find_by_id(uint32_t);
+mrp_resource_def_t *mrp_resource_definition_iterate_manager(void **);
+
+
+mrp_resource_t     *mrp_resource_create(const char *, uint32_t, bool,
+                                        bool, mrp_attr_t *);
+void                mrp_resource_destroy(mrp_resource_t *);
+
+void                mrp_resource_notify(mrp_resource_t *, mrp_resource_set_t *,
+                                        mrp_resource_event_t);
+
+int                 mrp_resource_print(mrp_resource_t*, uint32_t,
+                                       size_t, char *, int);
+int                 mrp_resource_attribute_print(mrp_resource_t *, char *,int);
+
+void                mrp_resource_user_update(mrp_resource_t *, int, bool);
+
+#endif  /* __MURPHY_RESOURCE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/zone.c b/src/resource/zone.c
new file mode 100644 (file)
index 0000000..a05cb87
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <murphy/common/mm.h>
+#include <murphy/common/log.h>
+
+#include <murphy/resource/manager-api.h>
+#include <murphy/resource/client-api.h>
+#include <murphy/resource/config-api.h>
+
+#include <murphy-db/mqi.h>
+
+#include "zone.h"
+
+
+#define ATTRIBUTE_MAX  32
+#define NAME_LENGTH    24
+
+#define VALID_TYPE(t) ((t) == mqi_string  || \
+                       (t) == mqi_integer || \
+                       (t) == mqi_unsignd || \
+                       (t) == mqi_floating  )
+
+
+#define ZONE_ID_IDX          0
+#define ZONE_NAME_IDX        1
+#define FIRST_ATTRIBUTE_IDX  2
+
+typedef struct {
+    uint32_t          zone_id;
+    const char       *zone_name;
+    mrp_attr_value_t  attrs[MQI_COLUMN_MAX];
+} zone_row_t;
+
+static mrp_zone_def_t *zone_def;
+static uint32_t        zone_count;
+static mrp_zone_t     *zone_table[MRP_ZONE_MAX];
+static mqi_handle_t    db_table = MQI_HANDLE_INVALID;
+
+static mqi_handle_t create_zone_table(mrp_zone_def_t *);
+static void insert_into_zone_table(mrp_zone_t *);
+static void set_attr_descriptors(mqi_column_desc_t *);
+
+
+int mrp_zone_definition_create(mrp_attr_def_t *attrdefs)
+{
+    uint32_t nattr;
+    size_t size;
+    mrp_zone_def_t *def;
+
+    for (nattr = 0;  attrdefs && attrdefs[nattr].name;  nattr++)
+        ;
+
+    size = sizeof(mrp_zone_def_t) + sizeof(mrp_attr_def_t) * nattr;
+
+    if (!(def = mrp_allocz(size))) {
+        mrp_log_error("Memory alloc failure. Can't create zone definition");
+        return -1;
+    }
+
+    def->nattr = nattr;
+    zone_def = def;
+
+    if (mrp_attribute_copy_definitions(attrdefs, def->attrdefs) < 0)
+        return -1;
+
+    db_table = create_zone_table(def);
+
+    return 0;
+}
+
+uint32_t mrp_zone_count(void)
+{
+    return zone_count;
+}
+
+uint32_t mrp_zone_create(const char *name, mrp_attr_t *attrs)
+{
+    size_t size;
+    mrp_zone_t *zone;
+    const char *dup_name;
+    int sts;
+
+    MRP_ASSERT(name, "invalid argument");
+
+    if (!zone_def) {
+        mrp_log_error("Zone definition must preceed zone creation. "
+                      "can't create zone '%s'", name);
+        return MRP_ZONE_ID_INVALID;
+    }
+
+    if (zone_count >= MRP_ZONE_MAX) {
+        mrp_log_error("Zone table overflow. Can't create zone '%s'", name);
+        return MRP_ZONE_ID_INVALID;
+    }
+
+    size = sizeof(mrp_zone_t) + sizeof(mrp_attr_def_t) * zone_def->nattr;
+
+    if (!(zone = mrp_allocz(size)) || !(dup_name = mrp_strdup(name))) {
+        mrp_log_error("Memory alloc failure. Can't create zone '%s'", name);
+        return MRP_ZONE_ID_INVALID;
+    }
+
+
+    zone->id   = zone_count++;
+    zone->name = dup_name;
+
+    sts = mrp_attribute_set_values(attrs, zone_def->nattr,
+                                   zone_def->attrdefs, zone->attrs);
+    if (sts < 0) {
+        mrp_log_error("Memory alloc failure. Can't create zone '%s'", name);
+        return MRP_ZONE_ID_INVALID;
+    }
+
+    insert_into_zone_table(zone);
+
+    zone_table[zone->id] = zone;
+
+    return zone->id;
+}
+
+mrp_zone_t *mrp_zone_find_by_id(uint32_t id)
+{
+    if (id < zone_count)
+        return zone_table[id];
+
+    return NULL;
+}
+
+mrp_zone_t *mrp_zone_find_by_name(const char *name)
+{
+    mrp_zone_t *zone;
+    uint32_t id;
+
+    for (id = 0;  id < zone_count;  id++) {
+        zone = zone_table[id];
+
+        if (!strcasecmp(name, zone->name))
+            return zone;
+    }
+
+    return NULL;
+}
+
+uint32_t mrp_zone_get_id(mrp_zone_t *zone)
+{
+    if (!zone)
+        return MRP_ZONE_ID_INVALID;
+
+    return zone->id;
+}
+
+const char *mrp_zone_get_name(mrp_zone_t *zone)
+{
+    if (!zone || !zone->name)
+        return "<unknown zone>";
+
+    return zone->name;
+}
+
+const char **mrp_zone_get_all_names(uint32_t buflen, const char **buf)
+{
+    uint32_t i;
+
+    MRP_ASSERT(!buf || (buf && buflen > 1), "invlaid argument");
+
+    if (buf) {
+        if (buflen < zone_count + 1)
+            return NULL;
+    }
+    else {
+        buflen = zone_count + 1;
+        if (!(buf = mrp_allocz(sizeof(const char *) * buflen))) {
+            mrp_log_error("Memory alloc failure. Can't get all zone names");
+            return NULL;
+        }
+    }
+
+    for (i = 0;  i < zone_count;  i++)
+        buf[i] = zone_table[i]->name;
+
+    buf[i] = NULL;
+
+    return buf;
+}
+
+
+mrp_attr_t *mrp_zone_read_attribute(mrp_zone_t *zone,
+                                    uint32_t    idx,
+                                    mrp_attr_t *value)
+{
+    mrp_attr_t *retval;
+
+    MRP_ASSERT(zone, "invalid argument");
+    MRP_ASSERT(zone_def, "no zone definition");
+
+    retval = mrp_attribute_get_value(idx, value, zone_def->nattr,
+                                     zone_def->attrdefs, zone->attrs);
+
+    if (!retval) {
+        mrp_log_error("Memory alloc failure. Can't get "
+                      "zone '%s' attribute %u", zone->name, idx);
+    }
+
+    return retval;
+}
+
+mrp_attr_t *mrp_zone_read_all_attributes(mrp_zone_t *zone,
+                                         uint32_t nvalue,
+                                         mrp_attr_t *values)
+{
+    mrp_attr_t *retval;
+
+    MRP_ASSERT(zone, "invalid argument");
+
+    retval = mrp_attribute_get_all_values(nvalue, values, zone_def->nattr,
+                                          zone_def->attrdefs, zone->attrs);
+
+    if (!retval) {
+        mrp_log_error("Memory alloc failure. Can't get all"
+                      "attributes of zone '%s'", zone->name);
+    }
+
+    return retval;
+}
+
+int mrp_zone_attribute_print(mrp_zone_t *zone, char *buf, int len)
+{
+    if (len <= 0)
+        return 0;
+
+    MRP_ASSERT(zone && buf, "invalid argument");
+
+    return mrp_attribute_print(zone_def->nattr, zone_def->attrdefs,
+                               zone->attrs, buf,len);
+}
+
+
+static mqi_handle_t create_zone_table(mrp_zone_def_t *zdef)
+{
+    MQI_COLUMN_DEFINITION_LIST(base_coldefs,
+        MQI_COLUMN_DEFINITION( "zone_id"       , MQI_UNSIGNED             ),
+        MQI_COLUMN_DEFINITION( "zone_name"     , MQI_VARCHAR(NAME_LENGTH) )
+    );
+
+    MQI_INDEX_DEFINITION(indexdef,
+        MQI_INDEX_COLUMN("zone_id")
+    );
+
+    char *name;
+    mqi_column_def_t coldefs[MQI_COLUMN_MAX + 1];
+    mqi_column_def_t *col;
+    mrp_attr_def_t *atd;
+    mqi_handle_t table;
+    size_t i,j;
+
+    MRP_ASSERT(zdef, "invalid argument");
+    MRP_ASSERT(zdef->nattr < MQI_COLUMN_MAX, "too many zone attributes");
+    MRP_ASSERT(db_table == MQI_HANDLE_INVALID,
+               "multiple definition of zone data table");
+
+    mqi_open();
+
+    name = "zones";
+
+    j = MQI_DIMENSION(base_coldefs) - 1;
+    memcpy(coldefs, base_coldefs, j * sizeof(mqi_column_def_t));
+
+    for (i = 0;  i < zdef->nattr && j < MQI_COLUMN_MAX;  i++, j++) {
+        col = coldefs + j;
+        atd = zdef->attrdefs + i;
+
+        col->name   = atd->name;
+        col->type   = atd->type;
+        col->length = (col->type == mqi_string) ? NAME_LENGTH : 0;
+        col->flags  = 0;
+    }
+
+    memset(coldefs + j, 0, sizeof(mqi_column_def_t));
+
+    table = MQI_CREATE_TABLE(name, MQI_TEMPORARY, coldefs, indexdef);
+
+    if (table == MQI_HANDLE_INVALID)
+        mrp_log_error("Can't create table '%s': %s", name, strerror(errno));
+
+    return table;
+}
+
+static void insert_into_zone_table(mrp_zone_t *zone)
+{
+    uint32_t i;
+    int n;
+    zone_row_t row;
+    zone_row_t *rows[2];
+    mqi_column_desc_t cdsc[FIRST_ATTRIBUTE_IDX + MQI_COLUMN_MAX + 1];
+
+    MRP_ASSERT(zone_def, "no zone definition");
+    MRP_ASSERT(db_table != MQI_HANDLE_INVALID, "no zone table");
+    MRP_ASSERT(FIRST_ATTRIBUTE_IDX + zone_def->nattr <= MQI_COLUMN_MAX,
+               "too many attributes for a table");
+
+    row.zone_id    = zone->id;
+    row.zone_name  = zone->name;
+    memcpy(row.attrs, zone->attrs, zone_def->nattr * sizeof(mrp_attr_value_t));
+
+    i = 0;
+    cdsc[i].cindex = ZONE_ID_IDX;
+    cdsc[i].offset = MQI_OFFSET(zone_row_t, zone_id);
+
+    i++;
+    cdsc[i].cindex = ZONE_NAME_IDX;
+    cdsc[i].offset = MQI_OFFSET(zone_row_t, zone_name);
+
+    set_attr_descriptors(cdsc + (i+1));
+
+    rows[0] = &row;
+    rows[1] = NULL;
+
+    if ((n = MQI_INSERT_INTO(db_table, cdsc, rows)) != 1)
+        mrp_log_error("can't insert row into zone table");
+}
+
+static void set_attr_descriptors(mqi_column_desc_t *cdsc)
+{
+    uint32_t i,j;
+    int o;
+
+    for (i = j = 0;  j < zone_def->nattr;  j++) {
+        switch (zone_def->attrdefs[j].type) {
+        case mqi_string:   o = MQI_OFFSET(zone_row_t,attrs[j].string);   break;
+        case mqi_integer:  o = MQI_OFFSET(zone_row_t,attrs[j].integer);  break;
+        case mqi_unsignd:  o = MQI_OFFSET(zone_row_t,attrs[j].unsignd);  break;
+        case mqi_floating: o = MQI_OFFSET(zone_row_t,attrs[j].floating); break;
+        default:           /* skip this */                            continue;
+        }
+
+        cdsc[i].cindex = FIRST_ATTRIBUTE_IDX + j;
+        cdsc[i].offset = o;
+        i++;
+    }
+
+    cdsc[i].cindex = -1;
+    cdsc[i].offset =  1;
+}
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/resource/zone.h b/src/resource/zone.h
new file mode 100644 (file)
index 0000000..bde9cf5
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *  * Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *  * Neither the name of Intel Corporation nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MURPHY_ZONE_H__
+#define __MURPHY_ZONE_H__
+
+#include "attribute.h"
+
+struct mrp_zone_def_s {
+    uint32_t         nattr;
+    mrp_attr_def_t   attrdefs[0];
+};
+
+struct mrp_zone_s {
+    uint32_t          id;
+    const char       *name;
+    mrp_attr_value_t  attrs[0];
+};
+
+
+uint32_t mrp_zone_count(void);
+mrp_zone_t *mrp_zone_find_by_id(uint32_t);
+mrp_zone_t *mrp_zone_find_by_name(const char *);
+
+int mrp_zone_attribute_print(mrp_zone_t *, char *, int);
+
+
+#endif  /* __MURPHY_ZONE_H__ */
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/utils/Makefile.am b/utils/Makefile.am
new file mode 100644 (file)
index 0000000..1ab1a2b
--- /dev/null
@@ -0,0 +1,10 @@
+AM_CFLAGS       = $(WARNING_CFLAGS) -I$(top_builddir)
+noinst_PROGRAMS = collect-symbols
+
+collect_symbols_SOURCES = collect-symbols.c
+collect_symbols_CFLAGS  = $(AM_CFLAGS)
+collect_symbols_LDADD   =
+
+collect-symbols$(EXEEXT): $(collect_symbols_SOURCES)
+       $(CC_FOR_BUILD) $(collect_symbols_CFLAGS) -o $@ $< \
+               $(collect_symbols_LDADD)
diff --git a/utils/collect-symbols.c b/utils/collect-symbols.c
new file mode 100644 (file)
index 0000000..9b7b782
--- /dev/null
@@ -0,0 +1,997 @@
+/*
+ * Copyright (c) 2012, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *   * Redistributions of source code must retain the above copyright notice,
+ *     this list of conditions and the following disclaimer.
+ *   * Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *   * Neither the name of Intel Corporation nor the names of its contributors
+ *     may be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#define _GNU_SOURCE
+#include <getopt.h>
+#include <unistd.h>
+#include <regex.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#define RD 0
+#define WR 1
+
+typedef enum {
+    TOKEN_ERROR = -1,
+    TOKEN_LINEMARKER,                    /* a preprocessor line marker */
+    TOKEN_BLOCK,                         /* a block enclosed in {}/()/[] */
+    TOKEN_WORD,                          /* a word */
+    TOKEN_DQUOTED,                       /* a double-quoted sequence */
+    TOKEN_SQUOTED,                       /* a single-quoted sequence */
+    TOKEN_ASSIGN,                        /* '=' */
+    TOKEN_SEMICOLON,                     /* ';' */
+    TOKEN_COLON,                         /* ',' */
+    TOKEN_OTHER,                         /* any other token */
+} token_type_t;
+
+
+typedef struct {
+    token_type_t  type;                  /* token type */
+    char         *value;                 /* token value */
+} token_t;
+
+
+#define READBUF_SIZE ( 8 * 1024)
+#define RINGBUF_SIZE (16 * 1024)
+#define MAX_TOKEN    (512)
+#define MAX_TOKENS   (64)
+
+typedef struct {
+    int  fd;                             /* file descriptor to read */
+    char buf[READBUF_SIZE];              /* data buffer */
+    int  len;                            /* amount of data in buffer */
+    int  rd;                             /* data buffer read offset */
+    int  nxt;                            /* pushed back data if non-zero */
+} input_t;
+
+typedef struct {
+    char buf[RINGBUF_SIZE];              /* data buffer */
+    int  wr;                             /* write offset */
+} ringbuf_t;
+
+typedef struct {
+    char    *preproc;                    /* preprocessor to use */
+    char    *pattern;                    /* symbol pattern */
+    char   **files;                      /* files to parse for symbols */
+    int      nfile;                      /* number of files */
+    char    *cflags;                     /* compiler flags */
+    char    *output;                     /* output path */
+    int      gnuld;                      /* generate GNU ld script */
+    int      verbose;                    /* verbosity */
+} config_t;
+
+typedef struct {
+    char **syms;
+    int    nsym;
+} symtab_t;
+
+
+static int verbosity = 1;
+
+
+static void fatal_error(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+
+    exit(1);
+}
+
+
+static void verbose_message(int level, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (verbosity >= level) {
+        va_start(ap, fmt);
+        vfprintf(stderr, fmt, ap);
+        va_end(ap);
+    }
+}
+
+
+static char *unshave(char *cmd)
+{
+#define SHAVE "shave cc "
+    char *shave;
+
+    shave = strstr(cmd, SHAVE);
+
+    if (shave == NULL)
+        return cmd;
+    else
+        return shave + sizeof(SHAVE) - 1;
+#undef SHAVE
+}
+
+
+static void print_usage(const char *argv0, int exit_code, const char *fmt, ...)
+{
+    va_list ap;
+
+    if (fmt && *fmt) {
+        va_start(ap, fmt);
+        vprintf(fmt, ap);
+        va_end(ap);
+    }
+
+    printf("usage: %s [options]\n\n"
+           "The possible options are:\n"
+           "  -P  --preproc <preprocessor> preprocessor to use [gcc]\n"
+           "  -c, --compiler-flags <flags> flags to pass to compiler\n"
+           "  -p, --pattern <pattern>      symbol regexp pattern\n"
+           "  -o, --output <path>          write output to the given file\n"
+           "  -g, --gnu-ld <script>        generate GNU ld linker script\n"
+           "  -v, --verbose                increase verbosity\n"
+           "  -q, --quiet                  decrease verbosity\n"
+           "  -h, --help                   show this help on usage\n",
+           argv0);
+
+    if (exit_code < 0)
+        return;
+    else
+        exit(exit_code);
+}
+
+
+static void set_defaults(config_t *c)
+{
+    memset(c, 0, sizeof(*c));
+    c->preproc = "gcc";
+    c->pattern = "^mrp_|^_mrp";
+}
+
+
+static void parse_cmdline(config_t *cfg, int argc, char **argv)
+{
+#   define OPTIONS "P:c:p:o:gvqh"
+    struct option options[] = {
+        { "preprocessor"  , required_argument, NULL, 'P' },
+        { "compiler-flags", required_argument, NULL, 'c' },
+        { "pattern"       , required_argument, NULL, 'p' },
+        { "output"        , required_argument, NULL, 'o' },
+        { "gnu-ld"        , no_argument      , NULL, 'g' },
+        { "verbose"       , no_argument      , NULL, 'v' },
+        { "quiet"         , no_argument      , NULL, 'q' },
+        { "help"          , no_argument      , NULL, 'h' },
+        { NULL, 0, NULL, 0 }
+    };
+
+    int opt;
+
+    set_defaults(cfg);
+
+    while ((opt = getopt_long(argc, argv, OPTIONS, options, NULL)) != -1) {
+        switch (opt) {
+        case 'P':
+            cfg->preproc = unshave(optarg);
+            break;
+
+        case 'c':
+            cfg->cflags = optarg;
+            break;
+
+        case 'p':
+            cfg->pattern = optarg;
+            break;
+
+        case 'o':
+            cfg->output = optarg;
+            break;
+
+        case 'g':
+            cfg->gnuld = 1;
+            break;
+
+        case 'v':
+            verbosity++;
+            break;
+
+        case 'q':
+            verbosity--;
+            break;
+
+        case 'h':
+            print_usage(argv[0], -1, "");
+            exit(0);
+            break;
+
+        default:
+            print_usage(argv[0], EINVAL, "invalid option '%s'\n",
+                        argv[optind]);
+        }
+    }
+
+    cfg->files = argv + optind;
+    cfg->nfile = argc - optind;
+}
+
+
+static int preprocess_file(const char *preproc, const char *file,
+                           const char *cflags, pid_t *pid)
+{
+    char cmd[4096], *argv[32];
+    int  fd[2], argc, i;
+
+    /*
+     * preprocess the given file
+     *
+     * Fork off a process for preprocessing the given file with the
+     * configured compiler flags. Return the reading end of the pipe
+     * the preprocessor is writing to.
+     */
+
+    if (pipe(fd) != 0)
+        fatal_error("failed to create pipe (%d: %s).", errno, strerror(errno));
+
+    *pid = fork();
+
+    switch (*pid) {
+    case -1:
+        fatal_error("failed to for preprocessor (%d: %s).",
+                    errno, strerror(errno));
+        break;
+
+    case 0: /* child: exec preprocessor */
+        close(fd[RD]);
+
+        /*
+         * Notes:
+         *     Currently we execute the preprocessor by starting a shell
+         *     and feeding it our constructed preprocessor command using
+         *     the '-c' option. If we need to pass options to the pre-
+         *     processor we need to protect those from expansion by the
+         *     intermediate shell. This causes some level of pain if we
+         *     also have a script that gets its arguments somewhere else,
+         *     eg. from a Makefile, and passes those forward to us. This
+         *     is exactly how we are executed during Murphy builds.
+         *
+         *     To reduce the pain perhaps we should leave the shell out,
+         *     search $PATH ourselves for the preprocessor and just exec
+         *     it directly here.
+         */
+
+        argc         = 0;
+        argv[argc++] = "/bin/sh";
+        argv[argc++] = "-c";
+
+        if (cflags != NULL)
+            snprintf(cmd, sizeof(cmd), "%s %s -E %s", preproc, cflags, file);
+        else
+            snprintf(cmd, sizeof(cmd), "%s -E %s", preproc, file);
+
+        argv[argc++] = cmd;
+        argv[argc]   = NULL;
+
+        for (i = 0; i < argc; i++) {
+            verbose_message(3, "shell arg #%d: '%s'\n", i, argv[i]);
+        }
+
+        if (dup2(fd[WR], fileno(stdout)) < 0)
+            fatal_error("failed to redirect stdout (%d: %s)",
+                        errno, strerror(errno));
+
+        if (execv("/bin/sh", argv) != 0)
+            fatal_error("failed to exec command '%s' (%d: %s)", cmd,
+                        errno, strerror(errno));
+        break;
+
+    default: /* parent: return fd to read preprocessed data from */
+        close(fd[WR]);
+        return fd[RD];
+    }
+
+    return -1;  /* never reached */
+}
+
+
+static void input_init(input_t *in, int fd)
+{
+    memset(in, 0, sizeof(*in));
+
+    in->fd = fd;
+}
+
+
+static char input_read(input_t *in)
+{
+    char ch;
+
+    /*
+     * read the next input character
+     *
+     * If there is an pushed back character deliver (and clear) than one.
+     * Otherwise refill the input buffer if needed and return the next
+     * character from it.
+     */
+
+    if (in->nxt != 0) {
+        ch = in->nxt;
+        in->nxt = 0;
+    }
+    else {
+        if (in->len <= in->rd) {
+            in->len = read(in->fd, in->buf, sizeof(in->buf));
+
+            if (in->len > 0) {
+                in->rd = 1;
+                ch = in->buf[0];
+            }
+            else
+                ch = 0;
+        }
+        else
+            return ch = in->buf[in->rd++];
+    }
+
+    return ch;
+}
+
+
+static int input_pushback(input_t *in, char ch)
+{
+    /*
+     * push back a character to the input stream
+     *
+     * Note that you can only push back a single character. Trying to
+     * push back more than one will fail with an error.
+     */
+
+    if (in->nxt == 0) {
+        in->nxt = ch;
+
+        return 0;
+    }
+    else {
+        errno = EBUSY;
+
+        return -1;
+    }
+}
+
+
+static void input_discard_whitespace(input_t *in)
+{
+    char ch;
+
+    /*
+     * discard consecutive whitespace (including newline)
+     */
+
+    while ((ch = input_read(in)) == ' ' || ch == '\t' || ch == '\n')
+        ;
+
+    input_pushback(in, ch);
+}
+
+
+#if 0
+static void input_discard_line(input_t *in)
+{
+    int ch;
+
+    /*
+     * discard input till a newline
+     */
+
+    while ((ch = input_read(in)) != '\n' && ch != 0)
+        ;
+}
+#endif
+
+
+static int input_discard_quoted(input_t *in, char quote)
+{
+    char ch;
+
+    /*
+     * discard a block of quoted input
+     */
+
+    while ((ch = input_read(in)) != quote && ch != 0) {
+        if (ch == '\\')
+            input_read(in);
+    }
+
+    if (ch != quote) {
+        errno = EINVAL;
+        return -1;
+    }
+    else
+        return 0;
+}
+
+
+static int input_discard_block(input_t *in, char beg)
+{
+    char end, ch, quote;
+    int  level;
+
+    /*
+     * discard a block enclosed in {}, [], or ()
+     */
+
+    switch (beg) {
+    case '{': end = '}'; break;
+    case '[': end = ']'; break;
+    case '(': end = ')'; break;
+    default:             return 0;
+    }
+
+    level = 1;
+    while (level > 0) {
+        switch ((ch = input_read(in))) {
+        case '"':
+        case '\'':
+            quote = ch;
+            if (input_discard_quoted(in, quote) != 0)
+                return -1;
+            break;
+
+        default:
+            if (ch == end)
+                level--;
+            else if (ch == beg)
+                level++;
+        }
+    }
+
+    if (level == 0)
+        return 0;
+    else {
+        errno = EINVAL;
+        return -1;
+    }
+}
+
+
+static void ringbuf_init(ringbuf_t *rb)
+{
+    memset(rb->buf, 0, sizeof(rb->buf));
+    rb->wr = 0;
+}
+
+
+static char *ringbuf_save(ringbuf_t *rb, char *token, int len)
+{
+    char *t, *s, *d;
+    int   n, o, i;
+
+    /*
+     * save the given token in the token ring buffer
+     */
+
+    verbose_message(2, "saving '%s'...\n", token);
+
+    if (len < 0)
+        len = strlen(token);
+
+    n = sizeof(rb->buf) - 1 - rb->wr;
+
+    if (n < len + 1) {
+        t = rb->buf;
+        n = sizeof(rb->buf) - 1;
+        o = 0;
+    }
+    else {
+        t = rb->buf + rb->wr;
+        o = rb->wr;
+    }
+
+    if (n >= len + 1) {
+        s = token;
+        d = t;
+
+        for (i = 0; i < len; i++, o++)
+            *d++ = *s++;
+
+        *d = '\0';
+        rb->wr = o + 1;
+
+        return t;
+    }
+    else {
+        errno = ENOSPC;
+        return NULL;
+    }
+}
+
+
+static char *input_collect_word(input_t *in, ringbuf_t *rb)
+{
+#define WORD_CHAR(c)                            \
+    (('a' <= (c) && (c) <= 'z') ||              \
+     ('A' <= (c) && (c) <= 'Z') ||              \
+     ('0' <= (c) && (c) <= '9') ||              \
+     ((c) == '_' || (c) == '$'))
+
+    char buf[MAX_TOKEN], ch;
+    int  n;
+
+    /*
+     * collect and save the next word (consecutive sequence) of input
+     */
+
+    for (n = 0; n < (int)sizeof(buf) - 1; n++) {
+        ch = input_read(in);
+
+        if (WORD_CHAR(ch))
+            buf[n] = ch;
+        else {
+            buf[n] = '\0';
+            input_pushback(in, ch);
+
+            return ringbuf_save(rb, buf, n);
+        }
+    }
+
+    errno = ENOSPC;
+    return NULL;
+}
+
+
+static char *input_parse_linemarker(input_t *in, char *buf, size_t size)
+{
+    char ch;
+    int  i;
+
+    while((ch = input_read(in)) != '"' && ch != '\n' && ch)
+        ;
+
+    if (ch != '"')
+        return NULL;
+
+    for (i = 0; i < (int)size - 1; i++) {
+        buf[i] = ch = input_read(in);
+
+        if (ch == '"') {
+            buf[i] = '\0';
+
+            while ((ch = input_read(in)) != '\n' && ch)
+                ;
+
+            return buf;
+        }
+    }
+
+    return NULL;
+}
+
+
+static int same_file(const char *path1, const char *path2)
+{
+    struct stat st1, st2;
+
+    if (stat(path1, &st1) != 0 || stat(path2, &st2) != 0)
+        return 0;
+    else
+        return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
+}
+
+
+static int collect_tokens(input_t *in, ringbuf_t *rb, token_t *tokens,
+                          int ntoken)
+{
+    char ch, *v, path[1024];
+    int  n, has_paren;
+
+    /*
+     * collect a sequence of tokens that forms (or looks like) a logical unit
+     */
+
+    n = 0;
+    has_paren = 0;
+    while (n < ntoken) {
+        switch ((ch = input_read(in))) {
+            /* always treat a semicolon here as a sequence terminator */
+        case ';':
+            tokens[n].type  = TOKEN_SEMICOLON;
+            tokens[n].value = ringbuf_save(rb, ";", 1);
+            return n + 1;
+
+            /* extract path name from preprocessor line-markers */
+        case '#':
+            v = input_parse_linemarker(in, path, sizeof(path));
+            if (v != NULL) {
+                tokens[n].type  = TOKEN_LINEMARKER;
+                tokens[n].value = ringbuf_save(rb, v, -1);
+                if (n == 0)
+                    return n + 1;
+                else
+                    return -1;
+            }
+            break;
+
+            /* discard whitespace (including trailing newlines) */
+        case ' ':
+        case '\t':
+            input_discard_whitespace(in);
+            break;
+
+            /* ignore newlines */
+        case '\n':
+            break;
+
+            /* collate/collapse blocks to a block indicator token */
+        case '{':
+        case '(':
+        case '[':
+            if (input_discard_block(in, ch) != 0)
+                return -1;
+            else {
+                /* filter out __attribute__ ((.*)) token pairs */
+                if (ch == '(' && n > 0 &&
+                    tokens[n-1].type == TOKEN_WORD &&
+                    !strcmp(tokens[n-1].value, "__attribute__")) {
+                    n--;
+                    verbose_message(2, "filtered __attribute__...\n");
+                    continue;
+                }
+
+                v = (ch == '{' ? "{" : (ch == '[' ? "[" : "("));
+                tokens[n].type  = TOKEN_BLOCK;
+                tokens[n].value = ringbuf_save(rb, v, 1);
+                n++;
+
+                if (v[0] == '(')
+                    has_paren = 1;
+                else {
+                    /*
+                     * if this sequence includes both '(...)' and '{...}'
+                     * we assume this to be a function definition so we
+                     * don't wait for a semicolon but terminate sequence
+                     * here
+                     */
+                    if (v[0] == '{')
+                        if (has_paren)
+                            return n;
+                }
+            }
+            break;
+
+            /* end of file terminates the current sequence */
+        case 0:
+            return n;
+
+            /* collect and save the next word */
+        case 'a'...'z':
+        case 'A'...'Z':
+        case '_':
+        case '$':
+        case '0'...'9':
+            input_pushback(in, ch);
+            v = input_collect_word(in, rb);
+
+            if (v != NULL) {
+                if (!strcmp(v, "__extension__"))
+                    break;
+                tokens[n].type  = TOKEN_WORD;
+                tokens[n].value = v;
+                n++;
+            }
+            else
+                return -1;
+            break;
+
+        case '=':
+            tokens[n].type  = TOKEN_ASSIGN;
+            tokens[n].value = ringbuf_save(rb, "=", 1);
+            n++;
+            break;
+
+            /* ignore asterisks */
+        case '*':
+            break;
+
+            /* the rest we print for debugging */
+        default:
+            printf("%c", ch);
+        }
+    }
+
+    errno = EOVERFLOW;
+    return -1;
+}
+
+
+static char *symbol_from_tokens(token_t *tokens, int ntoken)
+{
+#define MATCHING_TOKEN(_n, _type, _val)                         \
+    (tokens[(_n)].type == TOKEN_##_type &&                      \
+     (!*_val || !strcmp(_val, tokens[(_n)].value)))
+
+    int last, has_paren, has_curly, has_bracket, has_assign;
+    int i;
+
+    /*
+     * extract the symbol from a sequence of tokens
+     */
+
+    if (verbosity > 2) {
+        for (i = 0; i < ntoken; i++)
+            verbose_message(3, "0x%x: '%s'\n", tokens[i].type, tokens[i].value);
+        verbose_message(3, "--\n");
+    }
+
+    has_paren = has_curly = has_bracket = has_assign = 0;
+    for (i = 0; i < ntoken; i++) {
+        if      (MATCHING_TOKEN(i, BLOCK , "(")) has_paren   = 1;
+        else if (MATCHING_TOKEN(i, BLOCK , "{")) has_curly   = 1;
+        else if (MATCHING_TOKEN(i, BLOCK , "[")) has_bracket = 1;
+        else if (MATCHING_TOKEN(i, ASSIGN, "" )) has_assign  = 1 + i;
+    }
+
+    last = ntoken - 1;
+
+    if (tokens[0].type != TOKEN_WORD) {
+        verbose_message(2, "ignoring sequence starting with non-word\n");
+        return NULL;
+    }
+
+    /* ignore typedefs and everything static */
+    if (MATCHING_TOKEN(0, WORD, "typedef") ||
+        MATCHING_TOKEN(0, WORD, "static")) {
+        verbose_message(2, "ignoring typedef or static sequence\n");
+        return NULL;
+    }
+
+    /* ignore forward declarations */
+    if (ntoken == 3 &&
+        (MATCHING_TOKEN(0, WORD, "struct") ||
+         MATCHING_TOKEN(0, WORD, "union" ) ||
+         MATCHING_TOKEN(0, WORD, "enum"  )) &&
+        MATCHING_TOKEN(1, WORD, "") &&
+        MATCHING_TOKEN(2, SEMICOLON, "")) {
+        verbose_message(2, "ignoring forward declaration sequence\n");
+        return NULL;
+    }
+
+    /* take care of function prototypes */
+    if (last > 2) {
+        if (MATCHING_TOKEN(last  , SEMICOLON, "" ) &&
+            MATCHING_TOKEN(last-1, BLOCK    , "(") &&
+            MATCHING_TOKEN(last-2, WORD     , "" ))
+            return tokens[last-2].value;
+    }
+
+    /* take care of global variables with assignments */
+    if (last > 1 && has_assign) {
+        i = has_assign - 1;
+        if (i > 0 && MATCHING_TOKEN(i-1, WORD, ""))
+            return tokens[i-1].value;
+        if (i > 1 &&
+            MATCHING_TOKEN(i-1, BLOCK, "[") &&
+            MATCHING_TOKEN(i-2, WORD , ""))
+            return tokens[i-2].value;
+    }
+
+    /* take care of global variables */
+    if (last > 1 && !has_paren && !has_curly) {
+        if (MATCHING_TOKEN(last  , SEMICOLON, "") &&
+            MATCHING_TOKEN(last-1, WORD     , ""))
+            return tokens[last-1].value;
+    }
+
+    verbose_message(2, "ignoring other non-matching token sequence\n");
+
+    return NULL;
+}
+
+
+static void symtab_init(symtab_t *st)
+{
+    st->syms = NULL;
+    st->nsym = 0;
+}
+
+
+static void symtab_add(symtab_t *st, char *sym)
+{
+    int i;
+
+    for (i = 0; i < st->nsym; i++)
+        if (!strcmp(st->syms[i], sym))
+            return;
+
+    st->syms = realloc(st->syms, (st->nsym + 1) * sizeof(st->syms[0]));
+
+    if (st->syms != NULL) {
+        st->syms[st->nsym] = strdup(sym);
+
+        if (st->syms[st->nsym] != NULL) {
+            st->nsym++;
+
+            return;
+        }
+
+        fatal_error("failed to save symbol '%s'", sym);
+    }
+
+    fatal_error("failed to allocate new symbol table entry");
+}
+
+
+static void symtab_reset(symtab_t *st)
+{
+    int i;
+
+    for (i = 0; i < st->nsym; i++)
+        free(st->syms[i]);
+
+    free(st->syms);
+
+    st->syms = NULL;
+    st->nsym = 0;
+}
+
+
+static void symtab_dump(symtab_t *st, int gnuld, FILE *out)
+{
+    int i;
+
+    if (!gnuld) {
+        for (i = 0; i < st->nsym; i++)
+            fprintf(out, "%s\n", st->syms[i]);
+    }
+    else {
+        fprintf(out, "{\n");
+        if (st->nsym > 0) {
+            fprintf(out, "    global:\n");
+            for (i = 0; i < st->nsym; i++)
+                fprintf(out, "        %s;\n", st->syms[i]);
+        }
+        fprintf(out, "    local:\n");
+        fprintf(out, "        *;\n");
+        fprintf(out, "};\n");
+    }
+}
+
+
+static void extract_symbols(const char *preproc, const char *path,
+                            const char *cflags, symtab_t *st, regex_t *re)
+{
+    input_t   in;
+    ringbuf_t rb;
+    int       fd;
+    pid_t     pp_pid;
+    token_t   tokens[MAX_TOKENS];
+    int       ntoken;
+    char     *sym;
+    int       pp_status, foreign;
+
+    fd = preprocess_file(preproc, path, cflags, &pp_pid);
+
+    input_init(&in, fd);
+    ringbuf_init(&rb);
+    foreign = 0;
+
+    while ((ntoken = collect_tokens(&in, &rb, tokens, MAX_TOKENS)) > 0) {
+        if (tokens[0].type == TOKEN_LINEMARKER) {
+            foreign = !same_file(path, tokens[0].value);
+
+            verbose_message(2, "input switched to %s file '%s'...\n",
+                            foreign ? "foreign" : "input", tokens[0].value);
+
+            continue;
+        }
+
+        if (foreign) {
+            verbose_message(2, "ignoring token stream from foreign file...\n");
+            continue;
+        }
+
+        sym = symbol_from_tokens(tokens, ntoken);
+
+        if (sym != NULL) {
+            if (re == NULL || regexec(re, sym, 0, NULL, 0) == 0)
+                symtab_add(st, sym);
+            else
+                verbose_message(2, "filtered non-matching '%s'...\n", sym);
+        }
+    }
+
+    close(fd);
+    waitpid(pp_pid, &pp_status, 0);
+
+    if (WIFEXITED(pp_status) && WEXITSTATUS(pp_status) != 0)
+        fatal_error("preprocessing of '%s' failed\n", path);
+}
+
+
+int main(int argc, char *argv[])
+{
+    config_t  cfg;
+    symtab_t  st;
+    regex_t   rebuf, *re;
+    char      regerr[1024];
+    FILE     *out;
+    int       err, i;
+
+    if (getenv("__COLLECT_SYMBOLS_DEBUG") != NULL) {
+        verbosity = 3;
+        for (i = 0; i < argc; i++) {
+            verbose_message(0, "argv[%d]: '%s'\n", i, argv[i]);
+        }
+    }
+
+    symtab_init(&st);
+    parse_cmdline(&cfg, argc, argv);
+
+    verbose_message(1, "using preprocessor '%s', cflags '%s'\n", cfg.preproc,
+                    cfg.cflags ? cfg.cflags : "");
+
+    if (cfg.pattern != NULL) {
+        err = regcomp(&rebuf, cfg.pattern, REG_EXTENDED);
+
+        if (err != 0) {
+            regerror(err, &rebuf, regerr, sizeof(regerr));
+            fatal_error("invalid pattern '%s' (error: %s)\n", cfg.pattern,
+                        regerr);
+        }
+
+        re = &rebuf;
+    }
+    else
+        re = NULL;
+
+    for (i = 0; i < cfg.nfile; i++)
+        extract_symbols(cfg.preproc, cfg.files[i], cfg.cflags, &st, re);
+
+    if (cfg.output != NULL) {
+        out = fopen(cfg.output, "w");
+
+        if (out == NULL)
+            fatal_error("failed to open '%s' (%d: %s)", cfg.output,
+                        errno, strerror(errno));
+    }
+    else
+        out = stdout;
+
+    symtab_dump(&st, cfg.gnuld, out);
+
+    if (re != NULL)
+        regfree(re);
+
+    symtab_reset(&st);
+
+    if (out != stdout)
+        fclose(out);
+
+    return 0;
+}