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