/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2003-2009 Thomas Graf <tgraf@suug.ch>
 */

/**
 * @defgroup cli Command Line Interface API
 *
 * @{
 *
 * These modules provide an interface for text based applications. The
 * functions provided are wrappers for their libnl equivalent with
 * added error handling. The functions check for allocation failures,
 * invalid input, and unknown types and will print error messages
 * accordingly via nl_cli_fatal().
 */

#include <netlink/cli/utils.h>
#include <locale.h>

#include "lib/defs.h"

#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif

/**
 * Parse a text based 32 bit unsigned integer argument
 * @arg arg		Integer in text form.
 *
 * Tries to convert the number provided in arg to a uint32_t. Will call
 * nl_cli_fatal() if the conversion fails.
 *
 * @return 32bit unsigned integer.
 */
uint32_t nl_cli_parse_u32(const char *arg)
{
	unsigned long lval;
	char *endptr;

	lval = strtoul(arg, &endptr, 0);
	if (endptr == arg || lval == ULONG_MAX)
		nl_cli_fatal(EINVAL, "Unable to parse \"%s\", not a number.",
			     arg);

	return (uint32_t) lval;
}

void nl_cli_print_version(void)
{
	printf("libnl tools version %s\n", LIBNL_VERSION);
	printf(
	"Copyright (C) 2003-2010 Thomas Graf <tgraf@redhat.com>\n"
	"\n"
	"This program comes with ABSOLUTELY NO WARRANTY. This is free \n"
	"software, and you are welcome to redistribute it under certain\n"
	"conditions. See the GNU General Public License for details.\n"
	);

	exit(0);
}

/**
 * Print error message and quit application
 * @arg err		Error code.
 * @arg fmt		Error message.
 *
 * Prints the formatted error message to stderr and quits the application
 * using the provided error code.
 */
void nl_cli_fatal(int err, const char *fmt, ...)
{
	va_list ap;

	fprintf(stderr, "Error: ");

	if (fmt) {
		va_start(ap, fmt);
		vfprintf(stderr, fmt, ap);
		va_end(ap);
		fprintf(stderr, "\n");
	} else {
		char *buf;
#ifdef HAVE_STRERROR_L
		locale_t loc = newlocale(LC_MESSAGES_MASK, "", (locale_t)0);
		if (loc == (locale_t)0) {
			if (errno == ENOENT)
				loc = newlocale(LC_MESSAGES_MASK,
						"POSIX", (locale_t)0);
			if (loc == (locale_t)0)
				buf = "newlocale() failed";
		}
		if (loc != (locale_t)0)
			buf = strerror_l(err, loc);
#else
		buf = strerror(err);
#endif
		fprintf(stderr, "%s\n", buf);
#ifdef HAVE_STRERROR_L
		if (loc != (locale_t)0)
			freelocale(loc);
#endif
	}

	exit(abs(err));
}

int nl_cli_connect(struct nl_sock *sk, int protocol)
{
	int err;

	if ((err = nl_connect(sk, protocol)) < 0)
		nl_cli_fatal(err, "Unable to connect netlink socket: %s",
			     nl_geterror(err));

	return err;
}

struct nl_sock *nl_cli_alloc_socket(void)
{
	struct nl_sock *sock;

	if (!(sock = nl_socket_alloc()))
		nl_cli_fatal(ENOBUFS, "Unable to allocate netlink socket");

	return sock;
}

struct nl_addr *nl_cli_addr_parse(const char *str, int family)
{
	struct nl_addr *addr;
	int err;

	if ((err = nl_addr_parse(str, family, &addr)) < 0)
		nl_cli_fatal(err, "Unable to parse address \"%s\": %s",
			     str, nl_geterror(err));

	return addr;
}

int nl_cli_parse_dumptype(const char *str)
{
	if (!strcasecmp(str, "brief"))
		return NL_DUMP_LINE;
	else if (!strcasecmp(str, "details") || !strcasecmp(str, "detailed"))
		return NL_DUMP_DETAILS;
	else if (!strcasecmp(str, "stats"))
		return NL_DUMP_STATS;
	else
		nl_cli_fatal(EINVAL, "Invalid dump type \"%s\".\n", str);

	return 0;
}

int nl_cli_confirm(struct nl_object *obj, struct nl_dump_params *params,
		   int default_yes)
{
	nl_object_dump(obj, params);

	for (;;) {
		char buf[32] = { 0 };
		int answer;

		printf("Delete? (%c/%c) ",
			default_yes ? 'Y' : 'y',
			default_yes ? 'n' : 'N');

		if (!fgets(buf, sizeof(buf), stdin)) {
			fprintf(stderr, "Error while reading\n.");
			continue;
		}

		switch ((answer = tolower(buf[0]))) {
		case '\n':
			answer = default_yes ? 'y' : 'n';
			/* fall through */
		case 'y':
		case 'n':
			return answer == 'y';
		}

		fprintf(stderr, "Invalid input, try again.\n");
	}

	return 0;

}

struct nl_cache *nl_cli_alloc_cache(struct nl_sock *sock, const char *name,
			    int (*ac)(struct nl_sock *, struct nl_cache **))
{
	struct nl_cache *cache;
	int err;

	if ((err = ac(sock, &cache)) < 0)
		nl_cli_fatal(err, "Unable to allocate %s cache: %s",
			     name, nl_geterror(err));

	nl_cache_mngt_provide(cache);

	return cache;
}

struct nl_cache *nl_cli_alloc_cache_flags(struct nl_sock *sock,
			    const char *name, unsigned int flags,
			    int (*ac)(struct nl_sock *, struct nl_cache **,
				      unsigned int))
{
	struct nl_cache *cache;
	int err;

	if ((err = ac(sock, &cache, flags)) < 0)
		nl_cli_fatal(err, "Unable to allocate %s cache: %s",
			     name, nl_geterror(err));

	nl_cache_mngt_provide(cache);

	return cache;
}

void nl_cli_load_module(const char *prefix, const char *name)
{
	char path[FILENAME_MAX+1];

	snprintf(path, sizeof(path), "%s/%s/%s.so",
		 PKGLIBDIR, prefix, name);

#ifdef HAVE_DLFCN_H
	{
		void *handle;

		handle = dlopen(path, RTLD_NOW);
		if (!handle) {
			nl_cli_fatal(ENOENT, "Unable to load module \"%s\": %s\n",
			             path, dlerror());
		}
		/* We intentionally leak the dlopen handle. */
		/* coverity[RESOURCE_LEAK] */
	}
#else
	nl_cli_fatal(ENOTSUP, "Unable to load module \"%s\": built without dynamic libraries support\n",
	             path);
#endif
}

/** @} */
