/*
 *   cli_dbus.c - Teamd daemon control library D-Bus client
 *   Copyright (C) 2013-2015 Jiri Pirko <jiri@resnulli.us>
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"

#ifdef ENABLE_DBUS

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <dbus/dbus.h>
#include <teamdctl.h>
#include "teamdctl_private.h"
#include "../teamd/teamd_dbus_common.h"

struct cli_dbus_priv {
	DBusConnection *conn;
	char *service_name;
};

static int cli_dbus_check_error_msg(struct teamdctl *tdc, DBusMessage *msg)
{
	DBusMessageIter args;
	dbus_bool_t dbres;
	char *param = NULL;
	const char *err_msg;

	err_msg = dbus_message_get_error_name(msg);
	if (!err_msg)
		return 0;
	err(tdc, "dbus: Error message received: \"%s\"", err_msg);

	dbres = dbus_message_iter_init(msg, &args);
	if (dbres == TRUE) {
		if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) {
			err(tdc, "dbus: Received argument is not string as expected.");
			return -EINVAL;
		}
		dbus_message_iter_get_basic(&args, &param);
		err(tdc, "dbus: Error message content: \"%s\"", param);
	}
	return -EINVAL;
}

static int cli_dbus_get_reply_str(struct teamdctl *tdc, char **p_reply,
				  DBusMessage *msg)
{
	DBusMessageIter args;
	dbus_bool_t dbres;
	char *param = NULL;

	dbres = dbus_message_iter_init(msg, &args);
	if (dbres == FALSE) {
		err(tdc, "Failed, no data received.");
		return -EINVAL;
	}

	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) {
		err(tdc, "dbus: Received argument is not string as expected.");
		return -EINVAL;
	}
	dbus_message_iter_get_basic(&args, &param);
	*p_reply = param;
	return 0;
}

static int cli_dbus_method_call(struct teamdctl *tdc, const char *method_name,
				char **p_reply, void *priv,
				const char *fmt, va_list ap)
{
	struct cli_dbus_priv *cli_dbus = priv;
	char *str;
	DBusMessage *msg;
	DBusMessageIter iter;
	dbus_bool_t dbres;
	DBusPendingCall *pending;
	char *reply;
	int err;

	dbg(tdc, "dbus: Calling method \"%s\"", method_name);
	msg = dbus_message_new_method_call(cli_dbus->service_name,
					   TEAMD_DBUS_PATH, TEAMD_DBUS_IFACE,
					   method_name);
	if (!msg) {
		err(tdc, "dbus: Failed to create message.");
		return -ENOMEM;
	}
	dbus_message_iter_init_append(msg, &iter);
	while (*fmt) {
		switch (*fmt++) {
		case 's': /* string */
			str = va_arg(ap, char *);
			dbres = dbus_message_iter_append_basic(&iter,
							       DBUS_TYPE_STRING,
							       &str);
			if (dbres == FALSE) {
				err(tdc, "dbus: Failed to construct message.");
				err = -ENOMEM;
				goto free_msg;
			}
			break;
		default:
			err(tdc, "dbus: Unknown argument type requested.");
			err = -EINVAL;
			goto free_msg;
		}
	}

	dbres = dbus_connection_send_with_reply(cli_dbus->conn, msg,
						&pending, TEAMDCTL_REPLY_TIMEOUT);
	if (dbres == FALSE) {
		err(tdc, "dbus: Send with reply failed.");
		err = -ENOMEM;
		goto free_msg;
	}
	if (!pending) {
		err(tdc, "dbus: Pending call not created.");
		err = -ENOMEM;
		goto free_msg;
	}

	dbus_pending_call_block(pending);

	dbus_message_unref(msg);
	msg = dbus_pending_call_steal_reply(pending);
	dbus_pending_call_unref(pending);
	if (!msg) {
		err(tdc, "dbus: Failed to get reply.");
		err = -EINVAL;
		goto out;
	}

	err = cli_dbus_check_error_msg(tdc, msg);
	if (err)
		goto free_msg;

	if (p_reply) {
		err = cli_dbus_get_reply_str(tdc, &reply, msg);
		if (err)
			goto free_msg;

		reply = strdup(reply);
		if (!reply) {
			err = -ENOMEM;
			goto free_msg;
		}
		*p_reply = reply;
	}

free_msg:
	dbus_message_unref(msg);
out:
	return err;
}

static int cli_dbus_init(struct teamdctl *tdc, const char *team_name, void *priv)
{
	struct cli_dbus_priv *cli_dbus = priv;
	DBusError error;
	int ret;
	int err;

	ret = asprintf(&cli_dbus->service_name, TEAMD_DBUS_SERVICE ".%s",
		       team_name);
	if (ret == -1)
		return -errno;

	dbus_error_init(&error);
	cli_dbus->conn = dbus_bus_get(DBUS_BUS_SYSTEM, &error);
	if (!cli_dbus->conn) {
		err(tdc, "dbus: Could not acquire the system bus: %s - %s",
			 error.name, error.message);
		err = -EINVAL;
		goto free_service_name;
	}
	err = 0;
	goto free_error;

free_service_name:
	free(cli_dbus->service_name);
free_error:
	dbus_error_free(&error);
	return err;
}

void cli_dbus_fini(struct teamdctl *tdc, void *priv)
{
	struct cli_dbus_priv *cli_dbus = priv;

	free(cli_dbus->service_name);
	dbus_connection_unref(cli_dbus->conn);
}

static const struct teamdctl_cli cli_dbus = {
	.name = "dbus",
	.init = cli_dbus_init,
	.fini = cli_dbus_fini,
	.test_method_call_required = true,
	.method_call = cli_dbus_method_call,
	.priv_size = sizeof(struct cli_dbus_priv),
};

const struct teamdctl_cli *teamdctl_cli_dbus_get(void)
{
	return &cli_dbus;
}

#endif /* ENABLE_DBUS */
