diff options
Diffstat (limited to 'sbin/hastctl')
-rw-r--r-- | sbin/hastctl/Makefile | 36 | ||||
-rw-r--r-- | sbin/hastctl/hastctl.8 | 217 | ||||
-rw-r--r-- | sbin/hastctl/hastctl.c | 526 |
3 files changed, 779 insertions, 0 deletions
diff --git a/sbin/hastctl/Makefile b/sbin/hastctl/Makefile new file mode 100644 index 0000000..43c8c20 --- /dev/null +++ b/sbin/hastctl/Makefile @@ -0,0 +1,36 @@ +# $FreeBSD$ + +.include <bsd.own.mk> + +.PATH: ${.CURDIR}/../hastd + +PROG= hastctl +SRCS= activemap.c +SRCS+= ebuf.c +SRCS+= hast_proto.c hastctl.c +SRCS+= metadata.c +SRCS+= nv.c +SRCS+= parse.y pjdlog.c +SRCS+= proto.c proto_common.c proto_tcp4.c proto_uds.c +SRCS+= token.l +SRCS+= subr.c +SRCS+= y.tab.h +WARNS?= 6 +MAN= hastctl.8 + +CFLAGS+=-I${.CURDIR}/../hastd +CFLAGS+=-DINET +.if ${MK_INET6_SUPPORT} != "no" +CFLAGS+=-DINET6 +.endif +# This is needed to have WARNS > 1. +CFLAGS+=-DYY_NO_UNPUT + +DPADD= ${LIBCRYPTO} ${LIBL} +LDADD= -lcrypto -ll + +YFLAGS+=-v + +CLEANFILES=y.tab.c y.tab.h y.output + +.include <bsd.prog.mk> diff --git a/sbin/hastctl/hastctl.8 b/sbin/hastctl/hastctl.8 new file mode 100644 index 0000000..bf03c2e --- /dev/null +++ b/sbin/hastctl/hastctl.8 @@ -0,0 +1,217 @@ +.\" Copyright (c) 2010 The FreeBSD Foundation +.\" All rights reserved. +.\" +.\" This software was developed by Pawel Jakub Dawidek under sponsorship from +.\" the FreeBSD Foundation. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd February 1, 2010 +.Dt HASTCTL 8 +.Os +.Sh NAME +.Nm hastctl +.Nd "Highly Available Storage control utility" +.Sh SYNOPSIS +.Nm +.Cm create +.Op Fl d +.Op Fl c Ar config +.Op Fl e Ar extentsize +.Op Fl k Ar keepdirty +.Op Fl m Ar mediasize +.Ar name ... +.Nm +.Cm role +.Op Fl d +.Op Fl c Ar config +.Aq init | primary | secondary +.Ar all | name ... +.Nm +.Cm status +.Op Fl d +.Op Fl c Ar config +.Op Ar all | name ... +.Nm +.Cm dump +.Op Fl d +.Op Fl c Ar config +.Op Ar all | name ... +.Sh DESCRIPTION +The +.Nm +utility is used to control the behaviour of the +.Xr hastd 8 +daemon. +.Pp +This utility should be used by HA software like +.Nm heartbeat +or +.Nm ucarp +to setup HAST resources role when changing from primary mode to +secondary or vice versa. +Be aware that if a file system like UFS exists on HAST provider and +primary node dies, file system has to be checked for inconsistencies +with the +.Xr fsck 8 +utility after switching secondary node to primary role. +.Pp +The first argument to +.Nm +indicates an action to be performed: +.Bl -tag -width ".Cm create" +.It Cm create +Initialize local provider configured for the given resource. +Additional options include: +.Bl -tag -width ".Fl e Ar extentsize" +.It Fl e Ar extentsize +Size of an extent. +Extent is a block which is used for synchronization. +.Nm +maintains a map of dirty extents and extent is the smallest region that +can be marked as dirty. +If any part of an extent is modified, entire extent will be synchronized +when nodes connect. +If extent size is too small, there will be too much disk activity +related to dirty map updates, which will degrade performance of the +given resource. +If extent size is too large, synchronization, even in case of short +outage, can take a long time increasing the risk of loosing up-to-date +node before synchronization process is completed. +The default extent size is +.Va 2MB . +.It Fl k Ar keepdirty +Maximum number of dirty extents to keep dirty all the time. +Most recently used extents are kept dirty to reduce number of metadata +updates. +The default numer of most recently used extents which will be kept +dirty is +.Va 64 . +.It Fl m Ar mediasize +Size of the smaller provider used as backend storage on both nodes. +This option can be omitted if node providers have the same size on both +sides. +.El +.It Cm role +Change role of the given resource. +The role can be one of: +.Bl -tag -width ".Cm secondary" +.It Cm init +Resource is turned off. +.It Cm primary +Local +.Xr hastd 8 +daemon will act as primary node for the given resource. +System on which resource role is set to primary can use +.Pa /dev/hast/<name> +GEOM provider. +.It Cm secondary +Local +.Xr hastd 8 +daemon will act as secondary node for the given resource - it will wait +for connection from the primary node and will handle I/O requests +received from it. +GEOM provider +.Pa /dev/hast/<name> +will not be created on secondary node. +.El +.It Cm status +Present status of the configured resources. +.It Cm dump +Dump metadata stored on local component for the configured resources. +.El +.Pp +In addition, every subcommand can be followed by the following options: +.Bl -tag -width ".Fl c Ar config" +.It Fl c Ar config +Specify alternative location of the configuration file. +The default location is +.Pa /etc/hast.conf . +.It Fl d +Print debugging information. +This option can be specified multiple times to raise the verbosity +level. +.El +.Sh EXIT STATUS +Exit status is 0 on success, or one of the values described in +.Xr sysexits 3 +on failure. +.Sh EXAMPLES +Initialize HAST provider, create file system on it and mount it. +.Bd -literal -offset indent +nodeB# hastctl create shared +nodeB# hastd +nodeB# hastctl role secondary shared + +nodeB# hastctl create shared +nodeA# hastd +nodeA# hastctl role primary shared +nodeA# newfs -U /dev/hast/shared +nodeA# mount -o noatime /dev/hast/shared /shared +nodeA# application_start +.Ed +.Pp +Switch roles for the +.Nm shared +HAST resource. +.Bd -literal -offset indent +nodeA# application_stop +nodeA# umount -f /shared +nodeA# hastctl role secondary shared + +nodeB# hastctl role primary shared +nodeB# fsck -t ufs /dev/hast/shared +nodeB# mount -o noatime /dev/hast/shared /shared +nodeB# application_start +.Ed +.Sh FILES +.Bl -tag -width ".Pa /var/run/hastctl" -compact +.It Pa /etc/hast.conf +Configuration file for +.Nm +and +.Xr hastd 8 . +.It Pa /var/run/hastctl +Control socket used by +.Nm +to communicate with the +.Xr hastd 8 +daemon. +.El +.Sh SEE ALSO +.Xr sysexits 3 , +.Xr geom 4 , +.Xr hast.conf 5 , +.Xr fsck 8 , +.Xr ggatec 8 , +.Xr ggatel 8 , +.Xr hastd 8 , +.Xr mount 8 , +.Xr newfs 8 . +.Sh AUTHORS +The +.Nm +was developed by +.An Pawel Jakub Dawidek Aq pjd@FreeBSD.org +under sponsorship of the FreeBSD Foundation. diff --git a/sbin/hastctl/hastctl.c b/sbin/hastctl/hastctl.c new file mode 100644 index 0000000..8499528 --- /dev/null +++ b/sbin/hastctl/hastctl.c @@ -0,0 +1,526 @@ +/*- + * Copyright (c) 2009-2010 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by Pawel Jakub Dawidek under sponsorship from + * the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (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/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/disk.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/sysctl.h> + +#include <assert.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sysexits.h> +#include <unistd.h> + +#include <activemap.h> + +#include "hast.h" +#include "hast_proto.h" +#include "metadata.h" +#include "nv.h" +#include "pjdlog.h" +#include "proto.h" +#include "subr.h" + +/* Path to configuration file. */ +static const char *cfgpath = HAST_CONFIG; +/* Hastd configuration. */ +static struct hastd_config *cfg; +/* Control connection. */ +static struct proto_conn *controlconn; + +enum { + CMD_INVALID, + CMD_CREATE, + CMD_ROLE, + CMD_STATUS, + CMD_DUMP +}; + +static __dead2 void +usage(void) +{ + + fprintf(stderr, + "usage: %s create [-d] [-c config] [-e extentsize] [-k keepdirty]\n" + "\t\t[-m mediasize] name ...\n", + getprogname()); + fprintf(stderr, + " %s role [-d] [-c config] <init | primary | secondary> all | name ...\n", + getprogname()); + fprintf(stderr, + " %s status [-d] [-c config] [all | name ...]\n", + getprogname()); + fprintf(stderr, + " %s dump [-d] [-c config] [all | name ...]\n", + getprogname()); + exit(EX_USAGE); +} + +static int +create_one(struct hast_resource *res, intmax_t mediasize, intmax_t extentsize, + intmax_t keepdirty) +{ + unsigned char *buf; + size_t mapsize; + int ec; + + ec = 0; + pjdlog_prefix_set("[%s] ", res->hr_name); + + if (provinfo(res, true) < 0) { + ec = EX_NOINPUT; + goto end; + } + if (mediasize == 0) + mediasize = res->hr_local_mediasize; + else if (mediasize > res->hr_local_mediasize) { + pjdlog_error("Provided mediasize is larger than provider %s size.", + res->hr_localpath); + ec = EX_DATAERR; + goto end; + } + if (!powerof2(res->hr_local_sectorsize)) { + pjdlog_error("Sector size of provider %s is not power of 2 (%u).", + res->hr_localpath, res->hr_local_sectorsize); + ec = EX_DATAERR; + goto end; + } + if (extentsize == 0) + extentsize = HAST_EXTENTSIZE; + if (extentsize < res->hr_local_sectorsize) { + pjdlog_error("Extent size (%jd) is less than sector size (%u).", + (intmax_t)extentsize, res->hr_local_sectorsize); + ec = EX_DATAERR; + goto end; + } + if ((extentsize % res->hr_local_sectorsize) != 0) { + pjdlog_error("Extent size (%jd) is not multiple of sector size (%u).", + (intmax_t)extentsize, res->hr_local_sectorsize); + ec = EX_DATAERR; + goto end; + } + mapsize = activemap_calc_ondisk_size(mediasize - METADATA_SIZE, + extentsize, res->hr_local_sectorsize); + if (keepdirty == 0) + keepdirty = HAST_KEEPDIRTY; + res->hr_datasize = mediasize - METADATA_SIZE - mapsize; + res->hr_extentsize = extentsize; + res->hr_keepdirty = keepdirty; + + res->hr_localoff = METADATA_SIZE + mapsize; + + if (metadata_write(res) < 0) { + ec = EX_IOERR; + goto end; + } + buf = calloc(1, mapsize); + if (buf == NULL) { + pjdlog_error("Unable to allocate %zu bytes of memory for initial bitmap.", + mapsize); + ec = EX_TEMPFAIL; + goto end; + } + if (pwrite(res->hr_localfd, buf, mapsize, METADATA_SIZE) != + (ssize_t)mapsize) { + pjdlog_errno(LOG_ERR, "Unable to store initial bitmap on %s", + res->hr_localpath); + free(buf); + ec = EX_IOERR; + goto end; + } + free(buf); +end: + if (res->hr_localfd >= 0) + close(res->hr_localfd); + pjdlog_prefix_set("%s", ""); + return (ec); +} + +static void +control_create(int argc, char *argv[], intmax_t mediasize, intmax_t extentsize, + intmax_t keepdirty) +{ + struct hast_resource *res; + int ec, ii, ret; + + /* Initialize the given resources. */ + if (argc < 1) + usage(); + ec = 0; + for (ii = 0; ii < argc; ii++) { + TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { + if (strcmp(argv[ii], res->hr_name) == 0) + break; + } + if (res == NULL) { + pjdlog_error("Unknown resource %s.", argv[ii]); + if (ec == 0) + ec = EX_DATAERR; + continue; + } + ret = create_one(res, mediasize, extentsize, keepdirty); + if (ret != 0 && ec == 0) + ec = ret; + } + exit(ec); +} + +static int +dump_one(struct hast_resource *res) +{ + int ret; + + ret = metadata_read(res, false); + if (ret != 0) + return (ret); + + printf("resource: %s\n", res->hr_name); + printf(" datasize: %ju\n", (uintmax_t)res->hr_datasize); + printf(" extentsize: %d\n", res->hr_extentsize); + printf(" keepdirty: %d\n", res->hr_keepdirty); + printf(" localoff: %ju\n", (uintmax_t)res->hr_localoff); + printf(" resuid: %ju\n", (uintmax_t)res->hr_resuid); + printf(" localcnt: %ju\n", (uintmax_t)res->hr_primary_localcnt); + printf(" remotecnt: %ju\n", (uintmax_t)res->hr_primary_remotecnt); + printf(" prevrole: %s\n", role2str(res->hr_previous_role)); + + return (0); +} + +static void +control_dump(int argc, char *argv[]) +{ + struct hast_resource *res; + int ec, ret; + + /* Dump metadata of the given resource(s). */ + + ec = 0; + if (argc == 0 || (argc == 1 && strcmp(argv[0], "all") == 0)) { + TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { + ret = dump_one(res); + if (ret != 0 && ec == 0) + ec = ret; + } + } else { + int ii; + + for (ii = 0; ii < argc; ii++) { + TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) { + if (strcmp(argv[ii], res->hr_name) == 0) + break; + } + if (res == NULL) { + pjdlog_error("Unknown resource %s.", argv[ii]); + if (ec == 0) + ec = EX_DATAERR; + continue; + } + ret = dump_one(res); + if (ret != 0 && ec == 0) + ec = ret; + } + } + exit(ec); +} + +static int +control_set_role(struct nv *nv, const char *newrole) +{ + const char *res, *oldrole; + unsigned int ii; + int error, ret; + + ret = 0; + + for (ii = 0; ; ii++) { + res = nv_get_string(nv, "resource%u", ii); + if (res == NULL) + break; + pjdlog_prefix_set("[%s] ", res); + error = nv_get_int16(nv, "error%u", ii); + if (error != 0) { + if (ret == 0) + ret = error; + pjdlog_warning("Received error %d from hastd.", error); + continue; + } + oldrole = nv_get_string(nv, "role%u", ii); + if (strcmp(oldrole, newrole) == 0) + pjdlog_debug(2, "Role unchanged (%s).", oldrole); + else { + pjdlog_debug(1, "Role changed from %s to %s.", oldrole, + newrole); + } + } + pjdlog_prefix_set("%s", ""); + return (ret); +} + +static int +control_status(struct nv *nv) +{ + unsigned int ii; + const char *str; + int error, ret; + + ret = 0; + + for (ii = 0; ; ii++) { + str = nv_get_string(nv, "resource%u", ii); + if (str == NULL) + break; + printf("%s:\n", str); + error = nv_get_int16(nv, "error%u", ii); + if (error != 0) { + if (ret == 0) + ret = error; + printf(" error: %d\n", error); + continue; + } + printf(" role: %s\n", nv_get_string(nv, "role%u", ii)); + printf(" provname: %s\n", + nv_get_string(nv, "provname%u", ii)); + printf(" localpath: %s\n", + nv_get_string(nv, "localpath%u", ii)); + printf(" extentsize: %u\n", + (unsigned int)nv_get_uint32(nv, "extentsize%u", ii)); + printf(" keepdirty: %u\n", + (unsigned int)nv_get_uint32(nv, "keepdirty%u", ii)); + printf(" remoteaddr: %s\n", + nv_get_string(nv, "remoteaddr%u", ii)); + printf(" replication: %s\n", + nv_get_string(nv, "replication%u", ii)); + str = nv_get_string(nv, "status%u", ii); + if (str != NULL) + printf(" status: %s\n", str); + printf(" dirty: %ju bytes\n", + (uintmax_t)nv_get_uint64(nv, "dirty%u", ii)); + } + return (ret); +} + +static int +numfromstr(const char *str, intmax_t *nump) +{ + intmax_t num; + char *suffix; + int rerrno; + + rerrno = errno; + errno = 0; + num = strtoimax(str, &suffix, 0); + if (errno == 0 && *suffix != '\0') + errno = EINVAL; + if (errno != 0) + return (-1); + *nump = num; + errno = rerrno; + return (0); +} + +int +main(int argc, char *argv[]) +{ + struct nv *nv; + intmax_t mediasize, extentsize, keepdirty; + int cmd, debug, error, ii; + const char *optstr; + + debug = 0; + mediasize = extentsize = keepdirty = 0; + + if (argc == 1) + usage(); + + if (strcmp(argv[1], "create") == 0) { + cmd = CMD_CREATE; + optstr = "c:de:k:m:h"; + } else if (strcmp(argv[1], "role") == 0) { + cmd = CMD_ROLE; + optstr = "c:dh"; + } else if (strcmp(argv[1], "status") == 0) { + cmd = CMD_STATUS; + optstr = "c:dh"; + } else if (strcmp(argv[1], "dump") == 0) { + cmd = CMD_DUMP; + optstr = "c:dh"; + } else + usage(); + + argc--; + argv++; + + for (;;) { + int ch; + + ch = getopt(argc, argv, optstr); + if (ch == -1) + break; + switch (ch) { + case 'c': + cfgpath = optarg; + break; + case 'd': + debug++; + break; + case 'e': + if (numfromstr(optarg, &extentsize) < 0) + err(1, "Invalid extentsize"); + break; + case 'k': + if (numfromstr(optarg, &keepdirty) < 0) + err(1, "Invalid keepdirty"); + break; + case 'm': + if (numfromstr(optarg, &mediasize) < 0) + err(1, "Invalid mediasize"); + break; + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + switch (cmd) { + case CMD_CREATE: + case CMD_ROLE: + if (argc == 0) + usage(); + break; + } + + pjdlog_debug_set(debug); + + cfg = yy_config_parse(cfgpath); + assert(cfg != NULL); + + switch (cmd) { + case CMD_CREATE: + control_create(argc, argv, mediasize, extentsize, keepdirty); + /* NOTREACHED */ + assert(!"What are we doing here?!"); + break; + case CMD_DUMP: + /* Dump metadata from local component of the given resource. */ + control_dump(argc, argv); + /* NOTREACHED */ + assert(!"What are we doing here?!"); + break; + case CMD_ROLE: + /* Change role for the given resources. */ + if (argc < 2) + usage(); + nv = nv_alloc(); + nv_add_uint8(nv, HASTCTL_CMD_SETROLE, "cmd"); + if (strcmp(argv[0], "init") == 0) + nv_add_uint8(nv, HAST_ROLE_INIT, "role"); + else if (strcmp(argv[0], "primary") == 0) + nv_add_uint8(nv, HAST_ROLE_PRIMARY, "role"); + else if (strcmp(argv[0], "secondary") == 0) + nv_add_uint8(nv, HAST_ROLE_SECONDARY, "role"); + else + usage(); + for (ii = 0; ii < argc - 1; ii++) + nv_add_string(nv, argv[ii + 1], "resource%d", ii); + break; + case CMD_STATUS: + /* Obtain status of the given resources. */ + nv = nv_alloc(); + nv_add_uint8(nv, HASTCTL_CMD_STATUS, "cmd"); + if (argc == 0) + nv_add_string(nv, "all", "resource%d", 0); + else { + for (ii = 0; ii < argc; ii++) + nv_add_string(nv, argv[ii], "resource%d", ii); + } + break; + default: + assert(!"Impossible role!"); + } + + /* Setup control connection... */ + if (proto_client(cfg->hc_controladdr, &controlconn) < 0) { + pjdlog_exit(EX_OSERR, + "Unable to setup control connection to %s", + cfg->hc_controladdr); + } + /* ...and connect to hastd. */ + if (proto_connect(controlconn) < 0) { + pjdlog_exit(EX_OSERR, "Unable to connect to hastd via %s", + cfg->hc_controladdr); + } + /* Send the command to the server... */ + if (hast_proto_send(NULL, controlconn, nv, NULL, 0) < 0) { + pjdlog_exit(EX_UNAVAILABLE, + "Unable to send command to hastd via %s", + cfg->hc_controladdr); + } + nv_free(nv); + /* ...and receive reply. */ + if (hast_proto_recv(NULL, controlconn, &nv, NULL, 0) < 0) { + pjdlog_exit(EX_UNAVAILABLE, + "cannot receive reply from hastd via %s", + cfg->hc_controladdr); + } + + error = nv_get_int16(nv, "error"); + if (error != 0) { + pjdlog_exitx(EX_SOFTWARE, "Error %d received from hastd.", + error); + } + nv_set_error(nv, 0); + + switch (cmd) { + case CMD_ROLE: + error = control_set_role(nv, argv[0]); + break; + case CMD_STATUS: + error = control_status(nv); + break; + default: + assert(!"Impossible role!"); + } + + exit(error); +} |