parse.y revision 220889
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 220889 2011-04-20 16:36:59Z 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		if ($2 <= 0) {
465			pjdlog_error("Negative or zero timeout.");
466			return (1);
467		}
468		switch (depth) {
469		case 0:
470			depth0_timeout = $2;
471			break;
472		case 1:
473			if (curres != NULL)
474				curres->hr_timeout = $2;
475			break;
476		default:
477			assert(!"timeout at wrong depth level");
478		}
479	}
480	;
481
482exec_statement:		EXEC STR
483	{
484		switch (depth) {
485		case 0:
486			if (strlcpy(depth0_exec, $2, sizeof(depth0_exec)) >=
487			    sizeof(depth0_exec)) {
488				pjdlog_error("Exec path is too long.");
489				free($2);
490				return (1);
491			}
492			break;
493		case 1:
494			if (curres == NULL)
495				break;
496			if (strlcpy(curres->hr_exec, $2,
497			    sizeof(curres->hr_exec)) >=
498			    sizeof(curres->hr_exec)) {
499				pjdlog_error("Exec path is too long.");
500				free($2);
501				return (1);
502			}
503			break;
504		default:
505			assert(!"exec at wrong depth level");
506		}
507		free($2);
508	}
509	;
510
511node_statement:		ON node_start OB node_entries CB
512	{
513		mynode = false;
514	}
515	;
516
517node_start:	STR
518	{
519		switch (isitme($1)) {
520		case -1:
521			free($1);
522			return (1);
523		case 0:
524			break;
525		case 1:
526			mynode = true;
527			break;
528		default:
529			assert(!"invalid isitme() return value");
530		}
531		free($1);
532	}
533	;
534
535node_entries:
536	|
537	node_entries node_entry
538	;
539
540node_entry:
541	control_statement
542	|
543	listen_statement
544	;
545
546resource_statement:	RESOURCE resource_start OB resource_entries CB
547	{
548		if (curres != NULL) {
549			/*
550			 * There must be section for this node, at least with
551			 * remote address configuration.
552			 */
553			if (!hadmynode) {
554				char *names;
555
556				if (node_names(&names) != 0)
557					return (1);
558				pjdlog_error("No resource %s configuration for this node (acceptable node names: %s).",
559				    curres->hr_name, names);
560				return (1);
561			}
562
563			/*
564			 * Let's see there are some resource-level settings
565			 * that we can use for node-level settings.
566			 */
567			if (curres->hr_provname[0] == '\0' &&
568			    depth1_provname[0] != '\0') {
569				/*
570				 * Provider name is not set at node-level,
571				 * but is set at resource-level, use it.
572				 */
573				strlcpy(curres->hr_provname, depth1_provname,
574				    sizeof(curres->hr_provname));
575			}
576			if (curres->hr_localpath[0] == '\0' &&
577			    depth1_localpath[0] != '\0') {
578				/*
579				 * Path to local provider is not set at
580				 * node-level, but is set at resource-level,
581				 * use it.
582				 */
583				strlcpy(curres->hr_localpath, depth1_localpath,
584				    sizeof(curres->hr_localpath));
585			}
586
587			/*
588			 * If provider name is not given, use resource name
589			 * as provider name.
590			 */
591			if (curres->hr_provname[0] == '\0') {
592				strlcpy(curres->hr_provname, curres->hr_name,
593				    sizeof(curres->hr_provname));
594			}
595
596			/*
597			 * Remote address has to be configured at this point.
598			 */
599			if (curres->hr_remoteaddr[0] == '\0') {
600				pjdlog_error("Remote address not configured for resource %s.",
601				    curres->hr_name);
602				return (1);
603			}
604			/*
605			 * Path to local provider has to be configured at this
606			 * point.
607			 */
608			if (curres->hr_localpath[0] == '\0') {
609				pjdlog_error("Path to local component not configured for resource %s.",
610				    curres->hr_name);
611				return (1);
612			}
613
614			/* Put it onto resource list. */
615			TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next);
616			curres = NULL;
617		}
618	}
619	;
620
621resource_start:	STR
622	{
623		/* Check if there is no duplicate entry. */
624		TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
625			if (strcmp(curres->hr_name, $1) == 0) {
626				pjdlog_error("Resource %s configured more than once.",
627				    curres->hr_name);
628				free($1);
629				return (1);
630			}
631		}
632
633		/*
634		 * Clear those, so we can tell if they were set at
635		 * resource-level or not.
636		 */
637		depth1_provname[0] = '\0';
638		depth1_localpath[0] = '\0';
639		hadmynode = false;
640
641		curres = calloc(1, sizeof(*curres));
642		if (curres == NULL) {
643			pjdlog_error("Unable to allocate memory for resource.");
644			free($1);
645			return (1);
646		}
647		if (strlcpy(curres->hr_name, $1,
648		    sizeof(curres->hr_name)) >=
649		    sizeof(curres->hr_name)) {
650			pjdlog_error("Resource name is too long.");
651			free($1);
652			return (1);
653		}
654		free($1);
655		curres->hr_role = HAST_ROLE_INIT;
656		curres->hr_previous_role = HAST_ROLE_INIT;
657		curres->hr_replication = -1;
658		curres->hr_checksum = -1;
659		curres->hr_compression = -1;
660		curres->hr_timeout = -1;
661		curres->hr_exec[0] = '\0';
662		curres->hr_provname[0] = '\0';
663		curres->hr_localpath[0] = '\0';
664		curres->hr_localfd = -1;
665		curres->hr_remoteaddr[0] = '\0';
666		curres->hr_sourceaddr[0] = '\0';
667		curres->hr_ggateunit = -1;
668	}
669	;
670
671resource_entries:
672	|
673	resource_entries resource_entry
674	;
675
676resource_entry:
677	replication_statement
678	|
679	checksum_statement
680	|
681	compression_statement
682	|
683	timeout_statement
684	|
685	exec_statement
686	|
687	name_statement
688	|
689	local_statement
690	|
691	resource_node_statement
692	;
693
694name_statement:		NAME STR
695	{
696		switch (depth) {
697		case 1:
698			if (strlcpy(depth1_provname, $2,
699			    sizeof(depth1_provname)) >=
700			    sizeof(depth1_provname)) {
701				pjdlog_error("name argument is too long.");
702				free($2);
703				return (1);
704			}
705			break;
706		case 2:
707			if (!mynode)
708				break;
709			assert(curres != NULL);
710			if (strlcpy(curres->hr_provname, $2,
711			    sizeof(curres->hr_provname)) >=
712			    sizeof(curres->hr_provname)) {
713				pjdlog_error("name argument is too long.");
714				free($2);
715				return (1);
716			}
717			break;
718		default:
719			assert(!"name at wrong depth level");
720		}
721		free($2);
722	}
723	;
724
725local_statement:	LOCAL STR
726	{
727		switch (depth) {
728		case 1:
729			if (strlcpy(depth1_localpath, $2,
730			    sizeof(depth1_localpath)) >=
731			    sizeof(depth1_localpath)) {
732				pjdlog_error("local argument is too long.");
733				free($2);
734				return (1);
735			}
736			break;
737		case 2:
738			if (!mynode)
739				break;
740			assert(curres != NULL);
741			if (strlcpy(curres->hr_localpath, $2,
742			    sizeof(curres->hr_localpath)) >=
743			    sizeof(curres->hr_localpath)) {
744				pjdlog_error("local argument is too long.");
745				free($2);
746				return (1);
747			}
748			break;
749		default:
750			assert(!"local at wrong depth level");
751		}
752		free($2);
753	}
754	;
755
756resource_node_statement:ON resource_node_start OB resource_node_entries CB
757	{
758		mynode = false;
759	}
760	;
761
762resource_node_start:	STR
763	{
764		if (curres != NULL) {
765			switch (isitme($1)) {
766			case -1:
767				free($1);
768				return (1);
769			case 0:
770				break;
771			case 1:
772				mynode = hadmynode = true;
773				break;
774			default:
775				assert(!"invalid isitme() return value");
776			}
777		}
778		free($1);
779	}
780	;
781
782resource_node_entries:
783	|
784	resource_node_entries resource_node_entry
785	;
786
787resource_node_entry:
788	name_statement
789	|
790	local_statement
791	|
792	remote_statement
793	|
794	source_statement
795	;
796
797remote_statement:	REMOTE STR
798	{
799		assert(depth == 2);
800		if (mynode) {
801			assert(curres != NULL);
802			if (strlcpy(curres->hr_remoteaddr, $2,
803			    sizeof(curres->hr_remoteaddr)) >=
804			    sizeof(curres->hr_remoteaddr)) {
805				pjdlog_error("remote argument is too long.");
806				free($2);
807				return (1);
808			}
809		}
810		free($2);
811	}
812	;
813
814source_statement:	SOURCE STR
815	{
816		assert(depth == 2);
817		if (mynode) {
818			assert(curres != NULL);
819			if (strlcpy(curres->hr_sourceaddr, $2,
820			    sizeof(curres->hr_sourceaddr)) >=
821			    sizeof(curres->hr_sourceaddr)) {
822				pjdlog_error("source argument is too long.");
823				free($2);
824				return (1);
825			}
826		}
827		free($2);
828	}
829	;
830