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