mapper-cli: rewrite with sdbus

There are too many instances of the wait/call applications
spawned during BMC startup.  Re-write using sdbus.

Signed-off-by: Brad Bishop <bradleyb@fuzziesquirrel.com>
Change-Id: Ia6fb3b74cb70f93cfd5cc57c1a8b7a9aa2d944c4
diff --git a/libmapper/Makefile b/libmapper/Makefile
index 806390c..2f71515 100644
--- a/libmapper/Makefile
+++ b/libmapper/Makefile
@@ -1,16 +1,18 @@
 libdir=/usr/lib
+sbindir=/usr/sbin
 includedir=/usr/include
 
 PACKAGE_DEPS=libsystemd
 SONAME=libmapper.so
 VERSION=1
 LIBMAPPER=$(SONAME).$(VERSION)
+BIN=mapper
 INCLUDES=mapper.h
 
 LDLIBS+=$(shell pkg-config --libs $(PACKAGE_DEPS))
 ALL_CFLAGS+=$(shell pkg-config --cflags $(PACKAGE_DEPS)) -fPIC -Werror $(CFLAGS)
 
-all: $(SONAME)
+all: $(SONAME) $(BIN)
 
 %.o: %.c
 	$(CC) -c $(ALL_CFLAGS) -o $@ $<
@@ -22,12 +24,17 @@
 	$(CC) -shared $(CFLAGS) $(LDFLAGS) -Wl,-soname,$(SONAME) \
 		-o $@ $^ $(LDLIBS)
 
-install: $(SONAME) $(LIBMAPPER)
+$(BIN): app.o $(LIBMAPPER)
+	$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS)
+
+install: $(SONAME) $(LIBMAPPER) $(BIN)
 	@mkdir -p $(DESTDIR)$(includedir)
 	install $(INCLUDES) $(DESTDIR)$(includedir)
 	@mkdir -p $(DESTDIR)$(libdir)
 	install $(LIBMAPPER) $(DESTDIR)$(libdir)
 	ln -sf $(LIBMAPPER) $(DESTDIR)$(libdir)/$(SONAME)
+	@mkdir -p $(DESTDIR)$(sbindir)
+	install $(BIN) $(DESTDIR)$(sbindir)
 
 clean:
-	rm -f *.o $(LIBMAPPER) $(SONAME)
+	rm -f *.o $(LIBMAPPER) $(SONAME) $(BIN)
diff --git a/libmapper/app.c b/libmapper/app.c
new file mode 100644
index 0000000..e2b94eb
--- /dev/null
+++ b/libmapper/app.c
@@ -0,0 +1,159 @@
+/**
+ * Copyright © 2016 IBM Corporation
+ *
+ * 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.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-event.h>
+#include "mapper.h"
+
+static int call_main(int argc, char *argv[])
+{
+	int r;
+	sd_bus *conn = NULL;
+	char *service = NULL;
+	sd_bus_message *m = NULL, *reply = NULL;
+	sd_bus_error error = SD_BUS_ERROR_NULL;
+
+	if(argc < 5) {
+		fprintf(stderr, "Usage: %s call OBJECTPATH INTERFACE "
+				"METHOD [SIGNATURE [ARGUMENT...]\n", argv[0]);
+		r = -1;
+		goto finish;
+	}
+
+	r = sd_bus_default_system(&conn);
+	if(r < 0) {
+		fprintf(stderr, "Error connecting to system bus: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+	r = mapper_get_service(conn, argv[2], &service);
+	if(r < 0) {
+		fprintf(stderr, "Error finding service: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+	r = sd_bus_message_new_method_call(
+			conn, &m, service, argv[2], argv[3], argv[4]);
+	if(r < 0) {
+		fprintf(stderr, "Error populating message: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+	if(argc > 5) {
+		char **p;
+		p = argv + 6;
+		r = sd_bus_message_append_cmdline(m, argv[5], &p);
+		if(r < 0) {
+			fprintf(stderr, "Error appending method arguments: %s\n",
+					strerror(-r));
+			goto finish;
+		}
+	}
+
+	r = sd_bus_call(conn, m, 0, &error, &reply);
+	if(r < 0) {
+		fprintf(stderr, "Error invoking method: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+finish:
+	exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static void quit(int r, void *loop)
+{
+	sd_event_exit((sd_event *)loop, r);
+}
+
+static int wait_main(int argc, char *argv[])
+{
+	int r;
+	sd_bus *conn = NULL;
+	sd_event *loop = NULL;
+	mapper_async_wait *wait = NULL;
+
+	if(argc < 3) {
+		fprintf(stderr, "Usage: %s wait OBJECTPATH...\n", argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	r = sd_bus_default_system(&conn);
+	if(r < 0) {
+		fprintf(stderr, "Error connecting to system bus: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+	r = sd_event_default(&loop);
+	if (r < 0) {
+		fprintf(stderr, "Error obtaining event loop: %s\n",
+				strerror(-r));
+
+		goto finish;
+	}
+
+	r = sd_bus_attach_event(conn, loop, SD_EVENT_PRIORITY_NORMAL);
+	if (r < 0) {
+		fprintf(stderr, "Failed to attach system "
+				"bus to event loop: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+	r = mapper_wait_async(conn, argv+2, quit, loop, &wait);
+	if(r < 0) {
+		fprintf(stderr, "Error configuring waitlist: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+	r = sd_event_loop(loop);
+	if(r < 0) {
+		fprintf(stderr, "Error starting event loop: %s\n",
+				strerror(-r));
+		goto finish;
+	}
+
+finish:
+	exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+	static const char *usage =
+		"Usage: %s {COMMAND} ...\n"
+		"\nCOMMANDS:\n"
+		"  call           invoke the specified method\n"
+		"  wait           wait for the specified objects to appear on the DBus\n";
+
+	if(argc < 2) {
+		fprintf(stderr, usage, argv[0]);
+		exit(EXIT_FAILURE);
+	}
+
+	if(!strcmp(argv[1], "call"))
+		call_main(argc, argv);
+	if(!strcmp(argv[1], "wait"))
+		wait_main(argc, argv);
+
+	fprintf(stderr, usage, argv[0]);
+	exit(EXIT_FAILURE);
+}
diff --git a/libmapper/mapper.c b/libmapper/mapper.c
index 62dd2e6..3646cc1 100644
--- a/libmapper/mapper.c
+++ b/libmapper/mapper.c
@@ -13,10 +13,319 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <stdlib.h>
 #include <string.h>
+#include <stdio.h>
+#include <errno.h>
 #include <systemd/sd-bus.h>
 #include "mapper.h"
 
+static const char *async_wait_name_owner_match =
+	"type='signal',"
+	"sender='org.freedesktop.DBus',"
+	"interface='org.freedesktop.DBus',"
+	"member='NameOwnerChanged',"
+	"path='/org/freedesktop/DBus'";
+
+static const char *async_wait_interfaces_added_match =
+	"type='signal',"
+	"interface='org.freedesktop.DBus.ObjectManager',"
+	"member='InterfacesAdded'";
+
+struct mapper_async_wait
+{
+	char **objs;
+	void (*callback)(int, void *);
+	void *userdata;
+	sd_bus *conn;
+	sd_bus_slot *name_owner_slot;
+	sd_bus_slot *intf_slot;
+	int *status;
+	int count;
+	int finished;
+	int r;
+};
+
+struct async_wait_callback_data
+{
+	mapper_async_wait *wait;
+	const char *path;
+};
+
+static int async_wait_match_name_owner_changed(sd_bus_message *, void *,
+		sd_bus_error *);
+static int async_wait_match_interfaces_added(sd_bus_message *, void *,
+		sd_bus_error *);
+static int async_wait_check_done(mapper_async_wait *);
+static void async_wait_done(int r, mapper_async_wait *);
+static int async_wait_get_objects(mapper_async_wait *);
+
+static int sarraylen(char *array[])
+{
+	int count = 0;
+	char **p = array;
+
+	while(*p != NULL) {
+		++count;
+		++p;
+	}
+
+	return count;
+}
+
+static void sarrayfree(char *array[])
+{
+	char **p = array;
+	while(*p != NULL) {
+		free(*p);
+		++p;
+	}
+	free(array);
+}
+
+static char **sarraydup(char *array[])
+{
+	int count = sarraylen(array);
+	int i;
+	char **ret = NULL;
+
+	ret = malloc(sizeof(*ret) * count);
+	if(!ret)
+		return NULL;
+
+	for(i=0; i<count; ++i) {
+		ret[i] = strdup(array[i]);
+		if(!ret[i])
+			goto error;
+	}
+
+	return ret;
+
+error:
+	sarrayfree(ret);
+	return NULL;
+}
+
+static int async_wait_getobject_callback(sd_bus_message *m,
+		void *userdata,
+		sd_bus_error *e)
+{
+	int i, r;
+	const char *msg;
+	struct async_wait_callback_data *data = userdata;
+	mapper_async_wait *wait = data->wait;
+
+	if(wait->finished)
+		return 0;
+	if(sd_bus_message_get_errno(m))
+		return 0;
+
+	for(i=0; i<wait->count; ++i) {
+		if(!strcmp(data->path, wait->objs[i])) {
+			wait->status[i] = 1;
+		}
+	}
+
+	free(data);
+	if(async_wait_check_done(wait))
+		async_wait_done(0, wait);
+
+	return 0;
+}
+
+static int async_wait_get_objects(mapper_async_wait *wait)
+{
+	int i, r;
+	struct async_wait_callback_data *data = NULL;
+
+	for(i=0; i<wait->count; ++i) {
+		if(wait->status[i])
+			continue;
+		data = malloc(sizeof(*data));
+		data->wait = wait;
+		data->path = wait->objs[i];
+		r = sd_bus_call_method_async(
+				wait->conn,
+				NULL,
+				"org.openbmc.ObjectMapper",
+				"/org/openbmc/ObjectMapper",
+				"org.openbmc.ObjectMapper",
+				"GetObject",
+				async_wait_getobject_callback,
+				data,
+				"s",
+				wait->objs[i]);
+		if(r < 0) {
+			free(data);
+			fprintf(stderr, "Error invoking method: %s\n",
+					strerror(-r));
+			return r;
+		}
+	}
+
+	return 0;
+}
+
+static int async_wait_match_name_owner_changed(sd_bus_message *m, void *w,
+		sd_bus_error *e)
+{
+	int i, r;
+
+	mapper_async_wait *wait = w;
+	if(wait->finished)
+		return 0;
+
+	r = async_wait_get_objects(wait);
+	if(r < 0)
+		async_wait_done(r, wait);
+
+	return 0;
+}
+
+static int async_wait_match_interfaces_added(sd_bus_message *m, void *w,
+		sd_bus_error *e)
+{
+	int i, r;
+	mapper_async_wait *wait = w;
+	const char *path;
+
+	if(wait->finished)
+		return 0;
+
+	r = sd_bus_message_read(m, "o", &path);
+	if (r < 0) {
+		fprintf(stderr, "Error reading message: %s\n",
+				strerror(-r));
+		goto finished;
+	}
+
+	for(i=0; i<wait->count; ++i) {
+		if(!strcmp(path, wait->objs[i]))
+			wait->status[i] = 1;
+	}
+
+finished:
+	if(r < 0 || async_wait_check_done(wait))
+		async_wait_done(r < 0 ? r : 0, wait);
+
+	return 0;
+}
+
+static void async_wait_done(int r, mapper_async_wait *w)
+{
+	if(w->finished)
+		return;
+
+	w->finished = 1;
+	sd_bus_slot_unref(w->name_owner_slot);
+	sd_bus_slot_unref(w->intf_slot);
+
+	if(w->callback)
+		w->callback(r, w->userdata);
+}
+
+static int async_wait_check_done(mapper_async_wait *w)
+{
+	int i;
+
+	if(w->finished)
+		return 1;
+
+	for(i=0; i<w->count; ++i)
+		if(!w->status[i])
+			return 0;
+
+	return 1;
+}
+
+void mapper_wait_async_free(mapper_async_wait *w)
+{
+	free(w->status);
+	sarrayfree(w->objs);
+	free(w);
+}
+
+int mapper_wait_async(sd_bus *conn,
+		char *objs[],
+		void (*callback)(int, void *),
+		void *userdata,
+		mapper_async_wait **w)
+{
+	int r;
+	mapper_async_wait *wait = NULL;
+
+	wait = malloc(sizeof(*wait));
+	if(!wait)
+		return -ENOMEM;
+
+	memset(wait, 0, sizeof(*wait));
+	wait->conn = conn;
+	wait->callback = callback;
+	wait->userdata = userdata;
+	wait->count = sarraylen(objs);
+	if(!wait->count)
+		return 0;
+
+	wait->objs = sarraydup(objs);
+	if(!wait->objs) {
+		r = -ENOMEM;
+		goto free_wait;
+	}
+
+	wait->status = malloc(sizeof(*wait->status) * wait->count);
+	if(!wait->status) {
+		r = -ENOMEM;
+		goto free_objs;
+	}
+	memset(wait->status, 0, sizeof(*wait->status) * wait->count);
+
+	r = sd_bus_add_match(conn,
+                        &wait->name_owner_slot,
+			async_wait_name_owner_match,
+                        async_wait_match_name_owner_changed,
+                        wait);
+	if(r < 0) {
+		fprintf(stderr, "Error adding match rule: %s\n",
+				strerror(-r));
+		goto free_status;
+	}
+
+	r = sd_bus_add_match(conn,
+                        &wait->intf_slot,
+			async_wait_interfaces_added_match,
+                        async_wait_match_interfaces_added,
+                        wait);
+	if(r < 0) {
+		fprintf(stderr, "Error adding match rule: %s\n",
+				strerror(-r));
+		goto unref_name_slot;
+	}
+
+	r = async_wait_get_objects(wait);
+	if(r < 0) {
+		fprintf(stderr, "Error calling method: %s\n",
+				strerror(-r));
+		goto unref_intf_slot;
+	}
+
+	*w = wait;
+
+	return 0;
+
+unref_intf_slot:
+	sd_bus_slot_unref(wait->intf_slot);
+unref_name_slot:
+	sd_bus_slot_unref(wait->name_owner_slot);
+free_status:
+	free(wait->status);
+free_objs:
+	sarrayfree(wait->objs);
+free_wait:
+	free(wait);
+
+	return r;
+}
+
 int mapper_get_service(sd_bus *conn, const char *obj, char **service)
 {
 	sd_bus_error error = SD_BUS_ERROR_NULL;
diff --git a/libmapper/mapper.h b/libmapper/mapper.h
index 58f1844..b49b302 100644
--- a/libmapper/mapper.h
+++ b/libmapper/mapper.h
@@ -3,6 +3,11 @@
 #ifdef __cplusplus
 extern "C" {
 #endif
+typedef struct mapper_async_wait mapper_async_wait;
+void mapper_wait_async_free(mapper_async_wait *);
+
+int mapper_wait_async(sd_bus *, char *[],
+		void (*)(int, void *), void *, mapper_async_wait **);
 int mapper_get_service(sd_bus *conn, const char *obj, char **service);
 #ifdef __cplusplus
 }
diff --git a/mapper b/mapper
deleted file mode 100644
index be64aac..0000000
--- a/mapper
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-
-# Contributors Listed Below - COPYRIGHT 2016
-# [+] International Business Machines Corp.
-#
-#
-# 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.
-
-import sys
-import obmc.mapper.cli
-
-if __name__ == '__main__':
-    sys.exit(obmc.mapper.cli.mapper_main())
diff --git a/obmc/mapper/cli.py b/obmc/mapper/cli.py
deleted file mode 100644
index 7f96316..0000000
--- a/obmc/mapper/cli.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Contributors Listed Below - COPYRIGHT 2016
-# [+] International Business Machines Corp.
-#
-#
-# 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.
-
-import sys
-import dbus
-import dbus.mainloop.glib
-import gobject
-import obmc.dbuslib.enums
-import obmc.mapper
-import obmc.mapper.utils
-import optparse
-
-
-def add_systemd_path_option(parser):
-    parser.add_option(
-        '-s', '--systemd', action='store_true', default=False,
-        help='interpret-dash-delimited-path-arguments-as-paths')
-
-
-def systemd_to_dbus(item):
-    if not item.startswith('/'):
-        item = '/%s' % item.replace('-', '/')
-    return item
-
-
-class CallApp(object):
-    usage = 'OBJECTPATH INTERFACE METHOD ARGUMENTS...'
-    description = 'Invoke a DBus method on the named DBus object.'
-
-    def setup(self, parser, command):
-        add_systemd_path_option(parser)
-
-    def main(self, parser):
-        args = parser.largs
-        try:
-            path, interface, method, parameters = \
-                args[0], args[1], args[2], args[3:]
-        except IndexError:
-            parser.error('Not enough arguments')
-
-        bus = dbus.SystemBus()
-        mapper = obmc.mapper.Mapper(bus)
-        if parser.values.systemd:
-            path = systemd_to_dbus(path)
-
-        try:
-            service_info = mapper.get_object(path)
-        except dbus.exceptions.DBusException, e:
-            if e.get_dbus_name() != obmc.mapper.MAPPER_NOT_FOUND:
-                raise
-            parser.error('\'%s\' was not found' % path)
-
-        obj = bus.get_object(list(service_info)[0], path, introspect=False)
-        iface = dbus.Interface(obj, interface)
-        func = getattr(iface, method)
-        try:
-            return func(*parameters)
-        except dbus.exceptions.DBusException, e:
-            if e.get_dbus_name() != obmc.dbuslib.enums.DBUS_UNKNOWN_METHOD:
-                raise
-            parser.error(
-                '\'%s.%s\' is not a valid method for \'%s\''
-                % (interface, method, path))
-
-
-class WaitApp(object):
-    usage = 'OBJECTPATH...'
-    description = 'Wait for one or more DBus ' \
-        'object(s) to appear on the system bus.'
-
-    def setup(self, parser, command):
-        add_systemd_path_option(parser)
-
-    def main(self, parser):
-        if not parser.largs:
-            parser.error('Specify one or more object paths')
-
-        dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
-        loop = gobject.MainLoop()
-        bus = dbus.SystemBus()
-        if parser.values.systemd:
-            waitlist = [systemd_to_dbus(x) for x in parser.largs]
-        else:
-            waitlist = parser.largs
-
-        waiter = obmc.mapper.utils.Wait(bus, waitlist, callback=loop.quit)
-        loop.run()
-
-
-def mapper_main():
-    all_commands = []
-    usage = '''%prog [options] SUBCOMMAND\n\nSUBCOMMANDS:\n'''
-    for k, v in sys.modules[__name__].__dict__.iteritems():
-        if k.endswith('App'):
-            all_commands.append(k.replace('App', '').lower())
-            usage += '    %s - %s\n' % (all_commands[-1], v.description)
-
-    parser = optparse.OptionParser(usage=usage)
-    commands = list(set(sys.argv[1:]).intersection(all_commands))
-    if len(commands) != 1:
-        parser.error('Specify a single sub-command')
-
-    classname = '%sApp' % commands[0].capitalize()
-    cls = getattr(sys.modules[__name__], classname)
-    usage = getattr(cls, 'usage')
-
-    parser.set_usage('%%prog %s [options] %s' % (commands[0], usage))
-    inst = cls()
-    inst.setup(parser, commands[0])
-    opts, args = parser.parse_args()
-    parser.largs = parser.largs[1:]
-    return inst.main(parser)
diff --git a/setup.py b/setup.py
index ca87b38..9967f98 100644
--- a/setup.py
+++ b/setup.py
@@ -2,5 +2,5 @@
 setup(name='phosphor-mapper',
       version='1.0',
       packages=['obmc.mapper'],
-      scripts=['phosphor-mapper', 'mapper']
+      scripts=['phosphor-mapper']
       )