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