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