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