From da07ee69267b4557028357eae802d2a7a57c34e7 Mon Sep 17 00:00:00 2001 From: Doohwan Kim Date: Thu, 26 Feb 2015 17:39:16 +0900 Subject: Initialize Tizen 3.0 Signed-off-by: Doohwan Kim Change-Id: I2ad7e23e121506673a724f512fea429489928451 --- AUTHORS | 4 + COPYING | 14 + ChangeLog | 1 + LICENSE-BSD | 26 + Makefile.am | 50 + NEWS | 1 + README | 1 + README.md | 84 + bootstrap | 21 + build-aux/enlicense | 132 + build-aux/gen-linkedin-loader | 163 ++ build-aux/gen-linker-script | 24 + build-aux/gen-linker-script.collect-symbols | 24 + build-aux/gen-linker-script.ctags | 133 + build-aux/git-version-gen | 154 ++ build-aux/shave-libtool.in | 109 + build-aux/shave.in | 133 + configure.ac | 741 +++++ doc/CODING-STYLE | 312 +++ doc/Makefile.am | 34 + doc/Makefile.rules | 43 + doc/common/figures/db-layers.svg | 608 +++++ doc/plugin-developer-guide/Makefile.am | 6 + doc/plugin-developer-guide/db/Makefile.am | 62 + doc/plugin-developer-guide/doxml/Doxyfile | 1720 ++++++++++++ doc/plugin-developer-guide/doxml/Makefile.am | 12 + .../lyx/murphy-db-high-level-api.lyx | 94 + .../lyx/murphy-db-introduction.lyx | 120 + .../lyx/murphy-db-query-language.lyx | 102 + .../lyx/plugin-developer-guide.lyx | 146 + doc/plugins/resource-dbus/dbus-api-resource.txt | 114 + doc/plugins/resource-dbus/resource-client.py | 577 ++++ doc/scripts/abnf.py | 640 +++++ doc/scripts/dblyxfix.py | 87 + doc/scripts/doxml2db.py | 915 +++++++ doc/scripts/doxydeps.py | 220 ++ githooks/commit-msg | 206 ++ githooks/post-rewrite | 10 + githooks/pre-commit | 118 + githooks/pre-rebase | 25 + m4/docsetup.m4 | 22 + m4/shave.m4 | 113 + m4/websockets.m4 | 234 ++ packaging.in/murphy-lua.conf | 1 + packaging.in/murphy-wait-for-launchpad-ready.path | 3 + packaging.in/murphy.lua | 2385 ++++++++++++++++ packaging.in/murphy.spec.in | 596 ++++ packaging.in/murphyd.conf | 2 + packaging.in/murphyd.init | 156 ++ packaging.in/murphyd.service | 10 + packaging.in/org.Murphy.conf.in | 15 + packaging/murphy.manifest | 5 + packaging/murphy.spec | 601 ++++ patches/lua/lua-5.1.4-specfile.patch | 79 + patches/lua/lua-5.2.2-specfile.patch | 62 + scripts/cleanup-whitespace.sh | 55 + src/Makefile.am | 1524 +++++++++++ src/breedline/LICENSE-BSD | 26 + src/breedline/Makefile | 7 + src/breedline/README | 43 + src/breedline/breedline-glib.c | 131 + src/breedline/breedline-glib.h | 46 + src/breedline/breedline-glib.pc.in | 11 + src/breedline/breedline-murphy.c | 124 + src/breedline/breedline-murphy.h | 44 + src/breedline/breedline-murphy.pc.in | 11 + src/breedline/breedline.c | 1490 ++++++++++ src/breedline/breedline.h | 98 + src/breedline/breedline.pc.in | 10 + src/breedline/macros.h | 51 + src/breedline/mm.h | 85 + src/breedline/tests/Makefile.am | 28 + src/breedline/tests/breedline-glib-test.c | 89 + src/breedline/tests/breedline-murphy-test.c | 88 + src/breedline/tests/breedline-test.c | 68 + src/common.h | 44 + src/common/Makefile | 7 + src/common/dbus-error.h | 82 + src/common/dbus-libdbus-glue.c | 374 +++ src/common/dbus-libdbus-transport.c | 1745 ++++++++++++ src/common/dbus-libdbus.c | 2078 ++++++++++++++ src/common/dbus-libdbus.h | 362 +++ src/common/dbus-sdbus-glue.c | 140 + src/common/dbus-sdbus-transport.c | 1782 ++++++++++++ src/common/dbus-sdbus.c | 1955 +++++++++++++ src/common/dbus-sdbus.h | 398 +++ src/common/dbus-transport.c | 1721 ++++++++++++ src/common/dbus-transport.h | 55 + src/common/debug-auto-register.c | 47 + src/common/debug-info.h | 60 + src/common/debug.c | 418 +++ src/common/debug.h | 111 + src/common/dgram-transport.c | 866 ++++++ src/common/ecore-glue.c | 372 +++ src/common/ecore-glue.h | 46 + src/common/env.c | 156 ++ src/common/env.h | 53 + src/common/file-utils.c | 380 +++ src/common/file-utils.h | 79 + src/common/fragbuf.c | 276 ++ src/common/fragbuf.h | 95 + src/common/glib-glue.c | 362 +++ src/common/glib-glue.h | 49 + src/common/hashtbl.c | 377 +++ src/common/hashtbl.h | 102 + src/common/internal-transport.c | 585 ++++ src/common/json.c | 599 ++++ src/common/json.h | 248 ++ src/common/libdbus-glue.c | 374 +++ src/common/libdbus.c | 1471 ++++++++++ src/common/libdbus.h | 178 ++ src/common/list.h | 199 ++ src/common/log.c | 413 +++ src/common/log.h | 157 ++ src/common/macros.h | 160 ++ src/common/mainloop.c | 2625 ++++++++++++++++++ src/common/mainloop.h | 431 +++ src/common/mask.h | 464 ++++ src/common/mm.c | 1414 ++++++++++ src/common/mm.h | 185 ++ src/common/msg.c | 2222 +++++++++++++++ src/common/msg.h | 461 ++++ src/common/murphy-common.pc.in | 11 + src/common/murphy-dbus-libdbus.pc.in | 11 + src/common/murphy-dbus-sdbus.pc.in | 11 + src/common/murphy-ecore.pc.in | 11 + src/common/murphy-glib.pc.in | 11 + src/common/murphy-libdbus.pc.in | 11 + src/common/murphy-pulse.pc.in | 11 + src/common/murphy-qt.pc.in | 11 + src/common/native-types.c | 1626 +++++++++++ src/common/native-types.h | 368 +++ src/common/process.c | 1077 ++++++++ src/common/process.h | 75 + src/common/pulse-glue.c | 353 +++ src/common/pulse-glue.h | 45 + src/common/pulse-subloop.c | 572 ++++ src/common/pulse-subloop.h | 54 + src/common/qt-glue-priv.h | 114 + src/common/qt-glue.cpp | 413 +++ src/common/qt-glue.h | 49 + src/common/refcnt.h | 116 + src/common/socket-utils.c | 101 + src/common/socket-utils.h | 43 + src/common/stream-transport.c | 853 ++++++ src/common/tests/Makefile.am | 144 + src/common/tests/dbus-pump.c | 350 +++ src/common/tests/dbus-sdbus-test.c | 563 ++++ src/common/tests/dbus-test.c | 513 ++++ src/common/tests/fragbuf-test.c | 384 +++ src/common/tests/glib-pump.c | 149 + src/common/tests/hash-test.c | 419 +++ src/common/tests/hash12-test.c | 112 + src/common/tests/internal-transport-test.c | 784 ++++++ src/common/tests/libdbus-test.c | 482 ++++ src/common/tests/libdbus-transport-test.c | 847 ++++++ src/common/tests/mainloop-ecore-test.c | 115 + src/common/tests/mainloop-glib-test.c | 145 + src/common/tests/mainloop-pulse-test.c | 142 + src/common/tests/mainloop-qt-test.cpp | 109 + src/common/tests/mainloop-qt-test.h | 46 + src/common/tests/mainloop-test.c | 1795 ++++++++++++ src/common/tests/mask-test.c | 130 + src/common/tests/mkdir-test.c | 21 + src/common/tests/mm-test.c | 284 ++ src/common/tests/msg-test.c | 823 ++++++ src/common/tests/native-test.c | 307 +++ src/common/tests/path-test.c | 50 + src/common/tests/process-test.c | 151 + src/common/tests/sdbus-error-message.c | 99 + src/common/tests/sdbus-test.c | 226 ++ src/common/tests/transport-test.c | 998 +++++++ src/common/tlv.c | 662 +++++ src/common/tlv.h | 130 + src/common/transport.c | 829 ++++++ src/common/transport.h | 559 ++++ src/common/utils.c | 211 ++ src/common/utils.h | 40 + src/common/websocket.c | 96 + src/common/websocket.h | 113 + src/common/websocklib.c | 2105 ++++++++++++++ src/common/websocklib.h | 238 ++ src/common/wsck-transport.c | 995 +++++++ src/common/wsck-transport.h | 142 + src/console-client/Makefile | 7 + src/console-client/client.c | 478 ++++ src/core.h | 37 + src/core/Makefile | 7 + src/core/auth-deny.c | 47 + src/core/auth-smack.c | 70 + src/core/auth.c | 204 ++ src/core/auth.h | 98 + src/core/console-builtin.c | 258 ++ src/core/console-command.c | 33 + src/core/console-command.h | 148 + src/core/console-db.c | 138 + src/core/console-debug.c | 202 ++ src/core/console-log.c | 112 + src/core/console-priv.h | 38 + src/core/console.c | 1051 +++++++ src/core/console.h | 185 ++ src/core/context.c | 75 + src/core/context.h | 114 + src/core/domain-types.h | 106 + src/core/domain.c | 147 + src/core/domain.h | 90 + src/core/event.c | 356 +++ src/core/event.h | 199 ++ src/core/lua-bindings/Makefile | 7 + src/core/lua-bindings/lua-bitwise.c | 156 ++ src/core/lua-bindings/lua-console.c | 339 +++ src/core/lua-bindings/lua-deferred.c | 290 ++ src/core/lua-bindings/lua-env.c | 129 + src/core/lua-bindings/lua-event.c | 402 +++ src/core/lua-bindings/lua-json.c | 456 ++++ src/core/lua-bindings/lua-json.h | 51 + src/core/lua-bindings/lua-log.c | 148 + src/core/lua-bindings/lua-lua.c | 213 ++ src/core/lua-bindings/lua-murphy.c | 608 +++++ src/core/lua-bindings/lua-plugin.c | 428 +++ src/core/lua-bindings/lua-sighandler.c | 267 ++ src/core/lua-bindings/lua-timer.c | 292 ++ src/core/lua-bindings/lua-transport.c | 635 +++++ src/core/lua-bindings/murphy.h | 111 + src/core/lua-decision/Makefile | 7 + src/core/lua-decision/element.c | 1234 +++++++++ src/core/lua-decision/element.h | 117 + src/core/lua-decision/mdb.c | 1746 ++++++++++++ src/core/lua-decision/mdb.h | 79 + src/core/lua-decision/murphy-lua-decision.pc.in | 11 + src/core/lua-decision/tests/Makefile.am | 18 + src/core/lua-decision/tests/decision-test.c | 458 ++++ src/core/lua-decision/tests/decision-test.lua | 171 ++ src/core/lua-utils/Makefile | 7 + src/core/lua-utils/error.c | 62 + src/core/lua-utils/error.h | 249 ++ src/core/lua-utils/funcbridge.c | 1155 ++++++++ src/core/lua-utils/funcbridge.h | 136 + src/core/lua-utils/include.c | 123 + src/core/lua-utils/include.h | 54 + src/core/lua-utils/lua-utils.c | 231 ++ src/core/lua-utils/lua-utils.h | 91 + src/core/lua-utils/murphy-lua-utils.pc.in | 11 + src/core/lua-utils/object.c | 2877 ++++++++++++++++++++ src/core/lua-utils/object.h | 770 ++++++ src/core/lua-utils/strarray.c | 133 + src/core/lua-utils/strarray.h | 54 + src/core/method.c | 439 +++ src/core/method.h | 132 + src/core/murphy-core.pc.in | 11 + src/core/plugin.c | 1155 ++++++++ src/core/plugin.h | 455 ++++ src/core/scripting.c | 523 ++++ src/core/scripting.h | 251 ++ src/core/tests/Makefile.am | 1 + src/daemon/Makefile | 7 + src/daemon/config.c | 1550 +++++++++++ src/daemon/config.h | 90 + src/daemon/daemon.c | 303 +++ src/daemon/daemon.h | 43 + src/daemon/murphy-lua.conf | 2 + src/daemon/murphy.conf | 51 + src/daemon/murphy.lua | 283 ++ src/daemon/sample-config/cgroup-test.rules | 33 + src/daemon/sample-config/common.cfg | 56 + src/daemon/sample-config/console.cfg | 14 + src/daemon/sample-config/domain-control.cfg | 12 + src/daemon/sample-config/glib.cfg | 4 + src/daemon/sample-config/main.cfg | 42 + src/daemon/sample-config/murphy.cfg | 1 + src/daemon/sample-config/resource.cfg | 136 + src/daemon/sample-config/speed-volume.rules | 28 + src/daemon/sample-config/system-controller.cfg | 3 + src/daemon/sample-config/system-monitor.cfg | 4 + src/daemon/sample-config/system-monitor.rules | 67 + src/daemon/sample-config/systemd.cfg | 4 + src/daemon/sample-config/timer-test.rules | 65 + src/daemon/tests/Makefile.am | 1 + src/murphy-db/Makefile.am | 21 + src/murphy-db/include/Makefile.am | 6 + src/murphy-db/include/murphy-db/assert.h | 46 + src/murphy-db/include/murphy-db/handle.h | 60 + src/murphy-db/include/murphy-db/hash.h | 91 + src/murphy-db/include/murphy-db/list.h | 122 + src/murphy-db/include/murphy-db/mdb.h | 89 + src/murphy-db/include/murphy-db/mqi-types.h | 281 ++ src/murphy-db/include/murphy-db/mqi.h | 208 ++ src/murphy-db/include/murphy-db/mql-result.h | 113 + src/murphy-db/include/murphy-db/mql-statement.h | 74 + src/murphy-db/include/murphy-db/mql-trigger.h | 51 + src/murphy-db/include/murphy-db/mql.h | 168 ++ src/murphy-db/include/murphy-db/sequence.h | 76 + src/murphy-db/mdb/Makefile.am | 48 + src/murphy-db/mdb/column.c | 225 ++ src/murphy-db/mdb/column.h | 61 + src/murphy-db/mdb/cond.c | 398 +++ src/murphy-db/mdb/cond.h | 48 + src/murphy-db/mdb/handle.c | 379 +++ src/murphy-db/mdb/hash.c | 574 ++++ src/murphy-db/mdb/index.c | 345 +++ src/murphy-db/mdb/index.h | 73 + src/murphy-db/mdb/log.c | 489 ++++ src/murphy-db/mdb/log.h | 94 + src/murphy-db/mdb/mqi-types.c | 164 ++ src/murphy-db/mdb/row.c | 171 ++ src/murphy-db/mdb/row.h | 59 + src/murphy-db/mdb/sequence.c | 331 +++ src/murphy-db/mdb/table.c | 837 ++++++ src/murphy-db/mdb/table.h | 67 + src/murphy-db/mdb/transaction.c | 276 ++ src/murphy-db/mdb/transaction.h | 47 + src/murphy-db/mdb/trigger.c | 628 +++++ src/murphy-db/mdb/trigger.h | 66 + src/murphy-db/mqi/Makefile.am | 34 + src/murphy-db/mqi/db.h | 80 + src/murphy-db/mqi/mdb-backend.c | 314 +++ src/murphy-db/mqi/mdb-backend.h | 46 + src/murphy-db/mqi/mqi.c | 833 ++++++ src/murphy-db/mql/Makefile.am | 61 + src/murphy-db/mql/mql-parser.y | 1564 +++++++++++ src/murphy-db/mql/mql-scanner.l | 283 ++ src/murphy-db/mql/result.c | 1480 ++++++++++ src/murphy-db/mql/statement.c | 1026 +++++++ src/murphy-db/mql/transaction.c | 151 + src/murphy-db/mql/trigger.c | 700 +++++ src/murphy-db/murphy-db.pc.in | 12 + src/murphy-db/tests/Makefile.am | 47 + src/murphy-db/tests/check-libmdb.c | 88 + src/murphy-db/tests/check-libmqi.c | 1181 ++++++++ src/murphy-db/tests/check-libmql.c | 985 +++++++ src/plugins/Makefile | 7 + src/plugins/console-protocol.h | 39 + src/plugins/console/Makefile | 13 + src/plugins/console/console.html | 92 + src/plugins/console/console.js | 451 +++ src/plugins/console/plugin-console.c | 821 ++++++ src/plugins/domain-control/Makefile | 7 + src/plugins/domain-control/client.c | 807 ++++++ src/plugins/domain-control/client.h | 175 ++ src/plugins/domain-control/domain-control-api.js | 430 +++ .../domain-control/domain-control-test.html | 224 ++ src/plugins/domain-control/domain-control-types.h | 171 ++ src/plugins/domain-control/domain-control.c | 782 ++++++ src/plugins/domain-control/domain-control.h | 42 + src/plugins/domain-control/message.c | 1671 ++++++++++++ src/plugins/domain-control/message.h | 202 ++ .../domain-control/murphy-domain-controller.pc.in | 11 + src/plugins/domain-control/notify.c | 164 ++ src/plugins/domain-control/notify.h | 37 + src/plugins/domain-control/plugin-domain-control.c | 125 + src/plugins/domain-control/proxy.c | 255 ++ src/plugins/domain-control/proxy.h | 56 + src/plugins/domain-control/table.c | 645 +++++ src/plugins/domain-control/table.h | 62 + src/plugins/domain-control/test-client.c | 1381 ++++++++++ src/plugins/plugin-dbus.c | 381 +++ src/plugins/plugin-glib.c | 180 ++ src/plugins/plugin-lua.c | 207 ++ src/plugins/plugin-resource-dbus.c | 2345 ++++++++++++++++ src/plugins/plugin-systemd.c | 67 + src/plugins/plugin-test.c | 810 ++++++ src/plugins/resource-dbus/org.Murphy.conf | 15 + src/plugins/resource-native/Makefile | 7 + .../resource-native/libmurphy-resource/api_test.c | 419 +++ .../resource-native/libmurphy-resource/attribute.c | 233 ++ .../resource-native/libmurphy-resource/attribute.h | 45 + .../libmurphy-resource/context-create.c | 87 + .../resource-native/libmurphy-resource/message.c | 672 +++++ .../resource-native/libmurphy-resource/message.h | 92 + .../libmurphy-resource/murphy-resource.pc.in | 11 + .../libmurphy-resource/resource-api.h | 471 ++++ .../libmurphy-resource/resource-fuzz.c | 399 +++ .../libmurphy-resource/resource-log.c | 70 + .../libmurphy-resource/resource-private.h | 158 ++ .../resource-native/libmurphy-resource/resource.c | 552 ++++ .../resource-native/libmurphy-resource/rset.c | 888 ++++++ .../resource-native/libmurphy-resource/rset.h | 63 + .../libmurphy-resource/string_array.c | 92 + .../libmurphy-resource/string_array.h | 40 + .../resource-native/plugin-resource-native.c | 1225 +++++++++ src/plugins/resource-native/resource-client.c | 1744 ++++++++++++ src/plugins/resource-wrt/Makefile | 7 + src/plugins/resource-wrt/plugin-resource-wrt.c | 1234 +++++++++ src/plugins/resource-wrt/resource-api.js | 585 ++++ src/plugins/resource-wrt/resource-test.html | 399 +++ src/plugins/resource-wrt/resource-wrt.h | 46 + src/plugins/tests/Makefile.am | 4 + src/resolver/console.c | 51 + src/resolver/events.c | 52 + src/resolver/events.h | 48 + src/resolver/fact.c | 246 ++ src/resolver/fact.h | 50 + src/resolver/murphy-resolver.pc.in | 11 + src/resolver/parser-api.h | 83 + src/resolver/parser.y | 277 ++ src/resolver/resolver-types.h | 89 + src/resolver/resolver.c | 330 +++ src/resolver/resolver.h | 134 + src/resolver/scanner.h | 40 + src/resolver/scanner.l | 327 +++ src/resolver/scripting/simple/builtins.c | 75 + src/resolver/scripting/simple/builtins.h | 35 + src/resolver/scripting/simple/call.c | 256 ++ src/resolver/scripting/simple/call.h | 49 + src/resolver/scripting/simple/simple-parser-api.h | 61 + src/resolver/scripting/simple/simple-parser.y | 343 +++ src/resolver/scripting/simple/simple-scanner.h | 39 + src/resolver/scripting/simple/simple-scanner.l | 295 ++ src/resolver/scripting/simple/simple-script.c | 148 + src/resolver/scripting/simple/simple-script.h | 90 + src/resolver/scripting/simple/token.h | 181 ++ src/resolver/target-sorter.c | 550 ++++ src/resolver/target-sorter.h | 38 + src/resolver/target.c | 761 ++++++ src/resolver/target.h | 57 + src/resolver/test-input | 22 + src/resolver/test-input-audio | 24 + src/resolver/test-input-video | 5 + src/resolver/tests/Makefile.am | 10 + src/resolver/tests/parser-test.c | 228 ++ src/resolver/tests/test | 19 + src/resolver/token.h | 112 + src/resource/Makefile | 7 + src/resource/application-class.c | 558 ++++ src/resource/application-class.h | 67 + src/resource/attribute.c | 299 ++ src/resource/attribute.h | 56 + src/resource/client-api.h | 141 + src/resource/common-api.h | 53 + src/resource/config-api.h | 59 + src/resource/config-lua.c | 1751 ++++++++++++ src/resource/config-lua.h | 59 + src/resource/data-types.h | 163 ++ src/resource/lua-resource.c | 1292 +++++++++ src/resource/manager-api.h | 69 + src/resource/protocol.h | 110 + src/resource/resource-client.c | 113 + src/resource/resource-client.h | 55 + src/resource/resource-lua.c | 711 +++++ src/resource/resource-lua.h | 69 + src/resource/resource-owner.c | 770 ++++++ src/resource/resource-owner.h | 63 + src/resource/resource-set.c | 684 +++++ src/resource/resource-set.h | 110 + src/resource/resource.c | 848 ++++++ src/resource/resource.h | 88 + src/resource/zone.c | 383 +++ src/resource/zone.h | 62 + utils/Makefile.am | 10 + utils/collect-symbols.c | 997 +++++++ 450 files changed, 142296 insertions(+) create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 LICENSE-BSD create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 README.md create mode 100755 bootstrap create mode 100755 build-aux/enlicense create mode 100755 build-aux/gen-linkedin-loader create mode 100755 build-aux/gen-linker-script create mode 100755 build-aux/gen-linker-script.collect-symbols create mode 100755 build-aux/gen-linker-script.ctags create mode 100755 build-aux/git-version-gen create mode 100644 build-aux/shave-libtool.in create mode 100644 build-aux/shave.in create mode 100644 configure.ac create mode 100644 doc/CODING-STYLE create mode 100644 doc/Makefile.am create mode 100644 doc/Makefile.rules create mode 100644 doc/common/figures/db-layers.svg create mode 100644 doc/plugin-developer-guide/Makefile.am create mode 100644 doc/plugin-developer-guide/db/Makefile.am create mode 100644 doc/plugin-developer-guide/doxml/Doxyfile create mode 100644 doc/plugin-developer-guide/doxml/Makefile.am create mode 100644 doc/plugin-developer-guide/lyx/murphy-db-high-level-api.lyx create mode 100644 doc/plugin-developer-guide/lyx/murphy-db-introduction.lyx create mode 100644 doc/plugin-developer-guide/lyx/murphy-db-query-language.lyx create mode 100644 doc/plugin-developer-guide/lyx/plugin-developer-guide.lyx create mode 100644 doc/plugins/resource-dbus/dbus-api-resource.txt create mode 100644 doc/plugins/resource-dbus/resource-client.py create mode 100755 doc/scripts/abnf.py create mode 100755 doc/scripts/dblyxfix.py create mode 100755 doc/scripts/doxml2db.py create mode 100755 doc/scripts/doxydeps.py create mode 100755 githooks/commit-msg create mode 100755 githooks/post-rewrite create mode 100755 githooks/pre-commit create mode 100755 githooks/pre-rebase create mode 100644 m4/docsetup.m4 create mode 100644 m4/shave.m4 create mode 100644 m4/websockets.m4 create mode 100644 packaging.in/murphy-lua.conf create mode 100644 packaging.in/murphy-wait-for-launchpad-ready.path create mode 100644 packaging.in/murphy.lua create mode 100644 packaging.in/murphy.spec.in create mode 100644 packaging.in/murphyd.conf create mode 100755 packaging.in/murphyd.init create mode 100644 packaging.in/murphyd.service create mode 100644 packaging.in/org.Murphy.conf.in create mode 100644 packaging/murphy.manifest create mode 100644 packaging/murphy.spec create mode 100644 patches/lua/lua-5.1.4-specfile.patch create mode 100644 patches/lua/lua-5.2.2-specfile.patch create mode 100755 scripts/cleanup-whitespace.sh create mode 100644 src/Makefile.am create mode 100644 src/breedline/LICENSE-BSD create mode 100644 src/breedline/Makefile create mode 100644 src/breedline/README create mode 100644 src/breedline/breedline-glib.c create mode 100644 src/breedline/breedline-glib.h create mode 100644 src/breedline/breedline-glib.pc.in create mode 100644 src/breedline/breedline-murphy.c create mode 100644 src/breedline/breedline-murphy.h create mode 100644 src/breedline/breedline-murphy.pc.in create mode 100644 src/breedline/breedline.c create mode 100644 src/breedline/breedline.h create mode 100644 src/breedline/breedline.pc.in create mode 100644 src/breedline/macros.h create mode 100644 src/breedline/mm.h create mode 100644 src/breedline/tests/Makefile.am create mode 100644 src/breedline/tests/breedline-glib-test.c create mode 100644 src/breedline/tests/breedline-murphy-test.c create mode 100644 src/breedline/tests/breedline-test.c create mode 100644 src/common.h create mode 100644 src/common/Makefile create mode 100644 src/common/dbus-error.h create mode 100644 src/common/dbus-libdbus-glue.c create mode 100644 src/common/dbus-libdbus-transport.c create mode 100644 src/common/dbus-libdbus.c create mode 100644 src/common/dbus-libdbus.h create mode 100644 src/common/dbus-sdbus-glue.c create mode 100644 src/common/dbus-sdbus-transport.c create mode 100644 src/common/dbus-sdbus.c create mode 100644 src/common/dbus-sdbus.h create mode 100644 src/common/dbus-transport.c create mode 100644 src/common/dbus-transport.h create mode 100644 src/common/debug-auto-register.c create mode 100644 src/common/debug-info.h create mode 100644 src/common/debug.c create mode 100644 src/common/debug.h create mode 100644 src/common/dgram-transport.c create mode 100644 src/common/ecore-glue.c create mode 100644 src/common/ecore-glue.h create mode 100644 src/common/env.c create mode 100644 src/common/env.h create mode 100644 src/common/file-utils.c create mode 100644 src/common/file-utils.h create mode 100644 src/common/fragbuf.c create mode 100644 src/common/fragbuf.h create mode 100644 src/common/glib-glue.c create mode 100644 src/common/glib-glue.h create mode 100644 src/common/hashtbl.c create mode 100644 src/common/hashtbl.h create mode 100644 src/common/internal-transport.c create mode 100644 src/common/json.c create mode 100644 src/common/json.h create mode 100644 src/common/libdbus-glue.c create mode 100644 src/common/libdbus.c create mode 100644 src/common/libdbus.h create mode 100644 src/common/list.h create mode 100644 src/common/log.c create mode 100644 src/common/log.h create mode 100644 src/common/macros.h create mode 100644 src/common/mainloop.c create mode 100644 src/common/mainloop.h create mode 100644 src/common/mask.h create mode 100644 src/common/mm.c create mode 100644 src/common/mm.h create mode 100644 src/common/msg.c create mode 100644 src/common/msg.h create mode 100644 src/common/murphy-common.pc.in create mode 100644 src/common/murphy-dbus-libdbus.pc.in create mode 100644 src/common/murphy-dbus-sdbus.pc.in create mode 100644 src/common/murphy-ecore.pc.in create mode 100644 src/common/murphy-glib.pc.in create mode 100644 src/common/murphy-libdbus.pc.in create mode 100644 src/common/murphy-pulse.pc.in create mode 100644 src/common/murphy-qt.pc.in create mode 100644 src/common/native-types.c create mode 100644 src/common/native-types.h create mode 100644 src/common/process.c create mode 100644 src/common/process.h create mode 100644 src/common/pulse-glue.c create mode 100644 src/common/pulse-glue.h create mode 100644 src/common/pulse-subloop.c create mode 100644 src/common/pulse-subloop.h create mode 100644 src/common/qt-glue-priv.h create mode 100644 src/common/qt-glue.cpp create mode 100644 src/common/qt-glue.h create mode 100644 src/common/refcnt.h create mode 100644 src/common/socket-utils.c create mode 100644 src/common/socket-utils.h create mode 100644 src/common/stream-transport.c create mode 100644 src/common/tests/Makefile.am create mode 100644 src/common/tests/dbus-pump.c create mode 100644 src/common/tests/dbus-sdbus-test.c create mode 100644 src/common/tests/dbus-test.c create mode 100644 src/common/tests/fragbuf-test.c create mode 100644 src/common/tests/glib-pump.c create mode 100644 src/common/tests/hash-test.c create mode 100644 src/common/tests/hash12-test.c create mode 100644 src/common/tests/internal-transport-test.c create mode 100644 src/common/tests/libdbus-test.c create mode 100644 src/common/tests/libdbus-transport-test.c create mode 100644 src/common/tests/mainloop-ecore-test.c create mode 100644 src/common/tests/mainloop-glib-test.c create mode 100644 src/common/tests/mainloop-pulse-test.c create mode 100644 src/common/tests/mainloop-qt-test.cpp create mode 100644 src/common/tests/mainloop-qt-test.h create mode 100644 src/common/tests/mainloop-test.c create mode 100644 src/common/tests/mask-test.c create mode 100644 src/common/tests/mkdir-test.c create mode 100644 src/common/tests/mm-test.c create mode 100644 src/common/tests/msg-test.c create mode 100644 src/common/tests/native-test.c create mode 100644 src/common/tests/path-test.c create mode 100644 src/common/tests/process-test.c create mode 100644 src/common/tests/sdbus-error-message.c create mode 100644 src/common/tests/sdbus-test.c create mode 100644 src/common/tests/transport-test.c create mode 100644 src/common/tlv.c create mode 100644 src/common/tlv.h create mode 100644 src/common/transport.c create mode 100644 src/common/transport.h create mode 100644 src/common/utils.c create mode 100644 src/common/utils.h create mode 100644 src/common/websocket.c create mode 100644 src/common/websocket.h create mode 100644 src/common/websocklib.c create mode 100644 src/common/websocklib.h create mode 100644 src/common/wsck-transport.c create mode 100644 src/common/wsck-transport.h create mode 100644 src/console-client/Makefile create mode 100644 src/console-client/client.c create mode 100644 src/core.h create mode 100644 src/core/Makefile create mode 100644 src/core/auth-deny.c create mode 100644 src/core/auth-smack.c create mode 100644 src/core/auth.c create mode 100644 src/core/auth.h create mode 100644 src/core/console-builtin.c create mode 100644 src/core/console-command.c create mode 100644 src/core/console-command.h create mode 100644 src/core/console-db.c create mode 100644 src/core/console-debug.c create mode 100644 src/core/console-log.c create mode 100644 src/core/console-priv.h create mode 100644 src/core/console.c create mode 100644 src/core/console.h create mode 100644 src/core/context.c create mode 100644 src/core/context.h create mode 100644 src/core/domain-types.h create mode 100644 src/core/domain.c create mode 100644 src/core/domain.h create mode 100644 src/core/event.c create mode 100644 src/core/event.h create mode 100644 src/core/lua-bindings/Makefile create mode 100644 src/core/lua-bindings/lua-bitwise.c create mode 100644 src/core/lua-bindings/lua-console.c create mode 100644 src/core/lua-bindings/lua-deferred.c create mode 100644 src/core/lua-bindings/lua-env.c create mode 100644 src/core/lua-bindings/lua-event.c create mode 100644 src/core/lua-bindings/lua-json.c create mode 100644 src/core/lua-bindings/lua-json.h create mode 100644 src/core/lua-bindings/lua-log.c create mode 100644 src/core/lua-bindings/lua-lua.c create mode 100644 src/core/lua-bindings/lua-murphy.c create mode 100644 src/core/lua-bindings/lua-plugin.c create mode 100644 src/core/lua-bindings/lua-sighandler.c create mode 100644 src/core/lua-bindings/lua-timer.c create mode 100644 src/core/lua-bindings/lua-transport.c create mode 100644 src/core/lua-bindings/murphy.h create mode 100644 src/core/lua-decision/Makefile create mode 100644 src/core/lua-decision/element.c create mode 100644 src/core/lua-decision/element.h create mode 100644 src/core/lua-decision/mdb.c create mode 100644 src/core/lua-decision/mdb.h create mode 100644 src/core/lua-decision/murphy-lua-decision.pc.in create mode 100644 src/core/lua-decision/tests/Makefile.am create mode 100644 src/core/lua-decision/tests/decision-test.c create mode 100644 src/core/lua-decision/tests/decision-test.lua create mode 100644 src/core/lua-utils/Makefile create mode 100644 src/core/lua-utils/error.c create mode 100644 src/core/lua-utils/error.h create mode 100644 src/core/lua-utils/funcbridge.c create mode 100644 src/core/lua-utils/funcbridge.h create mode 100644 src/core/lua-utils/include.c create mode 100644 src/core/lua-utils/include.h create mode 100644 src/core/lua-utils/lua-utils.c create mode 100644 src/core/lua-utils/lua-utils.h create mode 100644 src/core/lua-utils/murphy-lua-utils.pc.in create mode 100644 src/core/lua-utils/object.c create mode 100644 src/core/lua-utils/object.h create mode 100644 src/core/lua-utils/strarray.c create mode 100644 src/core/lua-utils/strarray.h create mode 100644 src/core/method.c create mode 100644 src/core/method.h create mode 100644 src/core/murphy-core.pc.in create mode 100644 src/core/plugin.c create mode 100644 src/core/plugin.h create mode 100644 src/core/scripting.c create mode 100644 src/core/scripting.h create mode 100644 src/core/tests/Makefile.am create mode 100644 src/daemon/Makefile create mode 100644 src/daemon/config.c create mode 100644 src/daemon/config.h create mode 100644 src/daemon/daemon.c create mode 100644 src/daemon/daemon.h create mode 100644 src/daemon/murphy-lua.conf create mode 100644 src/daemon/murphy.conf create mode 100644 src/daemon/murphy.lua create mode 100644 src/daemon/sample-config/cgroup-test.rules create mode 100644 src/daemon/sample-config/common.cfg create mode 100644 src/daemon/sample-config/console.cfg create mode 100644 src/daemon/sample-config/domain-control.cfg create mode 100644 src/daemon/sample-config/glib.cfg create mode 100644 src/daemon/sample-config/main.cfg create mode 100644 src/daemon/sample-config/murphy.cfg create mode 100644 src/daemon/sample-config/resource.cfg create mode 100644 src/daemon/sample-config/speed-volume.rules create mode 100644 src/daemon/sample-config/system-controller.cfg create mode 100644 src/daemon/sample-config/system-monitor.cfg create mode 100644 src/daemon/sample-config/system-monitor.rules create mode 100644 src/daemon/sample-config/systemd.cfg create mode 100644 src/daemon/sample-config/timer-test.rules create mode 100644 src/daemon/tests/Makefile.am create mode 100644 src/murphy-db/Makefile.am create mode 100644 src/murphy-db/include/Makefile.am create mode 100644 src/murphy-db/include/murphy-db/assert.h create mode 100644 src/murphy-db/include/murphy-db/handle.h create mode 100644 src/murphy-db/include/murphy-db/hash.h create mode 100644 src/murphy-db/include/murphy-db/list.h create mode 100644 src/murphy-db/include/murphy-db/mdb.h create mode 100644 src/murphy-db/include/murphy-db/mqi-types.h create mode 100644 src/murphy-db/include/murphy-db/mqi.h create mode 100644 src/murphy-db/include/murphy-db/mql-result.h create mode 100644 src/murphy-db/include/murphy-db/mql-statement.h create mode 100644 src/murphy-db/include/murphy-db/mql-trigger.h create mode 100644 src/murphy-db/include/murphy-db/mql.h create mode 100644 src/murphy-db/include/murphy-db/sequence.h create mode 100644 src/murphy-db/mdb/Makefile.am create mode 100644 src/murphy-db/mdb/column.c create mode 100644 src/murphy-db/mdb/column.h create mode 100644 src/murphy-db/mdb/cond.c create mode 100644 src/murphy-db/mdb/cond.h create mode 100644 src/murphy-db/mdb/handle.c create mode 100644 src/murphy-db/mdb/hash.c create mode 100644 src/murphy-db/mdb/index.c create mode 100644 src/murphy-db/mdb/index.h create mode 100644 src/murphy-db/mdb/log.c create mode 100644 src/murphy-db/mdb/log.h create mode 100644 src/murphy-db/mdb/mqi-types.c create mode 100644 src/murphy-db/mdb/row.c create mode 100644 src/murphy-db/mdb/row.h create mode 100644 src/murphy-db/mdb/sequence.c create mode 100644 src/murphy-db/mdb/table.c create mode 100644 src/murphy-db/mdb/table.h create mode 100644 src/murphy-db/mdb/transaction.c create mode 100644 src/murphy-db/mdb/transaction.h create mode 100644 src/murphy-db/mdb/trigger.c create mode 100644 src/murphy-db/mdb/trigger.h create mode 100644 src/murphy-db/mqi/Makefile.am create mode 100644 src/murphy-db/mqi/db.h create mode 100644 src/murphy-db/mqi/mdb-backend.c create mode 100644 src/murphy-db/mqi/mdb-backend.h create mode 100644 src/murphy-db/mqi/mqi.c create mode 100644 src/murphy-db/mql/Makefile.am create mode 100644 src/murphy-db/mql/mql-parser.y create mode 100644 src/murphy-db/mql/mql-scanner.l create mode 100644 src/murphy-db/mql/result.c create mode 100644 src/murphy-db/mql/statement.c create mode 100644 src/murphy-db/mql/transaction.c create mode 100644 src/murphy-db/mql/trigger.c create mode 100644 src/murphy-db/murphy-db.pc.in create mode 100644 src/murphy-db/tests/Makefile.am create mode 100644 src/murphy-db/tests/check-libmdb.c create mode 100644 src/murphy-db/tests/check-libmqi.c create mode 100644 src/murphy-db/tests/check-libmql.c create mode 100644 src/plugins/Makefile create mode 100644 src/plugins/console-protocol.h create mode 100644 src/plugins/console/Makefile create mode 100644 src/plugins/console/console.html create mode 100644 src/plugins/console/console.js create mode 100644 src/plugins/console/plugin-console.c create mode 100644 src/plugins/domain-control/Makefile create mode 100644 src/plugins/domain-control/client.c create mode 100644 src/plugins/domain-control/client.h create mode 100644 src/plugins/domain-control/domain-control-api.js create mode 100644 src/plugins/domain-control/domain-control-test.html create mode 100644 src/plugins/domain-control/domain-control-types.h create mode 100644 src/plugins/domain-control/domain-control.c create mode 100644 src/plugins/domain-control/domain-control.h create mode 100644 src/plugins/domain-control/message.c create mode 100644 src/plugins/domain-control/message.h create mode 100644 src/plugins/domain-control/murphy-domain-controller.pc.in create mode 100644 src/plugins/domain-control/notify.c create mode 100644 src/plugins/domain-control/notify.h create mode 100644 src/plugins/domain-control/plugin-domain-control.c create mode 100644 src/plugins/domain-control/proxy.c create mode 100644 src/plugins/domain-control/proxy.h create mode 100644 src/plugins/domain-control/table.c create mode 100644 src/plugins/domain-control/table.h create mode 100644 src/plugins/domain-control/test-client.c create mode 100644 src/plugins/plugin-dbus.c create mode 100644 src/plugins/plugin-glib.c create mode 100644 src/plugins/plugin-lua.c create mode 100644 src/plugins/plugin-resource-dbus.c create mode 100644 src/plugins/plugin-systemd.c create mode 100644 src/plugins/plugin-test.c create mode 100644 src/plugins/resource-dbus/org.Murphy.conf create mode 100644 src/plugins/resource-native/Makefile create mode 100644 src/plugins/resource-native/libmurphy-resource/api_test.c create mode 100644 src/plugins/resource-native/libmurphy-resource/attribute.c create mode 100644 src/plugins/resource-native/libmurphy-resource/attribute.h create mode 100644 src/plugins/resource-native/libmurphy-resource/context-create.c create mode 100644 src/plugins/resource-native/libmurphy-resource/message.c create mode 100644 src/plugins/resource-native/libmurphy-resource/message.h create mode 100644 src/plugins/resource-native/libmurphy-resource/murphy-resource.pc.in create mode 100644 src/plugins/resource-native/libmurphy-resource/resource-api.h create mode 100644 src/plugins/resource-native/libmurphy-resource/resource-fuzz.c create mode 100644 src/plugins/resource-native/libmurphy-resource/resource-log.c create mode 100644 src/plugins/resource-native/libmurphy-resource/resource-private.h create mode 100644 src/plugins/resource-native/libmurphy-resource/resource.c create mode 100644 src/plugins/resource-native/libmurphy-resource/rset.c create mode 100644 src/plugins/resource-native/libmurphy-resource/rset.h create mode 100644 src/plugins/resource-native/libmurphy-resource/string_array.c create mode 100644 src/plugins/resource-native/libmurphy-resource/string_array.h create mode 100644 src/plugins/resource-native/plugin-resource-native.c create mode 100644 src/plugins/resource-native/resource-client.c create mode 100644 src/plugins/resource-wrt/Makefile create mode 100644 src/plugins/resource-wrt/plugin-resource-wrt.c create mode 100644 src/plugins/resource-wrt/resource-api.js create mode 100644 src/plugins/resource-wrt/resource-test.html create mode 100644 src/plugins/resource-wrt/resource-wrt.h create mode 100644 src/plugins/tests/Makefile.am create mode 100644 src/resolver/console.c create mode 100644 src/resolver/events.c create mode 100644 src/resolver/events.h create mode 100644 src/resolver/fact.c create mode 100644 src/resolver/fact.h create mode 100644 src/resolver/murphy-resolver.pc.in create mode 100644 src/resolver/parser-api.h create mode 100644 src/resolver/parser.y create mode 100644 src/resolver/resolver-types.h create mode 100644 src/resolver/resolver.c create mode 100644 src/resolver/resolver.h create mode 100644 src/resolver/scanner.h create mode 100644 src/resolver/scanner.l create mode 100644 src/resolver/scripting/simple/builtins.c create mode 100644 src/resolver/scripting/simple/builtins.h create mode 100644 src/resolver/scripting/simple/call.c create mode 100644 src/resolver/scripting/simple/call.h create mode 100644 src/resolver/scripting/simple/simple-parser-api.h create mode 100644 src/resolver/scripting/simple/simple-parser.y create mode 100644 src/resolver/scripting/simple/simple-scanner.h create mode 100644 src/resolver/scripting/simple/simple-scanner.l create mode 100644 src/resolver/scripting/simple/simple-script.c create mode 100644 src/resolver/scripting/simple/simple-script.h create mode 100644 src/resolver/scripting/simple/token.h create mode 100644 src/resolver/target-sorter.c create mode 100644 src/resolver/target-sorter.h create mode 100644 src/resolver/target.c create mode 100644 src/resolver/target.h create mode 100644 src/resolver/test-input create mode 100644 src/resolver/test-input-audio create mode 100644 src/resolver/test-input-video create mode 100644 src/resolver/tests/Makefile.am create mode 100644 src/resolver/tests/parser-test.c create mode 100644 src/resolver/tests/test create mode 100644 src/resolver/token.h create mode 100644 src/resource/Makefile create mode 100644 src/resource/application-class.c create mode 100644 src/resource/application-class.h create mode 100644 src/resource/attribute.c create mode 100644 src/resource/attribute.h create mode 100644 src/resource/client-api.h create mode 100644 src/resource/common-api.h create mode 100644 src/resource/config-api.h create mode 100644 src/resource/config-lua.c create mode 100644 src/resource/config-lua.h create mode 100644 src/resource/data-types.h create mode 100644 src/resource/lua-resource.c create mode 100644 src/resource/manager-api.h create mode 100644 src/resource/protocol.h create mode 100644 src/resource/resource-client.c create mode 100644 src/resource/resource-client.h create mode 100644 src/resource/resource-lua.c create mode 100644 src/resource/resource-lua.h create mode 100644 src/resource/resource-owner.c create mode 100644 src/resource/resource-owner.h create mode 100644 src/resource/resource-set.c create mode 100644 src/resource/resource-set.h create mode 100644 src/resource/resource.c create mode 100644 src/resource/resource.h create mode 100644 src/resource/zone.c create mode 100644 src/resource/zone.h create mode 100644 utils/Makefile.am create mode 100644 utils/collect-symbols.c diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..4ba079b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,4 @@ +Janos Kovacs +Jaska Uimonen +Ismo Puustinen +Krisztian Litkey diff --git a/COPYING b/COPYING new file mode 100644 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 index 0000000..8d1c8b6 --- /dev/null +++ b/ChangeLog @@ -0,0 +1 @@ + diff --git a/LICENSE-BSD b/LICENSE-BSD new file mode 100644 index 0000000..a52ad55 --- /dev/null +++ b/LICENSE-BSD @@ -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 index 0000000..657bda0 --- /dev/null +++ b/Makefile.am @@ -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 index 0000000..8d1c8b6 --- /dev/null +++ b/NEWS @@ -0,0 +1 @@ + diff --git a/README b/README new file mode 100644 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 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 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 index 0000000..79b98db --- /dev/null +++ b/build-aux/enlicense @@ -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 Use file to obtain license text." + echo " --git|-g Add license only to files in the repository." + echo " --exclude|-e Exclude files matching egrep pattern ." + 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 index 0000000..3bb4ed5 --- /dev/null +++ b/build-aux/gen-linkedin-loader @@ -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 -o , or" + info "usage: $0 -o -d " + 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 index 0000000..8ac31d4 --- /dev/null +++ b/build-aux/gen-linker-script @@ -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 index 0000000..8ac31d4 --- /dev/null +++ b/build-aux/gen-linker-script.collect-symbols @@ -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 index 0000000..ca2272d --- /dev/null +++ b/build-aux/gen-linker-script.ctags @@ -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 ] [-I ] -o " + 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 index 0000000..f38082d --- /dev/null +++ b/build-aux/git-version-gen @@ -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 index 0000000..0f7fb12 --- /dev/null +++ b/build-aux/shave-libtool.in @@ -0,0 +1,109 @@ +#!/bin/sh +# +# Copyright (c) 2009, Damien Lespiau +# +# 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 index 0000000..5150a4f --- /dev/null +++ b/build-aux/shave.in @@ -0,0 +1,133 @@ +#!/bin/sh +# +# Copyright (c) 2009, Damien Lespiau +# +# 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 index 0000000..0327584 --- /dev/null +++ b/configure.ac @@ -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_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= 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= 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 index 0000000..4287cd2 --- /dev/null +++ b/doc/CODING-STYLE @@ -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 index 0000000..69e6854 --- /dev/null +++ b/doc/Makefile.am @@ -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 index 0000000..c53f415 --- /dev/null +++ b/doc/Makefile.rules @@ -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 index 0000000..b3f48b4 --- /dev/null +++ b/doc/common/figures/db-layers.svg @@ -0,0 +1,608 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + Murphy-DB + Murphy Plugin + MQLMurphy Query Language + MQIMurphy Query Interface + Temporary Data BackendMDEMemory Database Engine + Persistent Data BackendSQLite + + + High levelAPI + Low levelAPI + + diff --git a/doc/plugin-developer-guide/Makefile.am b/doc/plugin-developer-guide/Makefile.am new file mode 100644 index 0000000..97a9a4c --- /dev/null +++ b/doc/plugin-developer-guide/Makefile.am @@ -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 index 0000000..e621a7c --- /dev/null +++ b/doc/plugin-developer-guide/db/Makefile.am @@ -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 index 0000000..74631c9 --- /dev/null +++ b/doc/plugin-developer-guide/doxml/Doxyfile @@ -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 , where is the value of +# the FILE_VERSION_FILTER tag, and 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 , where +# is the value of the INPUT_FILTER tag, and 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 +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +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 index 0000000..cce58a5 --- /dev/null +++ b/doc/plugin-developer-guide/doxml/Makefile.am @@ -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 index 0000000..6824497 --- /dev/null +++ b/doc/plugin-developer-guide/lyx/murphy-db-high-level-api.lyx @@ -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 index 0000000..6a5d8d1 --- /dev/null +++ b/doc/plugin-developer-guide/lyx/murphy-db-introduction.lyx @@ -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 index 0000000..9e588cd --- /dev/null +++ b/doc/plugin-developer-guide/lyx/murphy-db-query-language.lyx @@ -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 index 0000000..9d7af9e --- /dev/null +++ b/doc/plugin-developer-guide/lyx/plugin-developer-guide.lyx @@ -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 index 0000000..993b58e --- /dev/null +++ b/doc/plugins/resource-dbus/dbus-api-resource.txt @@ -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 index 0000000..2e6dbbf --- /dev/null +++ b/doc/plugins/resource-dbus/resource-client.py @@ -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 +changeSetClass +acquireSet +releaseSet +showSet + +createResource + +deleteResource +changeResource +showResource + +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 index 0000000..8691ef1 --- /dev/null +++ b/doc/scripts/abnf.py @@ -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 = "" + 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 "\n" % \ + (lname, yname) + +print "" +print_abnf_rules() +print "" + + + + diff --git a/doc/scripts/dblyxfix.py b/doc/scripts/dblyxfix.py new file mode 100755 index 0000000..b162094 --- /dev/null +++ b/doc/scripts/dblyxfix.py @@ -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 += "" % \ + (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 += "" % \ + (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 = "" + +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("") +graph_pattern = re.compile("") + +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 index 0000000..ab33657 --- /dev/null +++ b/doc/scripts/doxml2db.py @@ -0,0 +1,915 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" + Doxygen XML to DocBook converter + ================================ + + Usage: doxml2db.py [OPTIONS] {|--} + + - is the directory where the doxml files are + - list of one or more whitespace separated doxml input files + - the output file + -- - docbook is printed to stdout + + OPTIONS + -h or --help - print this help message + --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 = {"'" : ''', + '"' : '"', + '&' : '&' , + '<' : '<' , + '>' : '>' } + + 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 index 0000000..0dc0b02 --- /dev/null +++ b/doc/scripts/doxydeps.py @@ -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 index 0000000..8836d79 --- /dev/null +++ b/githooks/commit-msg @@ -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 index 0000000..a23bc51 --- /dev/null +++ b/githooks/post-rewrite @@ -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 index 0000000..df133c5 --- /dev/null +++ b/githooks/pre-commit @@ -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 index 0000000..1fe53bf --- /dev/null +++ b/githooks/pre-rebase @@ -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 index 0000000..7af4381 --- /dev/null +++ b/m4/docsetup.m4 @@ -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 index 0000000..94aec1f --- /dev/null +++ b/m4/shave.m4 @@ -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 index 0000000..ab1a41d --- /dev/null +++ b/m4/websockets.m4 @@ -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 index 0000000..a348679 --- /dev/null +++ b/packaging.in/murphy-lua.conf @@ -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 index 0000000..d943911 --- /dev/null +++ b/packaging.in/murphy-wait-for-launchpad-ready.path @@ -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 index 0000000..b625d1f --- /dev/null +++ b/packaging.in/murphy.lua @@ -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 index 0000000..445452b --- /dev/null +++ b/packaging.in/murphy.spec.in @@ -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 index 0000000..75395b7 --- /dev/null +++ b/packaging.in/murphyd.conf @@ -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 index 0000000..d19b931 --- /dev/null +++ b/packaging.in/murphyd.init @@ -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 index 0000000..8b6fda2 --- /dev/null +++ b/packaging.in/murphyd.service @@ -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 index 0000000..fa6ac40 --- /dev/null +++ b/packaging.in/org.Murphy.conf.in @@ -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 index 0000000..86dbb26 --- /dev/null +++ b/packaging/murphy.manifest @@ -0,0 +1,5 @@ +<manifest> + <request> + <domain name="_" /> + </request> +</manifest> diff --git a/packaging/murphy.spec b/packaging/murphy.spec new file mode 100644 index 0000000..4cf6cc9 --- /dev/null +++ b/packaging/murphy.spec @@ -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 index 0000000..3b8e35d --- /dev/null +++ b/patches/lua/lua-5.1.4-specfile.patch @@ -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 index 0000000..0bf3bee --- /dev/null +++ b/patches/lua/lua-5.2.2-specfile.patch @@ -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 index 0000000..bfab741 --- /dev/null +++ b/scripts/cleanup-whitespace.sh @@ -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 index 0000000..a9a5d9c --- /dev/null +++ b/src/Makefile.am @@ -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 index 0000000..f70109e --- /dev/null +++ b/src/breedline/LICENSE-BSD @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/breedline/Makefile @@ -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 index 0000000..8ce81ff --- /dev/null +++ b/src/breedline/README @@ -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 index 0000000..1362934 --- /dev/null +++ b/src/breedline/breedline-glib.c @@ -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 index 0000000..375d2e1 --- /dev/null +++ b/src/breedline/breedline-glib.h @@ -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 index 0000000..a10b1d4 --- /dev/null +++ b/src/breedline/breedline-glib.pc.in @@ -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 index 0000000..3e9c308 --- /dev/null +++ b/src/breedline/breedline-murphy.c @@ -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 index 0000000..7e764dd --- /dev/null +++ b/src/breedline/breedline-murphy.h @@ -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 index 0000000..e017173 --- /dev/null +++ b/src/breedline/breedline-murphy.pc.in @@ -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 index 0000000..3169e0d --- /dev/null +++ b/src/breedline/breedline.c @@ -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 index 0000000..44378e8 --- /dev/null +++ b/src/breedline/breedline.h @@ -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 index 0000000..ab9a02a --- /dev/null +++ b/src/breedline/breedline.pc.in @@ -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 index 0000000..894c262 --- /dev/null +++ b/src/breedline/macros.h @@ -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 index 0000000..6337659 --- /dev/null +++ b/src/breedline/mm.h @@ -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 index 0000000..90b2b32 --- /dev/null +++ b/src/breedline/tests/Makefile.am @@ -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 index 0000000..71ad7bd --- /dev/null +++ b/src/breedline/tests/breedline-glib-test.c @@ -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 index 0000000..bf82973 --- /dev/null +++ b/src/breedline/tests/breedline-murphy-test.c @@ -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 index 0000000..ad2081f --- /dev/null +++ b/src/breedline/tests/breedline-test.c @@ -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 index 0000000..dc7af31 --- /dev/null +++ b/src/common.h @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/common/Makefile @@ -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 index 0000000..1704cec --- /dev/null +++ b/src/common/dbus-error.h @@ -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 index 0000000..d1bb093 --- /dev/null +++ b/src/common/dbus-libdbus-glue.c @@ -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 index 0000000..e19dac8 --- /dev/null +++ b/src/common/dbus-libdbus-transport.c @@ -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 index 0000000..6dcf9fa --- /dev/null +++ b/src/common/dbus-libdbus.c @@ -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 index 0000000..8c729d9 --- /dev/null +++ b/src/common/dbus-libdbus.h @@ -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 index 0000000..a2b98c6 --- /dev/null +++ b/src/common/dbus-sdbus-glue.c @@ -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 index 0000000..3b62ac3 --- /dev/null +++ b/src/common/dbus-sdbus-transport.c @@ -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 index 0000000..a2fb9e9 --- /dev/null +++ b/src/common/dbus-sdbus.c @@ -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 index 0000000..982b0f6 --- /dev/null +++ b/src/common/dbus-sdbus.h @@ -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 index 0000000..dda1c8e --- /dev/null +++ b/src/common/dbus-transport.c @@ -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 index 0000000..77b5335 --- /dev/null +++ b/src/common/dbus-transport.h @@ -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 index 0000000..974e343 --- /dev/null +++ b/src/common/debug-auto-register.c @@ -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 index 0000000..970d4c9 --- /dev/null +++ b/src/common/debug-info.h @@ -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 index 0000000..9f5e091 --- /dev/null +++ b/src/common/debug.c @@ -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 index 0000000..3828402 --- /dev/null +++ b/src/common/debug.h @@ -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 index 0000000..b54ed62 --- /dev/null +++ b/src/common/dgram-transport.c @@ -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 index 0000000..8ad6238 --- /dev/null +++ b/src/common/ecore-glue.c @@ -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 index 0000000..ebf53a6 --- /dev/null +++ b/src/common/ecore-glue.h @@ -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 index 0000000..4ea8bad --- /dev/null +++ b/src/common/env.c @@ -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 index 0000000..1a739e8 --- /dev/null +++ b/src/common/env.h @@ -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 index 0000000..ce78c80 --- /dev/null +++ b/src/common/file-utils.c @@ -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(®exp, pattern, REG_EXTENDED | REG_NOSUB) != 0) { + closedir(dp); + return FALSE; + } + } + + stop = FALSE; + while ((de = readdir(dp)) != NULL && !stop) { + if (pattern != NULL && regexec(®exp, 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(®exp); + + 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 index 0000000..dd7266c --- /dev/null +++ b/src/common/file-utils.h @@ -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 index 0000000..740e138 --- /dev/null +++ b/src/common/fragbuf.c @@ -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 index 0000000..6e2ecc6 --- /dev/null +++ b/src/common/fragbuf.h @@ -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 index 0000000..2df2ea7 --- /dev/null +++ b/src/common/glib-glue.c @@ -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 index 0000000..88fef45 --- /dev/null +++ b/src/common/glib-glue.h @@ -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 index 0000000..3f49d83 --- /dev/null +++ b/src/common/hashtbl.c @@ -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 index 0000000..6dd7c05 --- /dev/null +++ b/src/common/hashtbl.h @@ -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 index 0000000..7ffae37 --- /dev/null +++ b/src/common/internal-transport.c @@ -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 index 0000000..c275330 --- /dev/null +++ b/src/common/json.c @@ -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 index 0000000..746a6ef --- /dev/null +++ b/src/common/json.h @@ -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 index 0000000..d1bb093 --- /dev/null +++ b/src/common/libdbus-glue.c @@ -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 index 0000000..a4c7a24 --- /dev/null +++ b/src/common/libdbus.c @@ -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 index 0000000..84f29b7 --- /dev/null +++ b/src/common/libdbus.h @@ -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 index 0000000..16c0ae4 --- /dev/null +++ b/src/common/list.h @@ -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 index 0000000..e35c43a --- /dev/null +++ b/src/common/log.c @@ -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 index 0000000..61e69c5 --- /dev/null +++ b/src/common/log.h @@ -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 index 0000000..1fde94a --- /dev/null +++ b/src/common/macros.h @@ -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 index 0000000..5702c15 --- /dev/null +++ b/src/common/mainloop.c @@ -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 index 0000000..4e7eee2 --- /dev/null +++ b/src/common/mainloop.h @@ -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 index 0000000..12ed336 --- /dev/null +++ b/src/common/mask.h @@ -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 index 0000000..b461241 --- /dev/null +++ b/src/common/mm.c @@ -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 index 0000000..73ffacb --- /dev/null +++ b/src/common/mm.h @@ -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 index 0000000..2db8214 --- /dev/null +++ b/src/common/msg.c @@ -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 index 0000000..e61805b --- /dev/null +++ b/src/common/msg.h @@ -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 index 0000000..0b75762 --- /dev/null +++ b/src/common/murphy-common.pc.in @@ -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 index 0000000..d6efb6f --- /dev/null +++ b/src/common/murphy-dbus-libdbus.pc.in @@ -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 index 0000000..94008ee --- /dev/null +++ b/src/common/murphy-dbus-sdbus.pc.in @@ -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 index 0000000..626a07b --- /dev/null +++ b/src/common/murphy-ecore.pc.in @@ -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 index 0000000..5193219 --- /dev/null +++ b/src/common/murphy-glib.pc.in @@ -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 index 0000000..bcda7b4 --- /dev/null +++ b/src/common/murphy-libdbus.pc.in @@ -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 index 0000000..2655307 --- /dev/null +++ b/src/common/murphy-pulse.pc.in @@ -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 index 0000000..95c6aba --- /dev/null +++ b/src/common/murphy-qt.pc.in @@ -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 index 0000000..d9890e3 --- /dev/null +++ b/src/common/native-types.c @@ -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 index 0000000..ac6dacf --- /dev/null +++ b/src/common/native-types.h @@ -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 index 0000000..71a10e1 --- /dev/null +++ b/src/common/process.c @@ -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 index 0000000..956a367 --- /dev/null +++ b/src/common/process.h @@ -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 index 0000000..98dbf24 --- /dev/null +++ b/src/common/pulse-glue.c @@ -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 index 0000000..24fa3e8 --- /dev/null +++ b/src/common/pulse-glue.h @@ -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 index 0000000..7e08b6c --- /dev/null +++ b/src/common/pulse-subloop.c @@ -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 index 0000000..f2fbe17 --- /dev/null +++ b/src/common/pulse-subloop.h @@ -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 index 0000000..cb31531 --- /dev/null +++ b/src/common/qt-glue-priv.h @@ -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 index 0000000..8fbf636 --- /dev/null +++ b/src/common/qt-glue.cpp @@ -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 index 0000000..8b2bda5 --- /dev/null +++ b/src/common/qt-glue.h @@ -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 index 0000000..dbaf8c2 --- /dev/null +++ b/src/common/refcnt.h @@ -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 index 0000000..4f7d957 --- /dev/null +++ b/src/common/socket-utils.c @@ -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 index 0000000..ad1b998 --- /dev/null +++ b/src/common/socket-utils.h @@ -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 index 0000000..d8ee1e6 --- /dev/null +++ b/src/common/stream-transport.c @@ -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 index 0000000..0162fad --- /dev/null +++ b/src/common/tests/Makefile.am @@ -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 index 0000000..4dc323d --- /dev/null +++ b/src/common/tests/dbus-pump.c @@ -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 index 0000000..a7fcb86 --- /dev/null +++ b/src/common/tests/dbus-sdbus-test.c @@ -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 index 0000000..e0ab062 --- /dev/null +++ b/src/common/tests/dbus-test.c @@ -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 index 0000000..4da9a34 --- /dev/null +++ b/src/common/tests/fragbuf-test.c @@ -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 index 0000000..ece07ff --- /dev/null +++ b/src/common/tests/glib-pump.c @@ -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 index 0000000..6195775 --- /dev/null +++ b/src/common/tests/hash-test.c @@ -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 index 0000000..667c212 --- /dev/null +++ b/src/common/tests/hash12-test.c @@ -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 index 0000000..e4fa131 --- /dev/null +++ b/src/common/tests/internal-transport-test.c @@ -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 index 0000000..1f68bc4 --- /dev/null +++ b/src/common/tests/libdbus-test.c @@ -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 index 0000000..06c2320 --- /dev/null +++ b/src/common/tests/libdbus-transport-test.c @@ -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 index 0000000..d094fac --- /dev/null +++ b/src/common/tests/mainloop-ecore-test.c @@ -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 index 0000000..a13741e --- /dev/null +++ b/src/common/tests/mainloop-glib-test.c @@ -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 index 0000000..58945dc --- /dev/null +++ b/src/common/tests/mainloop-pulse-test.c @@ -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 index 0000000..4c047a6 --- /dev/null +++ b/src/common/tests/mainloop-qt-test.cpp @@ -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 index 0000000..cfa3f3e --- /dev/null +++ b/src/common/tests/mainloop-qt-test.h @@ -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 index 0000000..77dea36 --- /dev/null +++ b/src/common/tests/mainloop-test.c @@ -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 index 0000000..e4db6d0 --- /dev/null +++ b/src/common/tests/mask-test.c @@ -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 index 0000000..611b82a --- /dev/null +++ b/src/common/tests/mkdir-test.c @@ -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 index 0000000..c22b866 --- /dev/null +++ b/src/common/tests/mm-test.c @@ -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 index 0000000..44c5cd2 --- /dev/null +++ b/src/common/tests/msg-test.c @@ -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 index 0000000..171f5a4 --- /dev/null +++ b/src/common/tests/native-test.c @@ -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 index 0000000..c2b0918 --- /dev/null +++ b/src/common/tests/path-test.c @@ -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 index 0000000..41a55e6 --- /dev/null +++ b/src/common/tests/process-test.c @@ -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 index 0000000..1e4b9f2 --- /dev/null +++ b/src/common/tests/sdbus-error-message.c @@ -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 index 0000000..f785139 --- /dev/null +++ b/src/common/tests/sdbus-test.c @@ -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 index 0000000..e5797a0 --- /dev/null +++ b/src/common/tests/transport-test.c @@ -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 index 0000000..fd216a3 --- /dev/null +++ b/src/common/tlv.c @@ -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 index 0000000..b746546 --- /dev/null +++ b/src/common/tlv.h @@ -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 index 0000000..d00c588 --- /dev/null +++ b/src/common/transport.c @@ -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 index 0000000..7b57a2c --- /dev/null +++ b/src/common/transport.h @@ -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 index 0000000..8fd1f76 --- /dev/null +++ b/src/common/utils.c @@ -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 index 0000000..fef28a8 --- /dev/null +++ b/src/common/utils.h @@ -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 index 0000000..25d3dc8 --- /dev/null +++ b/src/common/websocket.c @@ -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 index 0000000..cd380f2 --- /dev/null +++ b/src/common/websocket.h @@ -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 index 0000000..0595c46 --- /dev/null +++ b/src/common/websocklib.c @@ -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 index 0000000..83dbb95 --- /dev/null +++ b/src/common/websocklib.h @@ -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 index 0000000..fe44271 --- /dev/null +++ b/src/common/wsck-transport.c @@ -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 index 0000000..c35f29f --- /dev/null +++ b/src/common/wsck-transport.h @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/console-client/Makefile @@ -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 index 0000000..1346691 --- /dev/null +++ b/src/console-client/client.c @@ -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 index 0000000..d178d17 --- /dev/null +++ b/src/core.h @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/core/Makefile @@ -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 index 0000000..ecba7f1 --- /dev/null +++ b/src/core/auth-deny.c @@ -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 index 0000000..d549073 --- /dev/null +++ b/src/core/auth-smack.c @@ -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 index 0000000..5e310cc --- /dev/null +++ b/src/core/auth.c @@ -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 index 0000000..4eb712d --- /dev/null +++ b/src/core/auth.h @@ -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 index 0000000..5dfdfef --- /dev/null +++ b/src/core/console-builtin.c @@ -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 index 0000000..28317eb --- /dev/null +++ b/src/core/console-command.c @@ -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 index 0000000..9ef1732 --- /dev/null +++ b/src/core/console-command.h @@ -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 index 0000000..384a13c --- /dev/null +++ b/src/core/console-db.c @@ -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 index 0000000..0ec9040 --- /dev/null +++ b/src/core/console-debug.c @@ -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 index 0000000..9fd5294 --- /dev/null +++ b/src/core/console-log.c @@ -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 index 0000000..ac20de5 --- /dev/null +++ b/src/core/console-priv.h @@ -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 index 0000000..a9591b1 --- /dev/null +++ b/src/core/console.c @@ -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 index 0000000..8cf9243 --- /dev/null +++ b/src/core/console.h @@ -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 index 0000000..fd01cce --- /dev/null +++ b/src/core/context.c @@ -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 index 0000000..97725f0 --- /dev/null +++ b/src/core/context.h @@ -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 index 0000000..5064078 --- /dev/null +++ b/src/core/domain-types.h @@ -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 index 0000000..75df352 --- /dev/null +++ b/src/core/domain.c @@ -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 index 0000000..18358f6 --- /dev/null +++ b/src/core/domain.h @@ -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 index 0000000..2e148b1 --- /dev/null +++ b/src/core/event.c @@ -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 index 0000000..389c890 --- /dev/null +++ b/src/core/event.h @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/core/lua-bindings/Makefile @@ -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 index 0000000..0f40705 --- /dev/null +++ b/src/core/lua-bindings/lua-bitwise.c @@ -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 index 0000000..ef7e5e0 --- /dev/null +++ b/src/core/lua-bindings/lua-console.c @@ -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 index 0000000..9e0f3f1 --- /dev/null +++ b/src/core/lua-bindings/lua-deferred.c @@ -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 index 0000000..5190186 --- /dev/null +++ b/src/core/lua-bindings/lua-env.c @@ -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 index 0000000..0a9902f --- /dev/null +++ b/src/core/lua-bindings/lua-event.c @@ -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 index 0000000..2dd8f17 --- /dev/null +++ b/src/core/lua-bindings/lua-json.c @@ -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 index 0000000..54363af --- /dev/null +++ b/src/core/lua-bindings/lua-json.h @@ -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 index 0000000..89ddb50 --- /dev/null +++ b/src/core/lua-bindings/lua-log.c @@ -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 index 0000000..67720d0 --- /dev/null +++ b/src/core/lua-bindings/lua-lua.c @@ -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 index 0000000..5121b9f --- /dev/null +++ b/src/core/lua-bindings/lua-murphy.c @@ -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 index 0000000..6604d81 --- /dev/null +++ b/src/core/lua-bindings/lua-plugin.c @@ -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 index 0000000..be1caa1 --- /dev/null +++ b/src/core/lua-bindings/lua-sighandler.c @@ -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 index 0000000..eb87494 --- /dev/null +++ b/src/core/lua-bindings/lua-timer.c @@ -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 index 0000000..605f0fc --- /dev/null +++ b/src/core/lua-bindings/lua-transport.c @@ -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 index 0000000..42b5263 --- /dev/null +++ b/src/core/lua-bindings/murphy.h @@ -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 index 0000000..cfeca66 --- /dev/null +++ b/src/core/lua-decision/Makefile @@ -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 index 0000000..ab85f87 --- /dev/null +++ b/src/core/lua-decision/element.c @@ -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 index 0000000..5735dbf --- /dev/null +++ b/src/core/lua-decision/element.h @@ -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 index 0000000..378a791 --- /dev/null +++ b/src/core/lua-decision/mdb.c @@ -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 index 0000000..86c5bd7 --- /dev/null +++ b/src/core/lua-decision/mdb.h @@ -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 index 0000000..0ebfb88 --- /dev/null +++ b/src/core/lua-decision/murphy-lua-decision.pc.in @@ -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 index 0000000..b3ddeb2 --- /dev/null +++ b/src/core/lua-decision/tests/Makefile.am @@ -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 index 0000000..983b2be --- /dev/null +++ b/src/core/lua-decision/tests/decision-test.c @@ -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 index 0000000..e64b73e --- /dev/null +++ b/src/core/lua-decision/tests/decision-test.lua @@ -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 index 0000000..cfeca66 --- /dev/null +++ b/src/core/lua-utils/Makefile @@ -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 index 0000000..87d17bd --- /dev/null +++ b/src/core/lua-utils/error.c @@ -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 index 0000000..ea5678a --- /dev/null +++ b/src/core/lua-utils/error.h @@ -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 index 0000000..0e594c4 --- /dev/null +++ b/src/core/lua-utils/funcbridge.c @@ -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 index 0000000..e81da0a --- /dev/null +++ b/src/core/lua-utils/funcbridge.h @@ -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 index 0000000..143bec1 --- /dev/null +++ b/src/core/lua-utils/include.c @@ -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 index 0000000..5a30c4e --- /dev/null +++ b/src/core/lua-utils/include.h @@ -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 index 0000000..0fbc83d --- /dev/null +++ b/src/core/lua-utils/lua-utils.c @@ -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 index 0000000..33f36de --- /dev/null +++ b/src/core/lua-utils/lua-utils.h @@ -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 index 0000000..aed5208 --- /dev/null +++ b/src/core/lua-utils/murphy-lua-utils.pc.in @@ -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 index 0000000..c0bc029 --- /dev/null +++ b/src/core/lua-utils/object.c @@ -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 index 0000000..3838e1b --- /dev/null +++ b/src/core/lua-utils/object.h @@ -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 index 0000000..6f0b2d1 --- /dev/null +++ b/src/core/lua-utils/strarray.c @@ -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 index 0000000..b989626 --- /dev/null +++ b/src/core/lua-utils/strarray.h @@ -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 index 0000000..664af2c --- /dev/null +++ b/src/core/method.c @@ -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 index 0000000..504cc4f --- /dev/null +++ b/src/core/method.h @@ -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 index 0000000..cbb1926 --- /dev/null +++ b/src/core/murphy-core.pc.in @@ -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 index 0000000..3606216 --- /dev/null +++ b/src/core/plugin.c @@ -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 index 0000000..a7e569c --- /dev/null +++ b/src/core/plugin.h @@ -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 index 0000000..c99b6c9 --- /dev/null +++ b/src/core/scripting.c @@ -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 index 0000000..6b85ebf --- /dev/null +++ b/src/core/scripting.h @@ -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 index 0000000..5d9e084 --- /dev/null +++ b/src/core/tests/Makefile.am @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/daemon/Makefile @@ -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 index 0000000..7691e2a --- /dev/null +++ b/src/daemon/config.c @@ -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 index 0000000..365acca --- /dev/null +++ b/src/daemon/config.h @@ -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 index 0000000..65dee9b --- /dev/null +++ b/src/daemon/daemon.c @@ -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 index 0000000..20c0d97 --- /dev/null +++ b/src/daemon/daemon.h @@ -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 index 0000000..39d21cf --- /dev/null +++ b/src/daemon/murphy-lua.conf @@ -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 index 0000000..60b46cb --- /dev/null +++ b/src/daemon/murphy.conf @@ -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 index 0000000..d829682 --- /dev/null +++ b/src/daemon/murphy.lua @@ -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 index 0000000..9866992 --- /dev/null +++ b/src/daemon/sample-config/cgroup-test.rules @@ -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 index 0000000..a8e88ab --- /dev/null +++ b/src/daemon/sample-config/common.cfg @@ -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 index 0000000..5cd02e2 --- /dev/null +++ b/src/daemon/sample-config/console.cfg @@ -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 index 0000000..6183a02 --- /dev/null +++ b/src/daemon/sample-config/domain-control.cfg @@ -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 index 0000000..c514988 --- /dev/null +++ b/src/daemon/sample-config/glib.cfg @@ -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 index 0000000..2419396 --- /dev/null +++ b/src/daemon/sample-config/main.cfg @@ -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 index 0000000..dcdb9aa --- /dev/null +++ b/src/daemon/sample-config/murphy.cfg @@ -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 index 0000000..07b6890 --- /dev/null +++ b/src/daemon/sample-config/resource.cfg @@ -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 index 0000000..3038ff2 --- /dev/null +++ b/src/daemon/sample-config/speed-volume.rules @@ -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 index 0000000..d575ebb --- /dev/null +++ b/src/daemon/sample-config/system-controller.cfg @@ -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 index 0000000..266a94f --- /dev/null +++ b/src/daemon/sample-config/system-monitor.cfg @@ -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 index 0000000..1e69662 --- /dev/null +++ b/src/daemon/sample-config/system-monitor.rules @@ -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 index 0000000..e21409d --- /dev/null +++ b/src/daemon/sample-config/systemd.cfg @@ -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 index 0000000..81b8ade --- /dev/null +++ b/src/daemon/sample-config/timer-test.rules @@ -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 index 0000000..b47500b --- /dev/null +++ b/src/daemon/tests/Makefile.am @@ -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 index 0000000..38d4a94 --- /dev/null +++ b/src/murphy-db/Makefile.am @@ -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 index 0000000..9d20679 --- /dev/null +++ b/src/murphy-db/include/Makefile.am @@ -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 index 0000000..e3afc79 --- /dev/null +++ b/src/murphy-db/include/murphy-db/assert.h @@ -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 index 0000000..3a75ce6 --- /dev/null +++ b/src/murphy-db/include/murphy-db/handle.h @@ -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 index 0000000..2c35e3c --- /dev/null +++ b/src/murphy-db/include/murphy-db/hash.h @@ -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 index 0000000..8c2668b --- /dev/null +++ b/src/murphy-db/include/murphy-db/list.h @@ -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 index 0000000..1b53a41 --- /dev/null +++ b/src/murphy-db/include/murphy-db/mdb.h @@ -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 index 0000000..8ad595e --- /dev/null +++ b/src/murphy-db/include/murphy-db/mqi-types.h @@ -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 index 0000000..7d165a5 --- /dev/null +++ b/src/murphy-db/include/murphy-db/mqi.h @@ -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 index 0000000..e0bf10d --- /dev/null +++ b/src/murphy-db/include/murphy-db/mql-result.h @@ -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 index 0000000..3e843bf --- /dev/null +++ b/src/murphy-db/include/murphy-db/mql-statement.h @@ -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 index 0000000..19c4a76 --- /dev/null +++ b/src/murphy-db/include/murphy-db/mql-trigger.h @@ -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 index 0000000..4809dcc --- /dev/null +++ b/src/murphy-db/include/murphy-db/mql.h @@ -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 index 0000000..3929b72 --- /dev/null +++ b/src/murphy-db/include/murphy-db/sequence.h @@ -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 index 0000000..a53c763 --- /dev/null +++ b/src/murphy-db/mdb/Makefile.am @@ -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 index 0000000..9553a9b --- /dev/null +++ b/src/murphy-db/mdb/column.c @@ -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 = ∅ + + 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 index 0000000..5d9dda7 --- /dev/null +++ b/src/murphy-db/mdb/column.h @@ -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 index 0000000..5d7450f --- /dev/null +++ b/src/murphy-db/mdb/cond.c @@ -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 index 0000000..82462a5 --- /dev/null +++ b/src/murphy-db/mdb/cond.h @@ -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 index 0000000..04d0479 --- /dev/null +++ b/src/murphy-db/mdb/handle.c @@ -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 index 0000000..89ad450 --- /dev/null +++ b/src/murphy-db/mdb/hash.c @@ -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 index 0000000..6940378 --- /dev/null +++ b/src/murphy-db/mdb/index.c @@ -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 index 0000000..3bdf930 --- /dev/null +++ b/src/murphy-db/mdb/index.h @@ -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 index 0000000..815575b --- /dev/null +++ b/src/murphy-db/mdb/log.c @@ -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 index 0000000..e40fc85 --- /dev/null +++ b/src/murphy-db/mdb/log.h @@ -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 index 0000000..2496102 --- /dev/null +++ b/src/murphy-db/mdb/mqi-types.c @@ -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 index 0000000..6d5e0a2 --- /dev/null +++ b/src/murphy-db/mdb/row.c @@ -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 index 0000000..1018ad1 --- /dev/null +++ b/src/murphy-db/mdb/row.h @@ -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 index 0000000..e69e269 --- /dev/null +++ b/src/murphy-db/mdb/sequence.c @@ -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 index 0000000..427c70b --- /dev/null +++ b/src/murphy-db/mdb/table.c @@ -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 index 0000000..a806795 --- /dev/null +++ b/src/murphy-db/mdb/table.h @@ -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 index 0000000..9b0a8b7 --- /dev/null +++ b/src/murphy-db/mdb/transaction.c @@ -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 index 0000000..c59fd83 --- /dev/null +++ b/src/murphy-db/mdb/transaction.h @@ -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 index 0000000..a8eaf41 --- /dev/null +++ b/src/murphy-db/mdb/trigger.c @@ -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 index 0000000..887d698 --- /dev/null +++ b/src/murphy-db/mdb/trigger.h @@ -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 index 0000000..99e848b --- /dev/null +++ b/src/murphy-db/mqi/Makefile.am @@ -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 index 0000000..f5ff656 --- /dev/null +++ b/src/murphy-db/mqi/db.h @@ -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 index 0000000..9fc0ea6 --- /dev/null +++ b/src/murphy-db/mqi/mdb-backend.c @@ -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 index 0000000..e16db3b --- /dev/null +++ b/src/murphy-db/mqi/mdb-backend.h @@ -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 index 0000000..34cb75c --- /dev/null +++ b/src/murphy-db/mqi/mqi.c @@ -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 index 0000000..506ea23 --- /dev/null +++ b/src/murphy-db/mql/Makefile.am @@ -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 index 0000000..e598fd5 --- /dev/null +++ b/src/murphy-db/mql/mql-parser.y @@ -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 index 0000000..3e19650 --- /dev/null +++ b/src/murphy-db/mql/mql-scanner.l @@ -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 index 0000000..fb19d29 --- /dev/null +++ b/src/murphy-db/mql/result.c @@ -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 index 0000000..d7533ec --- /dev/null +++ b/src/murphy-db/mql/statement.c @@ -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 index 0000000..da21802 --- /dev/null +++ b/src/murphy-db/mql/transaction.c @@ -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 index 0000000..a214397 --- /dev/null +++ b/src/murphy-db/mql/trigger.c @@ -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 index 0000000..f41cf24 --- /dev/null +++ b/src/murphy-db/murphy-db.pc.in @@ -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 index 0000000..96aab16 --- /dev/null +++ b/src/murphy-db/tests/Makefile.am @@ -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 index 0000000..f08ecdb --- /dev/null +++ b/src/murphy-db/tests/check-libmdb.c @@ -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 index 0000000..907094a --- /dev/null +++ b/src/murphy-db/tests/check-libmqi.c @@ -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 index 0000000..543c1f1 --- /dev/null +++ b/src/murphy-db/tests/check-libmql.c @@ -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 index 0000000..2c0a593 --- /dev/null +++ b/src/plugins/Makefile @@ -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 index 0000000..9d49cec --- /dev/null +++ b/src/plugins/console-protocol.h @@ -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 index 0000000..efba399 --- /dev/null +++ b/src/plugins/console/Makefile @@ -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 index 0000000..56c50f7 --- /dev/null +++ b/src/plugins/console/console.html @@ -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) + + + + + + + +
+ diff --git a/src/plugins/console/console.js b/src/plugins/console/console.js new file mode 100644 index 0000000..f3b6f4c --- /dev/null +++ b/src/plugins/console/console.js @@ -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 index 0000000..13fa207 --- /dev/null +++ b/src/plugins/console/plugin-console.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "config.h" + +#ifdef WEBSOCKETS_ENABLED +# include +# include +#endif + +#include + +#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 " + + +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 index 0000000..2c0a593 --- /dev/null +++ b/src/plugins/domain-control/Makefile @@ -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 index 0000000..6f8e096 --- /dev/null +++ b/src/plugins/domain-control/client.c @@ -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 +#include + +#include +#include +#include +#include + +#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.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 *)®); + + 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 index 0000000..5a90b6d --- /dev/null +++ b/src/plugins/domain-control/client.h @@ -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 +#include +#include +#include + +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 index 0000000..9177be8 --- /dev/null +++ b/src/plugins/domain-control/domain-control-api.js @@ -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 : ""; + 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 index 0000000..8fdaca5 --- /dev/null +++ b/src/plugins/domain-control/domain-control-test.html @@ -0,0 +1,224 @@ + +Domain Controller Webruntime Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Actions: + + + + +
Status:
disconnected
Audio:
unknown
Video:
unknown
Data:
unknown
+ + + + diff --git a/src/plugins/domain-control/domain-control-types.h b/src/plugins/domain-control/domain-control-types.h new file mode 100644 index 0000000..bc418bf --- /dev/null +++ b/src/plugins/domain-control/domain-control-types.h @@ -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 + +#include +#include +#include +#include +#include +#include + +#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 index 0000000..4a0f8fa --- /dev/null +++ b/src/plugins/domain-control/domain-control.c @@ -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 + +#include +#include +#include + +#include +#include + +#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 : ""; + + 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 : ""; + + 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 : ""; + 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 : ""; + + 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 : ""; + 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 index 0000000..476e573 --- /dev/null +++ b/src/plugins/domain-control/domain-control.h @@ -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 index 0000000..ed7472e --- /dev/null +++ b/src/plugins/domain-control/message.c @@ -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 +#include +#include + +#include + +#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 index 0000000..4f48eba --- /dev/null +++ b/src/plugins/domain-control/message.h @@ -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 +#include +#include + +#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 index 0000000..83f8b0f --- /dev/null +++ b/src/plugins/domain-control/murphy-domain-controller.pc.in @@ -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 index 0000000..e48fabd --- /dev/null +++ b/src/plugins/domain-control/notify.c @@ -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 +#include + +#include + +#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 index 0000000..e53cc46 --- /dev/null +++ b/src/plugins/domain-control/notify.h @@ -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 index 0000000..287844e --- /dev/null +++ b/src/plugins/domain-control/plugin-domain-control.c @@ -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 + +#include +#include + +#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 " + +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 index 0000000..4840717 --- /dev/null +++ b/src/plugins/domain-control/proxy.c @@ -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 + +#include +#include +#include + +#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 index 0000000..42e03e8 --- /dev/null +++ b/src/plugins/domain-control/proxy.h @@ -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 + +#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 index 0000000..6e7615a --- /dev/null +++ b/src/plugins/domain-control/table.c @@ -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 +#include + +#include +#include +#include +#include + +#include +#include +#include + +#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 : ""); + + 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 index 0000000..243cc22 --- /dev/null +++ b/src/plugins/domain-control/table.h @@ -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 + +#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 index 0000000..f801470 --- /dev/null +++ b/src/plugins/domain-control/test-client.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +#define _GNU_SOURCE +#include + +#include +#include + +#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: ", 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 update as plugged"); + P(" unplug update as unplugged"); + P(" play update as playing"); + P(" stop update as stopped"); + P(" call update state of "); + P(" zone [occupied,[active]] update state of "); + +#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", + 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
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 index 0000000..deaf69a --- /dev/null +++ b/src/plugins/plugin-dbus.c @@ -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 + +#include +#include +#include +#include +#include +#include + +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 " + +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 index 0000000..d87d6cf --- /dev/null +++ b/src/plugins/plugin-glib.c @@ -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 + +#include +#include +#include +#include +#include + + +/* + * 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 " + +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 index 0000000..9674498 --- /dev/null +++ b/src/plugins/plugin-lua.c @@ -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 +#include +#include +#include +#include + +#include +#include +#include + +#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, "")) { + 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 " +#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 index 0000000..2f23141 --- /dev/null +++ b/src/plugins/plugin-resource-dbus.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + + +#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 " + +/* 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 index 0000000..c47d66d --- /dev/null +++ b/src/plugins/plugin-systemd.c @@ -0,0 +1,67 @@ +#include +#include + +#include +#include + +#include +#include + +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 " + +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 index 0000000..3885c4f --- /dev/null +++ b/src/plugins/plugin-test.c @@ -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 + +#include +#include +#include +#include +#include +#include + + +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 ", "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 \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: \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: [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 " + +#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 index 0000000..2d4542c --- /dev/null +++ b/src/plugins/resource-dbus/org.Murphy.conf @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + diff --git a/src/plugins/resource-native/Makefile b/src/plugins/resource-native/Makefile new file mode 100644 index 0000000..cfeca66 --- /dev/null +++ b/src/plugins/resource-native/Makefile @@ -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 index 0000000..2e80915 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/api_test.c @@ -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 +#include +#include +#include + +#include + +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 index 0000000..afc9eeb --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/attribute.c @@ -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 + +#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 index 0000000..4036296 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/attribute.h @@ -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 + +#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 index 0000000..eb94836 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/context-create.c @@ -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 +#include +#include +#include + +#include + +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 \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 index 0000000..8ae161a --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/message.c @@ -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 + +#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 = ""; + 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 index 0000000..5b37212 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/message.h @@ -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 + +#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 index 0000000..cc17582 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/murphy-resource.pc.in @@ -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 index 0000000..f4dae1e --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/resource-api.h @@ -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 + +/* + * 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 + +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 index 0000000..22ab756 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/resource-fuzz.c @@ -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 +#include +#include +#include + +#include + +#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 [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 index 0000000..d5433f9 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/resource-log.c @@ -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 +#include + +#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 index 0000000..18ec44b --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/resource-private.h @@ -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 + +#include + +#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 index 0000000..57a18b0 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/resource.c @@ -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 + +#include +#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 index 0000000..1c788b8 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/rset.c @@ -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 + +#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(©->priv->hook, 0, sizeof(mrp_list_hook_t)); + mrp_list_init(©->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 index 0000000..3bee589 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/rset.h @@ -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 + +#include +#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 index 0000000..53f89e7 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/string_array.c @@ -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 + + +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 index 0000000..4becad4 --- /dev/null +++ b/src/plugins/resource-native/libmurphy-resource/string_array.h @@ -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 index 0000000..13afd55 --- /dev/null +++ b/src/plugins/resource-native/plugin-resource-native.c @@ -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 +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#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("\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 " + +#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 index 0000000..ead5c88 --- /dev/null +++ b/src/plugins/resource-native/resource-client.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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", 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, ""); break; + } + } + + cnt++; + } + } + + if (!cnt && hdr && p < e) + p += snprintf(p, e-p, "%s", 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", 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 = ""; + 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 = ""; 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(" \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 index 0000000..2c0a593 --- /dev/null +++ b/src/plugins/resource-wrt/Makefile @@ -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 index 0000000..36d062f --- /dev/null +++ b/src/plugins/resource-wrt/plugin-resource-wrt.c @@ -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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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, ""); + 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 " +#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 index 0000000..a739e6b --- /dev/null +++ b/src/plugins/resource-wrt/resource-api.js @@ -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 : "" + }; + + 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 : "" + }; + 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 index 0000000..f87323b --- /dev/null +++ b/src/plugins/resource-wrt/resource-test.html @@ -0,0 +1,399 @@ + + Resource-Webruntime Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audio File:
Actions: + + + + +
Status:
disconnected
grant mask:
advice mask:
granted:
allocable:
+ + +
+ + + + diff --git a/src/plugins/resource-wrt/resource-wrt.h b/src/plugins/resource-wrt/resource-wrt.h new file mode 100644 index 0000000..f0df672 --- /dev/null +++ b/src/plugins/resource-wrt/resource-wrt.h @@ -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 index 0000000..b518650 --- /dev/null +++ b/src/plugins/tests/Makefile.am @@ -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 index 0000000..85de4da --- /dev/null +++ b/src/resolver/console.c @@ -0,0 +1,51 @@ +#include +#include +#include + +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 index 0000000..c0171a0 --- /dev/null +++ b/src/resolver/events.c @@ -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 + +#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 index 0000000..ee0ee1b --- /dev/null +++ b/src/resolver/events.h @@ -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 index 0000000..00a0f6c --- /dev/null +++ b/src/resolver/fact.c @@ -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 +#include +#include + +#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 index 0000000..c4e04a7 --- /dev/null +++ b/src/resolver/fact.h @@ -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 +#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 index 0000000..48d4524 --- /dev/null +++ b/src/resolver/murphy-resolver.pc.in @@ -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 index 0000000..6392359 --- /dev/null +++ b/src/resolver/parser-api.h @@ -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 + +#include + +#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 index 0000000..b2f7496 --- /dev/null +++ b/src/resolver/parser.y @@ -0,0 +1,277 @@ +%{ /* -*- c -*- */ + +#include +#include +#include + +#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 KEY_UPDATE_SCRIPT +%token KEY_END_SCRIPT +%token KEY_AUTOUPDATE +%token TKN_IDENT +%token TKN_FACT +%token TKN_SCRIPT_LINE +%token TKN_LEX_ERROR + +%type targets +%type target +%type optional_dependencies +%type dependencies +%type