/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <errno.h>
#include <ipmp_admin.h>
#include <libinetutil.h>
#include <locale.h>
#include <net/if.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/types.h>

typedef	void		offline_func_t(const char *, ipmp_handle_t);

static const char	*progname;
static int		sioc4fd, sioc6fd;
static offline_func_t	do_offline, undo_offline;
static boolean_t	set_lifflags(const char *, uint64_t);
static boolean_t	is_offline(const char *);
static void		warn(const char *, ...);
static void		die(const char *, ...);

static void
usage()
{
	(void) fprintf(stderr, "Usage: %s [-d | -r] <interface>\n", progname);
	exit(1);
}

static const char *
mpadm_errmsg(uint32_t error)
{
	switch (error) {
	case IPMP_EUNKIF:
		return ("not a physical interface or not in an IPMP group");
	case IPMP_EMINRED:
		return ("no other functioning interfaces are in its IPMP "
		    "group");
	default:
		return (ipmp_errmsg(error));
	}
}

int
main(int argc, char **argv)
{
	int retval;
	ipmp_handle_t handle;
	offline_func_t *ofuncp = NULL;
	const char *ifname;
	int c;

	if ((progname = strrchr(argv[0], '/')) != NULL)
		progname++;
	else
		progname = argv[0];

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	while ((c = getopt(argc, argv, "d:r:")) != EOF) {
		switch (c) {
		case 'd':
			ifname = optarg;
			ofuncp = do_offline;
			break;
		case 'r':
			ifname = optarg;
			ofuncp = undo_offline;
			break;
		default :
			usage();
		}
	}

	if (ofuncp == NULL)
		usage();

	/*
	 * Create the global V4 and V6 socket ioctl descriptors.
	 */
	sioc4fd = socket(AF_INET, SOCK_DGRAM, 0);
	sioc6fd = socket(AF_INET6, SOCK_DGRAM, 0);
	if (sioc4fd == -1 || sioc6fd == -1)
		die("cannot create sockets");

	if ((retval = ipmp_open(&handle)) != IPMP_SUCCESS)
		die("cannot create ipmp handle: %s\n", ipmp_errmsg(retval));

	(*ofuncp)(ifname, handle);

	ipmp_close(handle);
	(void) close(sioc4fd);
	(void) close(sioc6fd);

	return (EXIT_SUCCESS);
}

/*
 * Checks whether IFF_OFFLINE is set on `ifname'.
 */
boolean_t
is_offline(const char *ifname)
{
	struct lifreq lifr = { 0 };

	(void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name));
	if (ioctl(sioc4fd, SIOCGLIFFLAGS, &lifr) == -1) {
		if (errno != ENXIO ||
		    ioctl(sioc6fd, SIOCGLIFFLAGS, &lifr) == -1) {
			die("cannot get interface flags on %s", ifname);
		}
	}

	return ((lifr.lifr_flags & IFF_OFFLINE) != 0);
}

static void
do_offline(const char *ifname, ipmp_handle_t handle)
{
	ifaddrlistx_t *ifaddrp, *ifaddrs;
	int retval;

	if (is_offline(ifname))
		die("interface %s is already offline\n", ifname);

	if ((retval = ipmp_offline(handle, ifname, 1)) != IPMP_SUCCESS)
		die("cannot offline %s: %s\n", ifname, mpadm_errmsg(retval));

	/*
	 * Get all the up addresses for `ifname' and bring them down.
	 */
	if (ifaddrlistx(ifname, IFF_UP, 0, &ifaddrs) == -1)
		die("cannot get addresses on %s", ifname);

	for (ifaddrp = ifaddrs; ifaddrp != NULL; ifaddrp = ifaddrp->ia_next) {
		if (!(ifaddrp->ia_flags & IFF_OFFLINE))
			warn("IFF_OFFLINE vanished on %s\n", ifaddrp->ia_name);

		if (!set_lifflags(ifaddrp->ia_name,
		    ifaddrp->ia_flags & ~IFF_UP))
			warn("cannot bring down address on %s\n",
			    ifaddrp->ia_name);
	}

	ifaddrlistx_free(ifaddrs);
}

static void
undo_offline(const char *ifname, ipmp_handle_t handle)
{
	ifaddrlistx_t *ifaddrp, *ifaddrs;
	int retval;

	if (!is_offline(ifname))
		die("interface %s is not offline\n", ifname);

	/*
	 * Get all the down addresses for `ifname' and bring them up.
	 */
	if (ifaddrlistx(ifname, 0, IFF_UP, &ifaddrs) == -1)
		die("cannot get addresses for %s", ifname);

	for (ifaddrp = ifaddrs; ifaddrp != NULL; ifaddrp = ifaddrp->ia_next) {
		if (!(ifaddrp->ia_flags & IFF_OFFLINE))
			warn("IFF_OFFLINE vanished on %s\n", ifaddrp->ia_name);

		if (!set_lifflags(ifaddrp->ia_name, ifaddrp->ia_flags | IFF_UP))
			warn("cannot bring up address on %s\n",
			    ifaddrp->ia_name);
	}

	ifaddrlistx_free(ifaddrs);

	/*
	 * Undo the offline.
	 */
	if ((retval = ipmp_undo_offline(handle, ifname)) != IPMP_SUCCESS) {
		die("cannot undo-offline %s: %s\n", ifname,
		    mpadm_errmsg(retval));
	}

	/*
	 * Verify whether IFF_OFFLINE is set as a sanity check.
	 */
	if (is_offline(ifname))
		warn("in.mpathd has not cleared IFF_OFFLINE on %s\n", ifname);
}

/*
 * Change `lifname' to have `flags' set.  Returns B_TRUE on success.
 */
static boolean_t
set_lifflags(const char *lifname, uint64_t flags)
{
	struct lifreq 	lifr = { 0 };
	int		fd = (flags & IFF_IPV4) ? sioc4fd : sioc6fd;

	(void) strlcpy(lifr.lifr_name, lifname, LIFNAMSIZ);
	lifr.lifr_flags = flags;

	return (ioctl(fd, SIOCSLIFFLAGS, &lifr) >= 0);
}

/* PRINTFLIKE1 */
static void
die(const char *format, ...)
{
	va_list alist;
	char *errstr = strerror(errno);

	format = gettext(format);
	(void) fprintf(stderr, gettext("%s: fatal: "), progname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);

	if (strchr(format, '\n') == NULL)
		(void) fprintf(stderr, ": %s\n", errstr);

	exit(EXIT_FAILURE);
}

/* PRINTFLIKE1 */
static void
warn(const char *format, ...)
{
	va_list alist;
	char *errstr = strerror(errno);

	format = gettext(format);
	(void) fprintf(stderr, gettext("%s: warning: "), progname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);

	if (strchr(format, '\n') == NULL)
		(void) fprintf(stderr, ": %s\n", errstr);
}