parse.y revision 221632
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 221632 2011-05-08 09:31:17Z trociny $
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 <num> replication_type
293%type <num> checksum_type
294%type <num> compression_type
295
296%union
297{
298	int num;
299	char *str;
300}
301
302%token <num> NUM
303%token <str> STR
304
305%%
306
307statements:
308	|
309	statements statement
310	;
311
312statement:
313	control_statement
314	|
315	listen_statement
316	|
317	replication_statement
318	|
319	checksum_statement
320	|
321	compression_statement
322	|
323	timeout_statement
324	|
325	exec_statement
326	|
327	node_statement
328	|
329	resource_statement
330	;
331
332control_statement:	CONTROL STR
333	{
334		switch (depth) {
335		case 0:
336			if (strlcpy(depth0_control, $2,
337			    sizeof(depth0_control)) >=
338			    sizeof(depth0_control)) {
339				pjdlog_error("control argument is too long.");
340				free($2);
341				return (1);
342			}
343			break;
344		case 1:
345			if (!mynode)
346				break;
347			if (strlcpy(lconfig->hc_controladdr, $2,
348			    sizeof(lconfig->hc_controladdr)) >=
349			    sizeof(lconfig->hc_controladdr)) {
350				pjdlog_error("control argument is too long.");
351				free($2);
352				return (1);
353			}
354			break;
355		default:
356			assert(!"control at wrong depth level");
357		}
358		free($2);
359	}
360	;
361
362listen_statement:	LISTEN STR
363	{
364		switch (depth) {
365		case 0:
366			if (strlcpy(depth0_listen, $2,
367			    sizeof(depth0_listen)) >=
368			    sizeof(depth0_listen)) {
369				pjdlog_error("listen argument is too long.");
370				free($2);
371				return (1);
372			}
373			break;
374		case 1:
375			if (!mynode)
376				break;
377			if (strlcpy(lconfig->hc_listenaddr, $2,
378			    sizeof(lconfig->hc_listenaddr)) >=
379			    sizeof(lconfig->hc_listenaddr)) {
380				pjdlog_error("listen argument is too long.");
381				free($2);
382				return (1);
383			}
384			break;
385		default:
386			assert(!"listen at wrong depth level");
387		}
388		free($2);
389	}
390	;
391
392replication_statement:	REPLICATION replication_type
393	{
394		switch (depth) {
395		case 0:
396			depth0_replication = $2;
397			break;
398		case 1:
399			if (curres != NULL)
400				curres->hr_replication = $2;
401			break;
402		default:
403			assert(!"replication at wrong depth level");
404		}
405	}
406	;
407
408replication_type:
409	FULLSYNC	{ $$ = HAST_REPLICATION_FULLSYNC; }
410	|
411	MEMSYNC		{ $$ = HAST_REPLICATION_MEMSYNC; }
412	|
413	ASYNC		{ $$ = HAST_REPLICATION_ASYNC; }
414	;
415
416checksum_statement:	CHECKSUM checksum_type
417	{
418		switch (depth) {
419		case 0:
420			depth0_checksum = $2;
421			break;
422		case 1:
423			if (curres != NULL)
424				curres->hr_checksum = $2;
425			break;
426		default:
427			assert(!"checksum at wrong depth level");
428		}
429	}
430	;
431
432checksum_type:
433	NONE		{ $$ = HAST_CHECKSUM_NONE; }
434	|
435	CRC32		{ $$ = HAST_CHECKSUM_CRC32; }
436	|
437	SHA256		{ $$ = HAST_CHECKSUM_SHA256; }
438	;
439
440compression_statement:	COMPRESSION compression_type
441	{
442		switch (depth) {
443		case 0:
444			depth0_compression = $2;
445			break;
446		case 1:
447			if (curres != NULL)
448				curres->hr_compression = $2;
449			break;
450		default:
451			assert(!"compression at wrong depth level");
452		}
453	}
454	;
455
456compression_type:
457	NONE		{ $$ = HAST_COMPRESSION_NONE; }
458	|
459	HOLE		{ $$ = HAST_COMPRESSION_HOLE; }
460	|
461	LZF		{ $$ = HAST_COMPRESSION_LZF; }
462	;
463
464timeout_statement:	TIMEOUT NUM
465	{
466		if ($2 <= 0) {
467			pjdlog_error("Negative or zero timeout.");
468			return (1);
469		}
470		switch (depth) {
471		case 0:
472			depth0_timeout = $2;
473			break;
474		case 1:
475			if (curres != NULL)
476				curres->hr_timeout = $2;
477			break;
478		default:
479			assert(!"timeout at wrong depth level");
480		}
481	}
482	;
483
484exec_statement:		EXEC STR
485	{
486		switch (depth) {
487		case 0:
488			if (strlcpy(depth0_exec, $2, sizeof(depth0_exec)) >=
489			    sizeof(depth0_exec)) {
490				pjdlog_error("Exec path is too long.");
491				free($2);
492				return (1);
493			}
494			break;
495		case 1:
496			if (curres == NULL)
497				break;
498			if (strlcpy(curres->hr_exec, $2,
499			    sizeof(curres->hr_exec)) >=
500			    sizeof(curres->hr_exec)) {
501				pjdlog_error("Exec path is too long.");
502				free($2);
503				return (1);
504			}
505			break;
506		default:
507			assert(!"exec at wrong depth level");
508		}
509		free($2);
510	}
511	;
512
513node_statement:		ON node_start OB node_entries CB
514	{
515		mynode = false;
516	}
517	;
518
519node_start:	STR
520	{
521		switch (isitme($1)) {
522		case -1:
523			free($1);
524			return (1);
525		case 0:
526			break;
527		case 1:
528			mynode = true;
529			break;
530		default:
531			assert(!"invalid isitme() return value");
532		}
533		free($1);
534	}
535	;
536
537node_entries:
538	|
539	node_entries node_entry
540	;
541
542node_entry:
543	control_statement
544	|
545	listen_statement
546	;
547
548resource_statement:	RESOURCE resource_start OB resource_entries CB
549	{
550		if (curres != NULL) {
551			/*
552			 * There must be section for this node, at least with
553			 * remote address configuration.
554			 */
555			if (!hadmynode) {
556				char *names;
557
558				if (node_names(&names) != 0)
559					return (1);
560				pjdlog_error("No resource %s configuration for this node (acceptable node names: %s).",
561				    curres->hr_name, names);
562				return (1);
563			}
564
565			/*
566			 * Let's see there are some resource-level settings
567			 * that we can use for node-level settings.
568			 */
569			if (curres->hr_provname[0] == '\0' &&
570			    depth1_provname[0] != '\0') {
571				/*
572				 * Provider name is not set at node-level,
573				 * but is set at resource-level, use it.
574				 */
575				strlcpy(curres->hr_provname, depth1_provname,
576				    sizeof(curres->hr_provname));
577			}
578			if (curres->hr_localpath[0] == '\0' &&
579			    depth1_localpath[0] != '\0') {
580				/*
581				 * Path to local provider is not set at
582				 * node-level, but is set at resource-level,
583				 * use it.
584				 */
585				strlcpy(curres->hr_localpath, depth1_localpath,
586				    sizeof(curres->hr_localpath));
587			}
588
589			/*
590			 * If provider name is not given, use resource name
591			 * as provider name.
592			 */
593			if (curres->hr_provname[0] == '\0') {
594				strlcpy(curres->hr_provname, curres->hr_name,
595				    sizeof(curres->hr_provname));
596			}
597
598			/*
599			 * Remote address has to be configured at this point.
600			 */
601			if (curres->hr_remoteaddr[0] == '\0') {
602				pjdlog_error("Remote address not configured for resource %s.",
603				    curres->hr_name);
604				return (1);
605			}
606			/*
607			 * Path to local provider has to be configured at this
608			 * point.
609			 */
610			if (curres->hr_localpath[0] == '\0') {
611				pjdlog_error("Path to local component not configured for resource %s.",
612				    curres->hr_name);
613				return (1);
614			}
615
616			/* Put it onto resource list. */
617			TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next);
618			curres = NULL;
619		}
620	}
621	;
622
623resource_start:	STR
624	{
625		/* Check if there is no duplicate entry. */
626		TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
627			if (strcmp(curres->hr_name, $1) == 0) {
628				pjdlog_error("Resource %s configured more than once.",
629				    curres->hr_name);
630				free($1);
631				return (1);
632			}
633		}
634
635		/*
636		 * Clear those, so we can tell if they were set at
637		 * resource-level or not.
638		 */
639		depth1_provname[0] = '\0';
640		depth1_localpath[0] = '\0';
641		hadmynode = false;
642
643		curres = calloc(1, sizeof(*curres));
644		if (curres == NULL) {
645			pjdlog_error("Unable to allocate memory for resource.");
646			free($1);
647			return (1);
648		}
649		if (strlcpy(curres->hr_name, $1,
650		    sizeof(curres->hr_name)) >=
651		    sizeof(curres->hr_name)) {
652			pjdlog_error("Resource name is too long.");
653			free($1);
654			return (1);
655		}
656		free($1);
657		curres->hr_role = HAST_ROLE_INIT;
658		curres->hr_previous_role = HAST_ROLE_INIT;
659		curres->hr_replication = -1;
660		curres->hr_checksum = -1;
661		curres->hr_compression = -1;
662		curres->hr_timeout = -1;
663		curres->hr_exec[0] = '\0';
664		curres->hr_provname[0] = '\0';
665		curres->hr_localpath[0] = '\0';
666		curres->hr_localfd = -1;
667		curres->hr_remoteaddr[0] = '\0';
668		curres->hr_sourceaddr[0] = '\0';
669		curres->hr_ggateunit = -1;
670	}
671	;
672
673resource_entries:
674	|
675	resource_entries resource_entry
676	;
677
678resource_entry:
679	replication_statement
680	|
681	checksum_statement
682	|
683	compression_statement
684	|
685	timeout_statement
686	|
687	exec_statement
688	|
689	name_statement
690	|
691	local_statement
692	|
693	resource_node_statement
694	;
695
696name_statement:		NAME STR
697	{
698		switch (depth) {
699		case 1:
700			if (strlcpy(depth1_provname, $2,
701			    sizeof(depth1_provname)) >=
702			    sizeof(depth1_provname)) {
703				pjdlog_error("name argument is too long.");
704				free($2);
705				return (1);
706			}
707			break;
708		case 2:
709			if (!mynode)
710				break;
711			assert(curres != NULL);
712			if (strlcpy(curres->hr_provname, $2,
713			    sizeof(curres->hr_provname)) >=
714			    sizeof(curres->hr_provname)) {
715				pjdlog_error("name argument is too long.");
716				free($2);
717				return (1);
718			}
719			break;
720		default:
721			assert(!"name at wrong depth level");
722		}
723		free($2);
724	}
725	;
726
727local_statement:	LOCAL STR
728	{
729		switch (depth) {
730		case 1:
731			if (strlcpy(depth1_localpath, $2,
732			    sizeof(depth1_localpath)) >=
733			    sizeof(depth1_localpath)) {
734				pjdlog_error("local argument is too long.");
735				free($2);
736				return (1);
737			}
738			break;
739		case 2:
740			if (!mynode)
741				break;
742			assert(curres != NULL);
743			if (strlcpy(curres->hr_localpath, $2,
744			    sizeof(curres->hr_localpath)) >=
745			    sizeof(curres->hr_localpath)) {
746				pjdlog_error("local argument is too long.");
747				free($2);
748				return (1);
749			}
750			break;
751		default:
752			assert(!"local at wrong depth level");
753		}
754		free($2);
755	}
756	;
757
758resource_node_statement:ON resource_node_start OB resource_node_entries CB
759	{
760		mynode = false;
761	}
762	;
763
764resource_node_start:	STR
765	{
766		if (curres != NULL) {
767			switch (isitme($1)) {
768			case -1:
769				free($1);
770				return (1);
771			case 0:
772				break;
773			case 1:
774				mynode = hadmynode = true;
775				break;
776			default:
777				assert(!"invalid isitme() return value");
778			}
779		}
780		free($1);
781	}
782	;
783
784resource_node_entries:
785	|
786	resource_node_entries resource_node_entry
787	;
788
789resource_node_entry:
790	name_statement
791	|
792	local_statement
793	|
794	remote_statement
795	|
796	source_statement
797	;
798
799remote_statement:	REMOTE STR
800	{
801		assert(depth == 2);
802		if (mynode) {
803			assert(curres != NULL);
804			if (strlcpy(curres->hr_remoteaddr, $2,
805			    sizeof(curres->hr_remoteaddr)) >=
806			    sizeof(curres->hr_remoteaddr)) {
807				pjdlog_error("remote argument is too long.");
808				free($2);
809				return (1);
810			}
811		}
812		free($2);
813	}
814	;
815
816source_statement:	SOURCE STR
817	{
818		assert(depth == 2);
819		if (mynode) {
820			assert(curres != NULL);
821			if (strlcpy(curres->hr_sourceaddr, $2,
822			    sizeof(curres->hr_sourceaddr)) >=
823			    sizeof(curres->hr_sourceaddr)) {
824				pjdlog_error("source argument is too long.");
825				free($2);
826				return (1);
827			}
828		}
829		free($2);
830	}
831	;
832