xref: /freebsd/sbin/hastd/parse.y (revision b2db760808f74bb53c232900091c9da801ebbfcc)
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;
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 
65 static char depth1_provname[PATH_MAX];
66 static char depth1_localpath[PATH_MAX];
67 
68 extern void yyrestart(FILE *);
69 
70 static int
71 isitme(const char *name)
72 {
73 	char buf[MAXHOSTNAMELEN];
74 	char *pos;
75 	size_t bufsize;
76 
77 	/*
78 	 * First check if the give name matches our full hostname.
79 	 */
80 	if (gethostname(buf, sizeof(buf)) < 0) {
81 		pjdlog_errno(LOG_ERR, "gethostname() failed");
82 		return (-1);
83 	}
84 	if (strcmp(buf, name) == 0)
85 		return (1);
86 
87 	/*
88 	 * Now check if it matches first part of the host name.
89 	 */
90 	pos = strchr(buf, '.');
91 	if (pos != NULL && pos != buf && strncmp(buf, name, pos - buf) == 0)
92 		return (1);
93 
94 	/*
95 	 * At the end check if name is equal to our host's UUID.
96 	 */
97 	bufsize = sizeof(buf);
98 	if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
99 		pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
100 		return (-1);
101 	}
102 	if (strcasecmp(buf, name) == 0)
103 		return (1);
104 
105 	/*
106 	 * Looks like this isn't about us.
107 	 */
108 	return (0);
109 }
110 
111 void
112 yyerror(const char *str)
113 {
114 
115 	pjdlog_error("Unable to parse configuration file at line %d near '%s': %s",
116 	    lineno, yytext, str);
117 }
118 
119 struct hastd_config *
120 yy_config_parse(const char *config, bool exitonerror)
121 {
122 	int ret;
123 
124 	curres = NULL;
125 	mynode = false;
126 	depth = 0;
127 	lineno = 0;
128 
129 	depth0_timeout = HAST_TIMEOUT;
130 	depth0_replication = HAST_REPLICATION_MEMSYNC;
131 	strlcpy(depth0_control, HAST_CONTROL, sizeof(depth0_control));
132 	strlcpy(depth0_listen, HASTD_LISTEN, sizeof(depth0_listen));
133 
134 	lconfig = calloc(1, sizeof(*lconfig));
135 	if (lconfig == NULL) {
136 		pjdlog_error("Unable to allocate memory for configuration.");
137 		if (exitonerror)
138 			exit(EX_TEMPFAIL);
139 		return (NULL);
140 	}
141 
142 	TAILQ_INIT(&lconfig->hc_resources);
143 
144 	yyin = fopen(config, "r");
145 	if (yyin == NULL) {
146 		pjdlog_errno(LOG_ERR, "Unable to open configuration file %s",
147 		    config);
148 		yy_config_free(lconfig);
149 		if (exitonerror)
150 			exit(EX_OSFILE);
151 		return (NULL);
152 	}
153 	yyrestart(yyin);
154 	ret = yyparse();
155 	fclose(yyin);
156 	if (ret != 0) {
157 		yy_config_free(lconfig);
158 		if (exitonerror)
159 			exit(EX_CONFIG);
160 		return (NULL);
161 	}
162 
163 	/*
164 	 * Let's see if everything is set up.
165 	 */
166 	if (lconfig->hc_controladdr[0] == '\0') {
167 		strlcpy(lconfig->hc_controladdr, depth0_control,
168 		    sizeof(lconfig->hc_controladdr));
169 	}
170 	if (lconfig->hc_listenaddr[0] == '\0') {
171 		strlcpy(lconfig->hc_listenaddr, depth0_listen,
172 		    sizeof(lconfig->hc_listenaddr));
173 	}
174 	TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
175 		assert(curres->hr_provname[0] != '\0');
176 		assert(curres->hr_localpath[0] != '\0');
177 		assert(curres->hr_remoteaddr[0] != '\0');
178 
179 		if (curres->hr_replication == -1) {
180 			/*
181 			 * Replication is not set at resource-level.
182 			 * Use global or default setting.
183 			 */
184 			curres->hr_replication = depth0_replication;
185 		}
186 		if (curres->hr_timeout == -1) {
187 			/*
188 			 * Timeout is not set at resource-level.
189 			 * Use global or default setting.
190 			 */
191 			curres->hr_timeout = depth0_timeout;
192 		}
193 	}
194 
195 	return (lconfig);
196 }
197 
198 void
199 yy_config_free(struct hastd_config *config)
200 {
201 	struct hast_resource *res;
202 
203 	while ((res = TAILQ_FIRST(&config->hc_resources)) != NULL) {
204 		TAILQ_REMOVE(&config->hc_resources, res, hr_next);
205 		free(res);
206 	}
207 	free(config);
208 }
209 %}
210 
211 %token CONTROL LISTEN PORT REPLICATION TIMEOUT EXTENTSIZE RESOURCE NAME LOCAL REMOTE ON
212 %token FULLSYNC MEMSYNC ASYNC
213 %token NUM STR OB CB
214 
215 %type <num> replication_type
216 
217 %union
218 {
219 	int num;
220 	char *str;
221 }
222 
223 %token <num> NUM
224 %token <str> STR
225 
226 %%
227 
228 statements:
229 	|
230 	statements statement
231 	;
232 
233 statement:
234 	control_statement
235 	|
236 	listen_statement
237 	|
238 	replication_statement
239 	|
240 	timeout_statement
241 	|
242 	node_statement
243 	|
244 	resource_statement
245 	;
246 
247 control_statement:	CONTROL STR
248 	{
249 		switch (depth) {
250 		case 0:
251 			if (strlcpy(depth0_control, $2,
252 			    sizeof(depth0_control)) >=
253 			    sizeof(depth0_control)) {
254 				pjdlog_error("control argument is too long.");
255 				return (1);
256 			}
257 			break;
258 		case 1:
259 			if (mynode) {
260 				if (strlcpy(lconfig->hc_controladdr, $2,
261 				    sizeof(lconfig->hc_controladdr)) >=
262 				    sizeof(lconfig->hc_controladdr)) {
263 					pjdlog_error("control argument is too long.");
264 					return (1);
265 				}
266 			}
267 			break;
268 		default:
269 			assert(!"control at wrong depth level");
270 		}
271 	}
272 	;
273 
274 listen_statement:	LISTEN STR
275 	{
276 		switch (depth) {
277 		case 0:
278 			if (strlcpy(depth0_listen, $2,
279 			    sizeof(depth0_listen)) >=
280 			    sizeof(depth0_listen)) {
281 				pjdlog_error("listen argument is too long.");
282 				return (1);
283 			}
284 			break;
285 		case 1:
286 			if (mynode) {
287 				if (strlcpy(lconfig->hc_listenaddr, $2,
288 				    sizeof(lconfig->hc_listenaddr)) >=
289 				    sizeof(lconfig->hc_listenaddr)) {
290 					pjdlog_error("listen argument is too long.");
291 					return (1);
292 				}
293 			}
294 			break;
295 		default:
296 			assert(!"listen at wrong depth level");
297 		}
298 	}
299 	;
300 
301 replication_statement:	REPLICATION replication_type
302 	{
303 		switch (depth) {
304 		case 0:
305 			depth0_replication = $2;
306 			break;
307 		case 1:
308 			if (curres != NULL)
309 				curres->hr_replication = $2;
310 			break;
311 		default:
312 			assert(!"replication at wrong depth level");
313 		}
314 	}
315 	;
316 
317 replication_type:
318 	FULLSYNC	{ $$ = HAST_REPLICATION_FULLSYNC; }
319 	|
320 	MEMSYNC		{ $$ = HAST_REPLICATION_MEMSYNC; }
321 	|
322 	ASYNC		{ $$ = HAST_REPLICATION_ASYNC; }
323 	;
324 
325 timeout_statement:	TIMEOUT NUM
326 	{
327 		switch (depth) {
328 		case 0:
329 			depth0_timeout = $2;
330 			break;
331 		case 1:
332 			if (curres != NULL)
333 				curres->hr_timeout = $2;
334 			break;
335 		default:
336 			assert(!"timeout at wrong depth level");
337 		}
338 	}
339 	;
340 
341 node_statement:		ON node_start OB node_entries CB
342 	{
343 		mynode = false;
344 	}
345 	;
346 
347 node_start:	STR
348 	{
349 		switch (isitme($1)) {
350 		case -1:
351 			return (1);
352 		case 0:
353 			break;
354 		case 1:
355 			mynode = true;
356 			break;
357 		default:
358 			assert(!"invalid isitme() return value");
359 		}
360 	}
361 	;
362 
363 node_entries:
364 	|
365 	node_entries node_entry
366 	;
367 
368 node_entry:
369 	control_statement
370 	|
371 	listen_statement
372 	;
373 
374 resource_statement:	RESOURCE resource_start OB resource_entries CB
375 	{
376 		if (curres != NULL) {
377 			/*
378 			 * Let's see there are some resource-level settings
379 			 * that we can use for node-level settings.
380 			 */
381 			if (curres->hr_provname[0] == '\0' &&
382 			    depth1_provname[0] != '\0') {
383 				/*
384 				 * Provider name is not set at node-level,
385 				 * but is set at resource-level, use it.
386 				 */
387 				strlcpy(curres->hr_provname, depth1_provname,
388 				    sizeof(curres->hr_provname));
389 			}
390 			if (curres->hr_localpath[0] == '\0' &&
391 			    depth1_localpath[0] != '\0') {
392 				/*
393 				 * Path to local provider is not set at
394 				 * node-level, but is set at resource-level,
395 				 * use it.
396 				 */
397 				strlcpy(curres->hr_localpath, depth1_localpath,
398 				    sizeof(curres->hr_localpath));
399 			}
400 
401 			/*
402 			 * If provider name is not given, use resource name
403 			 * as provider name.
404 			 */
405 			if (curres->hr_provname[0] == '\0') {
406 				strlcpy(curres->hr_provname, curres->hr_name,
407 				    sizeof(curres->hr_provname));
408 			}
409 
410 			/*
411 			 * Remote address has to be configured at this point.
412 			 */
413 			if (curres->hr_remoteaddr[0] == '\0') {
414 				pjdlog_error("Remote address not configured for resource %s.",
415 				    curres->hr_name);
416 				return (1);
417 			}
418 			/*
419 			 * Path to local provider has to be configured at this
420 			 * point.
421 			 */
422 			if (curres->hr_localpath[0] == '\0') {
423 				pjdlog_error("Path to local component not configured for resource %s.",
424 				    curres->hr_name);
425 				return (1);
426 			}
427 
428 			/* Put it onto resource list. */
429 			TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next);
430 			curres = NULL;
431 		}
432 	}
433 	;
434 
435 resource_start:	STR
436 	{
437 		/*
438 		 * Clear those, so we can tell if they were set at
439 		 * resource-level or not.
440 		 */
441 		depth1_provname[0] = '\0';
442 		depth1_localpath[0] = '\0';
443 
444 		curres = calloc(1, sizeof(*curres));
445 		if (curres == NULL) {
446 			pjdlog_error("Unable to allocate memory for resource.");
447 			return (1);
448 		}
449 		if (strlcpy(curres->hr_name, $1,
450 		    sizeof(curres->hr_name)) >=
451 		    sizeof(curres->hr_name)) {
452 			pjdlog_error("Resource name is too long.");
453 			return (1);
454 		}
455 		curres->hr_role = HAST_ROLE_INIT;
456 		curres->hr_previous_role = HAST_ROLE_INIT;
457 		curres->hr_replication = -1;
458 		curres->hr_timeout = -1;
459 		curres->hr_provname[0] = '\0';
460 		curres->hr_localpath[0] = '\0';
461 		curres->hr_localfd = -1;
462 		curres->hr_remoteaddr[0] = '\0';
463 		curres->hr_ggateunit = -1;
464 	}
465 	;
466 
467 resource_entries:
468 	|
469 	resource_entries resource_entry
470 	;
471 
472 resource_entry:
473 	replication_statement
474 	|
475 	timeout_statement
476 	|
477 	name_statement
478 	|
479 	local_statement
480 	|
481 	resource_node_statement
482 	;
483 
484 name_statement:		NAME STR
485 	{
486 		switch (depth) {
487 		case 1:
488 			if (strlcpy(depth1_provname, $2,
489 			    sizeof(depth1_provname)) >=
490 			    sizeof(depth1_provname)) {
491 				pjdlog_error("name argument is too long.");
492 				return (1);
493 			}
494 			break;
495 		case 2:
496 			if (mynode) {
497 				assert(curres != NULL);
498 				if (strlcpy(curres->hr_provname, $2,
499 				    sizeof(curres->hr_provname)) >=
500 				    sizeof(curres->hr_provname)) {
501 					pjdlog_error("name argument is too long.");
502 					return (1);
503 				}
504 			}
505 			break;
506 		default:
507 			assert(!"name at wrong depth level");
508 		}
509 	}
510 	;
511 
512 local_statement:	LOCAL STR
513 	{
514 		switch (depth) {
515 		case 1:
516 			if (strlcpy(depth1_localpath, $2,
517 			    sizeof(depth1_localpath)) >=
518 			    sizeof(depth1_localpath)) {
519 				pjdlog_error("local argument is too long.");
520 				return (1);
521 			}
522 			break;
523 		case 2:
524 			if (mynode) {
525 				assert(curres != NULL);
526 				if (strlcpy(curres->hr_localpath, $2,
527 				    sizeof(curres->hr_localpath)) >=
528 				    sizeof(curres->hr_localpath)) {
529 					pjdlog_error("local argument is too long.");
530 					return (1);
531 				}
532 			}
533 			break;
534 		default:
535 			assert(!"local at wrong depth level");
536 		}
537 	}
538 	;
539 
540 resource_node_statement:ON resource_node_start OB resource_node_entries CB
541 	{
542 		mynode = false;
543 	}
544 	;
545 
546 resource_node_start:	STR
547 	{
548 		if (curres != NULL) {
549 			switch (isitme($1)) {
550 			case -1:
551 				return (1);
552 			case 0:
553 				break;
554 			case 1:
555 				mynode = true;
556 				break;
557 			default:
558 				assert(!"invalid isitme() return value");
559 			}
560 		}
561 	}
562 	;
563 
564 resource_node_entries:
565 	|
566 	resource_node_entries resource_node_entry
567 	;
568 
569 resource_node_entry:
570 	name_statement
571 	|
572 	local_statement
573 	|
574 	remote_statement
575 	;
576 
577 remote_statement:	REMOTE STR
578 	{
579 		assert(depth == 2);
580 		if (mynode) {
581 			assert(curres != NULL);
582 			if (strlcpy(curres->hr_remoteaddr, $2,
583 			    sizeof(curres->hr_remoteaddr)) >=
584 			    sizeof(curres->hr_remoteaddr)) {
585 				pjdlog_error("remote argument is too long.");
586 				return (1);
587 			}
588 		}
589 	}
590 	;
591