xref: /freebsd/sbin/hastd/parse.y (revision 10b9d77bf1ccf2f3affafa6261692cb92cf7e992)
1 %{
2 /*-
3  * Copyright (c) 2009-2010 The FreeBSD Foundation
4  * All rights reserved.
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  * $FreeBSD$
31  */
32 
33 #include <sys/param.h>	/* MAXHOSTNAMELEN */
34 #include <sys/queue.h>
35 #include <sys/sysctl.h>
36 
37 #include <arpa/inet.h>
38 
39 #include <assert.h>
40 #include <err.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <sysexits.h>
44 #include <unistd.h>
45 
46 #include <pjdlog.h>
47 
48 #include "hast.h"
49 
50 extern int depth;
51 extern int lineno;
52 
53 extern FILE *yyin;
54 extern char *yytext;
55 
56 static struct hastd_config *lconfig;
57 static struct hast_resource *curres;
58 static bool mynode, hadmynode;
59 
60 static char depth0_control[HAST_ADDRSIZE];
61 static char depth0_listen[HAST_ADDRSIZE];
62 static int depth0_replication;
63 static int depth0_timeout;
64 static char depth0_exec[PATH_MAX];
65 
66 static char depth1_provname[PATH_MAX];
67 static char depth1_localpath[PATH_MAX];
68 
69 extern void yyrestart(FILE *);
70 
71 static int
72 isitme(const char *name)
73 {
74 	char buf[MAXHOSTNAMELEN];
75 	char *pos;
76 	size_t bufsize;
77 
78 	/*
79 	 * First check if the give name matches our full hostname.
80 	 */
81 	if (gethostname(buf, sizeof(buf)) < 0) {
82 		pjdlog_errno(LOG_ERR, "gethostname() failed");
83 		return (-1);
84 	}
85 	if (strcmp(buf, name) == 0)
86 		return (1);
87 
88 	/*
89 	 * Now check if it matches first part of the host name.
90 	 */
91 	pos = strchr(buf, '.');
92 	if (pos != NULL && pos != buf && strncmp(buf, name, pos - buf) == 0)
93 		return (1);
94 
95 	/*
96 	 * At the end check if name is equal to our host's UUID.
97 	 */
98 	bufsize = sizeof(buf);
99 	if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
100 		pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
101 		return (-1);
102 	}
103 	if (strcasecmp(buf, name) == 0)
104 		return (1);
105 
106 	/*
107 	 * Looks like this isn't about us.
108 	 */
109 	return (0);
110 }
111 
112 static int
113 node_names(char **namesp)
114 {
115 	static char names[MAXHOSTNAMELEN * 3];
116 	char buf[MAXHOSTNAMELEN];
117 	char *pos;
118 	size_t bufsize;
119 
120 	if (gethostname(buf, sizeof(buf)) < 0) {
121 		pjdlog_errno(LOG_ERR, "gethostname() failed");
122 		return (-1);
123 	}
124 
125 	/* First component of the host name. */
126 	pos = strchr(buf, '.');
127 	if (pos != NULL && pos != buf) {
128 		(void)strlcpy(names, buf, MIN((size_t)(pos - buf + 1),
129 		    sizeof(names)));
130 		(void)strlcat(names, ", ", sizeof(names));
131 	}
132 
133 	/* Full host name. */
134 	(void)strlcat(names, buf, sizeof(names));
135 	(void)strlcat(names, ", ", sizeof(names));
136 
137 	/* Host UUID. */
138 	bufsize = sizeof(buf);
139 	if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
140 		pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
141 		return (-1);
142 	}
143 	(void)strlcat(names, buf, sizeof(names));
144 
145 	*namesp = names;
146 
147 	return (0);
148 }
149 
150 void
151 yyerror(const char *str)
152 {
153 
154 	pjdlog_error("Unable to parse configuration file at line %d near '%s': %s",
155 	    lineno, yytext, str);
156 }
157 
158 struct hastd_config *
159 yy_config_parse(const char *config, bool exitonerror)
160 {
161 	int ret;
162 
163 	curres = NULL;
164 	mynode = false;
165 	depth = 0;
166 	lineno = 0;
167 
168 	depth0_timeout = HAST_TIMEOUT;
169 	depth0_replication = HAST_REPLICATION_MEMSYNC;
170 	strlcpy(depth0_control, HAST_CONTROL, sizeof(depth0_control));
171 	strlcpy(depth0_listen, HASTD_LISTEN, sizeof(depth0_listen));
172 	depth0_exec[0] = '\0';
173 
174 	lconfig = calloc(1, sizeof(*lconfig));
175 	if (lconfig == NULL) {
176 		pjdlog_error("Unable to allocate memory for configuration.");
177 		if (exitonerror)
178 			exit(EX_TEMPFAIL);
179 		return (NULL);
180 	}
181 
182 	TAILQ_INIT(&lconfig->hc_resources);
183 
184 	yyin = fopen(config, "r");
185 	if (yyin == NULL) {
186 		pjdlog_errno(LOG_ERR, "Unable to open configuration file %s",
187 		    config);
188 		yy_config_free(lconfig);
189 		if (exitonerror)
190 			exit(EX_OSFILE);
191 		return (NULL);
192 	}
193 	yyrestart(yyin);
194 	ret = yyparse();
195 	fclose(yyin);
196 	if (ret != 0) {
197 		yy_config_free(lconfig);
198 		if (exitonerror)
199 			exit(EX_CONFIG);
200 		return (NULL);
201 	}
202 
203 	/*
204 	 * Let's see if everything is set up.
205 	 */
206 	if (lconfig->hc_controladdr[0] == '\0') {
207 		strlcpy(lconfig->hc_controladdr, depth0_control,
208 		    sizeof(lconfig->hc_controladdr));
209 	}
210 	if (lconfig->hc_listenaddr[0] == '\0') {
211 		strlcpy(lconfig->hc_listenaddr, depth0_listen,
212 		    sizeof(lconfig->hc_listenaddr));
213 	}
214 	TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
215 		assert(curres->hr_provname[0] != '\0');
216 		assert(curres->hr_localpath[0] != '\0');
217 		assert(curres->hr_remoteaddr[0] != '\0');
218 
219 		if (curres->hr_replication == -1) {
220 			/*
221 			 * Replication is not set at resource-level.
222 			 * Use global or default setting.
223 			 */
224 			curres->hr_replication = depth0_replication;
225 		}
226 		if (curres->hr_timeout == -1) {
227 			/*
228 			 * Timeout is not set at resource-level.
229 			 * Use global or default setting.
230 			 */
231 			curres->hr_timeout = depth0_timeout;
232 		}
233 		if (curres->hr_exec[0] == '\0') {
234 			/*
235 			 * Exec is not set at resource-level.
236 			 * Use global or default setting.
237 			 */
238 			strlcpy(curres->hr_exec, depth0_exec,
239 			    sizeof(curres->hr_exec));
240 		}
241 	}
242 
243 	return (lconfig);
244 }
245 
246 void
247 yy_config_free(struct hastd_config *config)
248 {
249 	struct hast_resource *res;
250 
251 	while ((res = TAILQ_FIRST(&config->hc_resources)) != NULL) {
252 		TAILQ_REMOVE(&config->hc_resources, res, hr_next);
253 		free(res);
254 	}
255 	free(config);
256 }
257 %}
258 
259 %token CONTROL LISTEN PORT REPLICATION TIMEOUT EXEC EXTENTSIZE RESOURCE NAME LOCAL REMOTE ON
260 %token FULLSYNC MEMSYNC ASYNC
261 %token NUM STR OB CB
262 
263 %type <num> replication_type
264 
265 %union
266 {
267 	int num;
268 	char *str;
269 }
270 
271 %token <num> NUM
272 %token <str> STR
273 
274 %%
275 
276 statements:
277 	|
278 	statements statement
279 	;
280 
281 statement:
282 	control_statement
283 	|
284 	listen_statement
285 	|
286 	replication_statement
287 	|
288 	timeout_statement
289 	|
290 	exec_statement
291 	|
292 	node_statement
293 	|
294 	resource_statement
295 	;
296 
297 control_statement:	CONTROL STR
298 	{
299 		switch (depth) {
300 		case 0:
301 			if (strlcpy(depth0_control, $2,
302 			    sizeof(depth0_control)) >=
303 			    sizeof(depth0_control)) {
304 				pjdlog_error("control argument is too long.");
305 				free($2);
306 				return (1);
307 			}
308 			break;
309 		case 1:
310 			if (!mynode)
311 				break;
312 			if (strlcpy(lconfig->hc_controladdr, $2,
313 			    sizeof(lconfig->hc_controladdr)) >=
314 			    sizeof(lconfig->hc_controladdr)) {
315 				pjdlog_error("control argument is too long.");
316 				free($2);
317 				return (1);
318 			}
319 			break;
320 		default:
321 			assert(!"control at wrong depth level");
322 		}
323 		free($2);
324 	}
325 	;
326 
327 listen_statement:	LISTEN STR
328 	{
329 		switch (depth) {
330 		case 0:
331 			if (strlcpy(depth0_listen, $2,
332 			    sizeof(depth0_listen)) >=
333 			    sizeof(depth0_listen)) {
334 				pjdlog_error("listen argument is too long.");
335 				free($2);
336 				return (1);
337 			}
338 			break;
339 		case 1:
340 			if (!mynode)
341 				break;
342 			if (strlcpy(lconfig->hc_listenaddr, $2,
343 			    sizeof(lconfig->hc_listenaddr)) >=
344 			    sizeof(lconfig->hc_listenaddr)) {
345 				pjdlog_error("listen argument is too long.");
346 				free($2);
347 				return (1);
348 			}
349 			break;
350 		default:
351 			assert(!"listen at wrong depth level");
352 		}
353 		free($2);
354 	}
355 	;
356 
357 replication_statement:	REPLICATION replication_type
358 	{
359 		switch (depth) {
360 		case 0:
361 			depth0_replication = $2;
362 			break;
363 		case 1:
364 			if (curres != NULL)
365 				curres->hr_replication = $2;
366 			break;
367 		default:
368 			assert(!"replication at wrong depth level");
369 		}
370 	}
371 	;
372 
373 replication_type:
374 	FULLSYNC	{ $$ = HAST_REPLICATION_FULLSYNC; }
375 	|
376 	MEMSYNC		{ $$ = HAST_REPLICATION_MEMSYNC; }
377 	|
378 	ASYNC		{ $$ = HAST_REPLICATION_ASYNC; }
379 	;
380 
381 timeout_statement:	TIMEOUT NUM
382 	{
383 		switch (depth) {
384 		case 0:
385 			depth0_timeout = $2;
386 			break;
387 		case 1:
388 			if (curres != NULL)
389 				curres->hr_timeout = $2;
390 			break;
391 		default:
392 			assert(!"timeout at wrong depth level");
393 		}
394 	}
395 	;
396 
397 exec_statement:		EXEC STR
398 	{
399 		switch (depth) {
400 		case 0:
401 			if (strlcpy(depth0_exec, $2, sizeof(depth0_exec)) >=
402 			    sizeof(depth0_exec)) {
403 				pjdlog_error("Exec path is too long.");
404 				free($2);
405 				return (1);
406 			}
407 			break;
408 		case 1:
409 			if (curres == NULL)
410 				break;
411 			if (strlcpy(curres->hr_exec, $2,
412 			    sizeof(curres->hr_exec)) >=
413 			    sizeof(curres->hr_exec)) {
414 				pjdlog_error("Exec path is too long.");
415 				free($2);
416 				return (1);
417 			}
418 			break;
419 		default:
420 			assert(!"exec at wrong depth level");
421 		}
422 		free($2);
423 	}
424 	;
425 
426 node_statement:		ON node_start OB node_entries CB
427 	{
428 		mynode = false;
429 	}
430 	;
431 
432 node_start:	STR
433 	{
434 		switch (isitme($1)) {
435 		case -1:
436 			free($1);
437 			return (1);
438 		case 0:
439 			break;
440 		case 1:
441 			mynode = true;
442 			break;
443 		default:
444 			assert(!"invalid isitme() return value");
445 		}
446 		free($1);
447 	}
448 	;
449 
450 node_entries:
451 	|
452 	node_entries node_entry
453 	;
454 
455 node_entry:
456 	control_statement
457 	|
458 	listen_statement
459 	;
460 
461 resource_statement:	RESOURCE resource_start OB resource_entries CB
462 	{
463 		if (curres != NULL) {
464 			/*
465 			 * There must be section for this node, at least with
466 			 * remote address configuration.
467 			 */
468 			if (!hadmynode) {
469 				char *names;
470 
471 				if (node_names(&names) != 0)
472 					return (1);
473 				pjdlog_error("No resource %s configuration for this node (acceptable node names: %s).",
474 				    curres->hr_name, names);
475 				return (1);
476 			}
477 
478 			/*
479 			 * Let's see there are some resource-level settings
480 			 * that we can use for node-level settings.
481 			 */
482 			if (curres->hr_provname[0] == '\0' &&
483 			    depth1_provname[0] != '\0') {
484 				/*
485 				 * Provider name is not set at node-level,
486 				 * but is set at resource-level, use it.
487 				 */
488 				strlcpy(curres->hr_provname, depth1_provname,
489 				    sizeof(curres->hr_provname));
490 			}
491 			if (curres->hr_localpath[0] == '\0' &&
492 			    depth1_localpath[0] != '\0') {
493 				/*
494 				 * Path to local provider is not set at
495 				 * node-level, but is set at resource-level,
496 				 * use it.
497 				 */
498 				strlcpy(curres->hr_localpath, depth1_localpath,
499 				    sizeof(curres->hr_localpath));
500 			}
501 
502 			/*
503 			 * If provider name is not given, use resource name
504 			 * as provider name.
505 			 */
506 			if (curres->hr_provname[0] == '\0') {
507 				strlcpy(curres->hr_provname, curres->hr_name,
508 				    sizeof(curres->hr_provname));
509 			}
510 
511 			/*
512 			 * Remote address has to be configured at this point.
513 			 */
514 			if (curres->hr_remoteaddr[0] == '\0') {
515 				pjdlog_error("Remote address not configured for resource %s.",
516 				    curres->hr_name);
517 				return (1);
518 			}
519 			/*
520 			 * Path to local provider has to be configured at this
521 			 * point.
522 			 */
523 			if (curres->hr_localpath[0] == '\0') {
524 				pjdlog_error("Path to local component not configured for resource %s.",
525 				    curres->hr_name);
526 				return (1);
527 			}
528 
529 			/* Put it onto resource list. */
530 			TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next);
531 			curres = NULL;
532 		}
533 	}
534 	;
535 
536 resource_start:	STR
537 	{
538 		/* Check if there is no duplicate entry. */
539 		TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
540 			if (strcmp(curres->hr_name, $1) == 0) {
541 				pjdlog_error("Resource %s configured more than once.",
542 				    curres->hr_name);
543 				free($1);
544 				return (1);
545 			}
546 		}
547 
548 		/*
549 		 * Clear those, so we can tell if they were set at
550 		 * resource-level or not.
551 		 */
552 		depth1_provname[0] = '\0';
553 		depth1_localpath[0] = '\0';
554 		hadmynode = false;
555 
556 		curres = calloc(1, sizeof(*curres));
557 		if (curres == NULL) {
558 			pjdlog_error("Unable to allocate memory for resource.");
559 			free($1);
560 			return (1);
561 		}
562 		if (strlcpy(curres->hr_name, $1,
563 		    sizeof(curres->hr_name)) >=
564 		    sizeof(curres->hr_name)) {
565 			pjdlog_error("Resource name is too long.");
566 			free($1);
567 			return (1);
568 		}
569 		free($1);
570 		curres->hr_role = HAST_ROLE_INIT;
571 		curres->hr_previous_role = HAST_ROLE_INIT;
572 		curres->hr_replication = -1;
573 		curres->hr_timeout = -1;
574 		curres->hr_exec[0] = '\0';
575 		curres->hr_provname[0] = '\0';
576 		curres->hr_localpath[0] = '\0';
577 		curres->hr_localfd = -1;
578 		curres->hr_remoteaddr[0] = '\0';
579 		curres->hr_ggateunit = -1;
580 	}
581 	;
582 
583 resource_entries:
584 	|
585 	resource_entries resource_entry
586 	;
587 
588 resource_entry:
589 	replication_statement
590 	|
591 	timeout_statement
592 	|
593 	exec_statement
594 	|
595 	name_statement
596 	|
597 	local_statement
598 	|
599 	resource_node_statement
600 	;
601 
602 name_statement:		NAME STR
603 	{
604 		switch (depth) {
605 		case 1:
606 			if (strlcpy(depth1_provname, $2,
607 			    sizeof(depth1_provname)) >=
608 			    sizeof(depth1_provname)) {
609 				pjdlog_error("name argument is too long.");
610 				free($2);
611 				return (1);
612 			}
613 			break;
614 		case 2:
615 			if (!mynode)
616 				break;
617 			assert(curres != NULL);
618 			if (strlcpy(curres->hr_provname, $2,
619 			    sizeof(curres->hr_provname)) >=
620 			    sizeof(curres->hr_provname)) {
621 				pjdlog_error("name argument is too long.");
622 				free($2);
623 				return (1);
624 			}
625 			break;
626 		default:
627 			assert(!"name at wrong depth level");
628 		}
629 		free($2);
630 	}
631 	;
632 
633 local_statement:	LOCAL STR
634 	{
635 		switch (depth) {
636 		case 1:
637 			if (strlcpy(depth1_localpath, $2,
638 			    sizeof(depth1_localpath)) >=
639 			    sizeof(depth1_localpath)) {
640 				pjdlog_error("local argument is too long.");
641 				free($2);
642 				return (1);
643 			}
644 			break;
645 		case 2:
646 			if (!mynode)
647 				break;
648 			assert(curres != NULL);
649 			if (strlcpy(curres->hr_localpath, $2,
650 			    sizeof(curres->hr_localpath)) >=
651 			    sizeof(curres->hr_localpath)) {
652 				pjdlog_error("local argument is too long.");
653 				free($2);
654 				return (1);
655 			}
656 			break;
657 		default:
658 			assert(!"local at wrong depth level");
659 		}
660 		free($2);
661 	}
662 	;
663 
664 resource_node_statement:ON resource_node_start OB resource_node_entries CB
665 	{
666 		mynode = false;
667 	}
668 	;
669 
670 resource_node_start:	STR
671 	{
672 		if (curres != NULL) {
673 			switch (isitme($1)) {
674 			case -1:
675 				free($1);
676 				return (1);
677 			case 0:
678 				break;
679 			case 1:
680 				mynode = hadmynode = true;
681 				break;
682 			default:
683 				assert(!"invalid isitme() return value");
684 			}
685 		}
686 		free($1);
687 	}
688 	;
689 
690 resource_node_entries:
691 	|
692 	resource_node_entries resource_node_entry
693 	;
694 
695 resource_node_entry:
696 	name_statement
697 	|
698 	local_statement
699 	|
700 	remote_statement
701 	;
702 
703 remote_statement:	REMOTE STR
704 	{
705 		assert(depth == 2);
706 		if (mynode) {
707 			assert(curres != NULL);
708 			if (strlcpy(curres->hr_remoteaddr, $2,
709 			    sizeof(curres->hr_remoteaddr)) >=
710 			    sizeof(curres->hr_remoteaddr)) {
711 				pjdlog_error("remote argument is too long.");
712 				free($2);
713 				return (1);
714 			}
715 		}
716 		free($2);
717 	}
718 	;
719