xref: /freebsd/sbin/hastctl/hastctl.c (revision bb15ca603fa442c72dde3f3cb8b46db6970e3950)
1 /*-
2  * Copyright (c) 2009-2010 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Pawel Jakub Dawidek under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/param.h>
34 #include <sys/disk.h>
35 #include <sys/ioctl.h>
36 #include <sys/stat.h>
37 #include <sys/sysctl.h>
38 
39 #include <err.h>
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <libutil.h>
43 #include <limits.h>
44 #include <signal.h>
45 #include <stdio.h>
46 #include <stdlib.h>
47 #include <string.h>
48 #include <sysexits.h>
49 #include <unistd.h>
50 
51 #include <activemap.h>
52 
53 #include "hast.h"
54 #include "hast_proto.h"
55 #include "metadata.h"
56 #include "nv.h"
57 #include "pjdlog.h"
58 #include "proto.h"
59 #include "subr.h"
60 
61 /* Path to configuration file. */
62 static const char *cfgpath = HAST_CONFIG;
63 /* Hastd configuration. */
64 static struct hastd_config *cfg;
65 /* Control connection. */
66 static struct proto_conn *controlconn;
67 
68 enum {
69 	CMD_INVALID,
70 	CMD_CREATE,
71 	CMD_ROLE,
72 	CMD_STATUS,
73 	CMD_DUMP
74 };
75 
76 static __dead2 void
77 usage(void)
78 {
79 
80 	fprintf(stderr,
81 	    "usage: %s create [-d] [-c config] [-e extentsize] [-k keepdirty]\n"
82 	    "\t\t[-m mediasize] name ...\n",
83 	    getprogname());
84 	fprintf(stderr,
85 	    "       %s role [-d] [-c config] <init | primary | secondary> all | name ...\n",
86 	    getprogname());
87 	fprintf(stderr,
88 	    "       %s status [-d] [-c config] [all | name ...]\n",
89 	    getprogname());
90 	fprintf(stderr,
91 	    "       %s dump [-d] [-c config] [all | name ...]\n",
92 	    getprogname());
93 	exit(EX_USAGE);
94 }
95 
96 static int
97 create_one(struct hast_resource *res, intmax_t mediasize, intmax_t extentsize,
98     intmax_t keepdirty)
99 {
100 	unsigned char *buf;
101 	size_t mapsize;
102 	int ec;
103 
104 	ec = 0;
105 	pjdlog_prefix_set("[%s] ", res->hr_name);
106 
107 	if (provinfo(res, true) < 0) {
108 		ec = EX_NOINPUT;
109 		goto end;
110 	}
111 	if (mediasize == 0)
112 		mediasize = res->hr_local_mediasize;
113 	else if (mediasize > res->hr_local_mediasize) {
114 		pjdlog_error("Provided mediasize is larger than provider %s size.",
115 		    res->hr_localpath);
116 		ec = EX_DATAERR;
117 		goto end;
118 	}
119 	if (!powerof2(res->hr_local_sectorsize)) {
120 		pjdlog_error("Sector size of provider %s is not power of 2 (%u).",
121 		    res->hr_localpath, res->hr_local_sectorsize);
122 		ec = EX_DATAERR;
123 		goto end;
124 	}
125 	if (extentsize == 0)
126 		extentsize = HAST_EXTENTSIZE;
127 	if (extentsize < res->hr_local_sectorsize) {
128 		pjdlog_error("Extent size (%jd) is less than sector size (%u).",
129 		    (intmax_t)extentsize, res->hr_local_sectorsize);
130 		ec = EX_DATAERR;
131 		goto end;
132 	}
133 	if ((extentsize % res->hr_local_sectorsize) != 0) {
134 		pjdlog_error("Extent size (%jd) is not multiple of sector size (%u).",
135 		    (intmax_t)extentsize, res->hr_local_sectorsize);
136 		ec = EX_DATAERR;
137 		goto end;
138 	}
139 	mapsize = activemap_calc_ondisk_size(mediasize - METADATA_SIZE,
140 	    extentsize, res->hr_local_sectorsize);
141 	if (keepdirty == 0)
142 		keepdirty = HAST_KEEPDIRTY;
143 	res->hr_datasize = mediasize - METADATA_SIZE - mapsize;
144 	res->hr_extentsize = extentsize;
145 	res->hr_keepdirty = keepdirty;
146 
147 	res->hr_localoff = METADATA_SIZE + mapsize;
148 
149 	if (metadata_write(res) < 0) {
150 		ec = EX_IOERR;
151 		goto end;
152 	}
153 	buf = calloc(1, mapsize);
154 	if (buf == NULL) {
155 		pjdlog_error("Unable to allocate %zu bytes of memory for initial bitmap.",
156 		    mapsize);
157 		ec = EX_TEMPFAIL;
158 		goto end;
159 	}
160 	if (pwrite(res->hr_localfd, buf, mapsize, METADATA_SIZE) !=
161 	    (ssize_t)mapsize) {
162 		pjdlog_errno(LOG_ERR, "Unable to store initial bitmap on %s",
163 		    res->hr_localpath);
164 		free(buf);
165 		ec = EX_IOERR;
166 		goto end;
167 	}
168 	free(buf);
169 end:
170 	if (res->hr_localfd >= 0)
171 		close(res->hr_localfd);
172 	pjdlog_prefix_set("%s", "");
173 	return (ec);
174 }
175 
176 static void
177 control_create(int argc, char *argv[], intmax_t mediasize, intmax_t extentsize,
178     intmax_t keepdirty)
179 {
180 	struct hast_resource *res;
181 	int ec, ii, ret;
182 
183 	/* Initialize the given resources. */
184 	if (argc < 1)
185 		usage();
186 	ec = 0;
187 	for (ii = 0; ii < argc; ii++) {
188 		TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
189 			if (strcmp(argv[ii], res->hr_name) == 0)
190 				break;
191 		}
192 		if (res == NULL) {
193 			pjdlog_error("Unknown resource %s.", argv[ii]);
194 			if (ec == 0)
195 				ec = EX_DATAERR;
196 			continue;
197 		}
198 		ret = create_one(res, mediasize, extentsize, keepdirty);
199 		if (ret != 0 && ec == 0)
200 			ec = ret;
201 	}
202 	exit(ec);
203 }
204 
205 static int
206 dump_one(struct hast_resource *res)
207 {
208 	int ret;
209 
210 	ret = metadata_read(res, false);
211 	if (ret != 0)
212 		return (ret);
213 
214 	printf("resource: %s\n", res->hr_name);
215 	printf("    datasize: %ju (%NB)\n", (uintmax_t)res->hr_datasize,
216 	    (intmax_t)res->hr_datasize);
217 	printf("    extentsize: %d (%NB)\n", res->hr_extentsize,
218 	    (intmax_t)res->hr_extentsize);
219 	printf("    keepdirty: %d\n", res->hr_keepdirty);
220 	printf("    localoff: %ju\n", (uintmax_t)res->hr_localoff);
221 	printf("    resuid: %ju\n", (uintmax_t)res->hr_resuid);
222 	printf("    localcnt: %ju\n", (uintmax_t)res->hr_primary_localcnt);
223 	printf("    remotecnt: %ju\n", (uintmax_t)res->hr_primary_remotecnt);
224 	printf("    prevrole: %s\n", role2str(res->hr_previous_role));
225 
226 	return (0);
227 }
228 
229 static void
230 control_dump(int argc, char *argv[])
231 {
232 	struct hast_resource *res;
233 	int ec, ret;
234 
235 	/* Dump metadata of the given resource(s). */
236 
237 	ec = 0;
238 	if (argc == 0 || (argc == 1 && strcmp(argv[0], "all") == 0)) {
239 		TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
240 			ret = dump_one(res);
241 			if (ret != 0 && ec == 0)
242 				ec = ret;
243 		}
244 	} else {
245 		int ii;
246 
247 		for (ii = 0; ii < argc; ii++) {
248 			TAILQ_FOREACH(res, &cfg->hc_resources, hr_next) {
249 				if (strcmp(argv[ii], res->hr_name) == 0)
250 					break;
251 			}
252 			if (res == NULL) {
253 				pjdlog_error("Unknown resource %s.", argv[ii]);
254 				if (ec == 0)
255 					ec = EX_DATAERR;
256 				continue;
257 			}
258 			ret = dump_one(res);
259 			if (ret != 0 && ec == 0)
260 				ec = ret;
261 		}
262 	}
263 	exit(ec);
264 }
265 
266 static int
267 control_set_role(struct nv *nv, const char *newrole)
268 {
269 	const char *res, *oldrole;
270 	unsigned int ii;
271 	int error, ret;
272 
273 	ret = 0;
274 
275 	for (ii = 0; ; ii++) {
276 		res = nv_get_string(nv, "resource%u", ii);
277 		if (res == NULL)
278 			break;
279 		pjdlog_prefix_set("[%s] ", res);
280 		error = nv_get_int16(nv, "error%u", ii);
281 		if (error != 0) {
282 			if (ret == 0)
283 				ret = error;
284 			pjdlog_warning("Received error %d from hastd.", error);
285 			continue;
286 		}
287 		oldrole = nv_get_string(nv, "role%u", ii);
288 		if (strcmp(oldrole, newrole) == 0)
289 			pjdlog_debug(2, "Role unchanged (%s).", oldrole);
290 		else {
291 			pjdlog_debug(1, "Role changed from %s to %s.", oldrole,
292 			    newrole);
293 		}
294 	}
295 	pjdlog_prefix_set("%s", "");
296 	return (ret);
297 }
298 
299 static int
300 control_status(struct nv *nv)
301 {
302 	unsigned int ii;
303 	const char *str;
304 	int error, ret;
305 
306 	ret = 0;
307 
308 	for (ii = 0; ; ii++) {
309 		str = nv_get_string(nv, "resource%u", ii);
310 		if (str == NULL)
311 			break;
312 		printf("%s:\n", str);
313 		error = nv_get_int16(nv, "error%u", ii);
314 		if (error != 0) {
315 			if (ret == 0)
316 				ret = error;
317 			printf("  error: %d\n", error);
318 			continue;
319 		}
320 		printf("  role: %s\n", nv_get_string(nv, "role%u", ii));
321 		printf("  provname: %s\n",
322 		    nv_get_string(nv, "provname%u", ii));
323 		printf("  localpath: %s\n",
324 		    nv_get_string(nv, "localpath%u", ii));
325 		printf("  extentsize: %u (%NB)\n",
326 		    (unsigned int)nv_get_uint32(nv, "extentsize%u", ii),
327 		    (intmax_t)nv_get_uint32(nv, "extentsize%u", ii));
328 		printf("  keepdirty: %u\n",
329 		    (unsigned int)nv_get_uint32(nv, "keepdirty%u", ii));
330 		printf("  remoteaddr: %s\n",
331 		    nv_get_string(nv, "remoteaddr%u", ii));
332 		str = nv_get_string(nv, "sourceaddr%u", ii);
333 		if (str != NULL)
334 			printf("  sourceaddr: %s\n", str);
335 		printf("  replication: %s\n",
336 		    nv_get_string(nv, "replication%u", ii));
337 		str = nv_get_string(nv, "status%u", ii);
338 		if (str != NULL)
339 			printf("  status: %s\n", str);
340 		printf("  dirty: %ju (%NB)\n",
341 		    (uintmax_t)nv_get_uint64(nv, "dirty%u", ii),
342 		    (intmax_t)nv_get_uint64(nv, "dirty%u", ii));
343 		printf("  statistics:\n");
344 		printf("    reads: %ju\n",
345 		    (uint64_t)nv_get_uint64(nv, "stat_read%u", ii));
346 		printf("    writes: %ju\n",
347 		    (uint64_t)nv_get_uint64(nv, "stat_write%u", ii));
348 		printf("    deletes: %ju\n",
349 		    (uint64_t)nv_get_uint64(nv, "stat_delete%u", ii));
350 		printf("    flushes: %ju\n",
351 		    (uint64_t)nv_get_uint64(nv, "stat_flush%u", ii));
352 		printf("    activemap updates: %ju\n",
353 		    (uint64_t)nv_get_uint64(nv, "stat_activemap_update%u", ii));
354 	}
355 	return (ret);
356 }
357 
358 int
359 main(int argc, char *argv[])
360 {
361 	struct nv *nv;
362 	int64_t mediasize, extentsize, keepdirty;
363 	int cmd, debug, error, ii;
364 	const char *optstr;
365 
366 	debug = 0;
367 	mediasize = extentsize = keepdirty = 0;
368 
369 	if (argc == 1)
370 		usage();
371 
372 	if (strcmp(argv[1], "create") == 0) {
373 		cmd = CMD_CREATE;
374 		optstr = "c:de:k:m:h";
375 	} else if (strcmp(argv[1], "role") == 0) {
376 		cmd = CMD_ROLE;
377 		optstr = "c:dh";
378 	} else if (strcmp(argv[1], "status") == 0) {
379 		cmd = CMD_STATUS;
380 		optstr = "c:dh";
381 	} else if (strcmp(argv[1], "dump") == 0) {
382 		cmd = CMD_DUMP;
383 		optstr = "c:dh";
384 	} else
385 		usage();
386 
387 	argc--;
388 	argv++;
389 
390 	for (;;) {
391 		int ch;
392 
393 		ch = getopt(argc, argv, optstr);
394 		if (ch == -1)
395 			break;
396 		switch (ch) {
397 		case 'c':
398 			cfgpath = optarg;
399 			break;
400 		case 'd':
401 			debug++;
402 			break;
403 		case 'e':
404 			if (expand_number(optarg, &extentsize) < 0)
405 				errx(EX_USAGE, "Invalid extentsize");
406 			break;
407 		case 'k':
408 			if (expand_number(optarg, &keepdirty) < 0)
409 				errx(EX_USAGE, "Invalid keepdirty");
410 			break;
411 		case 'm':
412 			if (expand_number(optarg, &mediasize) < 0)
413 				errx(EX_USAGE, "Invalid mediasize");
414 			break;
415 		case 'h':
416 		default:
417 			usage();
418 		}
419 	}
420 	argc -= optind;
421 	argv += optind;
422 
423 	switch (cmd) {
424 	case CMD_CREATE:
425 	case CMD_ROLE:
426 		if (argc == 0)
427 			usage();
428 		break;
429 	}
430 
431 	pjdlog_init(PJDLOG_MODE_STD);
432 	pjdlog_debug_set(debug);
433 
434 	cfg = yy_config_parse(cfgpath, true);
435 	PJDLOG_ASSERT(cfg != NULL);
436 
437 	switch (cmd) {
438 	case CMD_CREATE:
439 		control_create(argc, argv, mediasize, extentsize, keepdirty);
440 		/* NOTREACHED */
441 		PJDLOG_ABORT("What are we doing here?!");
442 		break;
443 	case CMD_DUMP:
444 		/* Dump metadata from local component of the given resource. */
445 		control_dump(argc, argv);
446 		/* NOTREACHED */
447 		PJDLOG_ABORT("What are we doing here?!");
448 		break;
449 	case CMD_ROLE:
450 		/* Change role for the given resources. */
451 		if (argc < 2)
452 			usage();
453 		nv = nv_alloc();
454 		nv_add_uint8(nv, HASTCTL_CMD_SETROLE, "cmd");
455 		if (strcmp(argv[0], "init") == 0)
456 			nv_add_uint8(nv, HAST_ROLE_INIT, "role");
457 		else if (strcmp(argv[0], "primary") == 0)
458 			nv_add_uint8(nv, HAST_ROLE_PRIMARY, "role");
459 		else if (strcmp(argv[0], "secondary") == 0)
460 			nv_add_uint8(nv, HAST_ROLE_SECONDARY, "role");
461 		else
462 			usage();
463 		for (ii = 0; ii < argc - 1; ii++)
464 			nv_add_string(nv, argv[ii + 1], "resource%d", ii);
465 		break;
466 	case CMD_STATUS:
467 		/* Obtain status of the given resources. */
468 		nv = nv_alloc();
469 		nv_add_uint8(nv, HASTCTL_CMD_STATUS, "cmd");
470 		if (argc == 0)
471 			nv_add_string(nv, "all", "resource%d", 0);
472 		else {
473 			for (ii = 0; ii < argc; ii++)
474 				nv_add_string(nv, argv[ii], "resource%d", ii);
475 		}
476 		break;
477 	default:
478 		PJDLOG_ABORT("Impossible command!");
479 	}
480 
481 	/* Setup control connection... */
482 	if (proto_client(NULL, cfg->hc_controladdr, &controlconn) < 0) {
483 		pjdlog_exit(EX_OSERR,
484 		    "Unable to setup control connection to %s",
485 		    cfg->hc_controladdr);
486 	}
487 	/* ...and connect to hastd. */
488 	if (proto_connect(controlconn, HAST_TIMEOUT) < 0) {
489 		pjdlog_exit(EX_OSERR, "Unable to connect to hastd via %s",
490 		    cfg->hc_controladdr);
491 	}
492 
493 	if (drop_privs(NULL) != 0)
494 		exit(EX_CONFIG);
495 
496 	/* Send the command to the server... */
497 	if (hast_proto_send(NULL, controlconn, nv, NULL, 0) < 0) {
498 		pjdlog_exit(EX_UNAVAILABLE,
499 		    "Unable to send command to hastd via %s",
500 		    cfg->hc_controladdr);
501 	}
502 	nv_free(nv);
503 	/* ...and receive reply. */
504 	if (hast_proto_recv_hdr(controlconn, &nv) < 0) {
505 		pjdlog_exit(EX_UNAVAILABLE,
506 		    "cannot receive reply from hastd via %s",
507 		    cfg->hc_controladdr);
508 	}
509 
510 	error = nv_get_int16(nv, "error");
511 	if (error != 0) {
512 		pjdlog_exitx(EX_SOFTWARE, "Error %d received from hastd.",
513 		    error);
514 	}
515 	nv_set_error(nv, 0);
516 
517 	switch (cmd) {
518 	case CMD_ROLE:
519 		error = control_set_role(nv, argv[0]);
520 		break;
521 	case CMD_STATUS:
522 		error = control_status(nv);
523 		break;
524 	default:
525 		PJDLOG_ABORT("Impossible command!");
526 	}
527 
528 	exit(error);
529 }
530