520 lines
13 KiB
C
520 lines
13 KiB
C
/*-
|
|
* 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 <libutil.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 (%NB)\n", (uintmax_t)res->hr_datasize,
|
|
(intmax_t)res->hr_datasize);
|
|
printf(" extentsize: %d (%NB)\n", res->hr_extentsize,
|
|
(intmax_t)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 (%NB)\n",
|
|
(unsigned int)nv_get_uint32(nv, "extentsize%u", ii),
|
|
(intmax_t)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));
|
|
str = nv_get_string(nv, "sourceaddr%u", ii);
|
|
if (str != NULL)
|
|
printf(" sourceaddr: %s\n", str);
|
|
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 (%NB)\n",
|
|
(uintmax_t)nv_get_uint64(nv, "dirty%u", ii),
|
|
(intmax_t)nv_get_uint64(nv, "dirty%u", ii));
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
struct nv *nv;
|
|
int64_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 (expand_number(optarg, &extentsize) < 0)
|
|
err(1, "Invalid extentsize");
|
|
break;
|
|
case 'k':
|
|
if (expand_number(optarg, &keepdirty) < 0)
|
|
err(1, "Invalid keepdirty");
|
|
break;
|
|
case 'm':
|
|
if (expand_number(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_init(PJDLOG_MODE_STD);
|
|
pjdlog_debug_set(debug);
|
|
|
|
cfg = yy_config_parse(cfgpath, true);
|
|
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 command!");
|
|
}
|
|
|
|
/* Setup control connection... */
|
|
if (proto_client(NULL, 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, HAST_TIMEOUT) < 0) {
|
|
pjdlog_exit(EX_OSERR, "Unable to connect to hastd via %s",
|
|
cfg->hc_controladdr);
|
|
}
|
|
|
|
if (drop_privs(true) != 0)
|
|
exit(EX_CONFIG);
|
|
|
|
/* 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_hdr(controlconn, &nv) < 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 command!");
|
|
}
|
|
|
|
exit(error);
|
|
}
|