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