parse.y revision 216722
1%{
2/*-
3 * Copyright (c) 2009-2010 The FreeBSD Foundation
4 * All rights reserved.
5 *
6 * This software was developed by Pawel Jakub Dawidek under sponsorship from
7 * the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 * $FreeBSD: head/sbin/hastd/parse.y 216722 2010-12-26 19:08:41Z pjd $
31 */
32
33#include <sys/param.h>	/* MAXHOSTNAMELEN */
34#include <sys/queue.h>
35#include <sys/sysctl.h>
36
37#include <arpa/inet.h>
38
39#include <assert.h>
40#include <err.h>
41#include <stdio.h>
42#include <string.h>
43#include <sysexits.h>
44#include <unistd.h>
45
46#include <pjdlog.h>
47
48#include "hast.h"
49
50extern int depth;
51extern int lineno;
52
53extern FILE *yyin;
54extern char *yytext;
55
56static struct hastd_config *lconfig;
57static struct hast_resource *curres;
58static bool mynode, hadmynode;
59
60static char depth0_control[HAST_ADDRSIZE];
61static char depth0_listen[HAST_ADDRSIZE];
62static int depth0_replication;
63static int depth0_timeout;
64static char depth0_exec[PATH_MAX];
65
66static char depth1_provname[PATH_MAX];
67static char depth1_localpath[PATH_MAX];
68
69extern void yyrestart(FILE *);
70
71static int
72isitme(const char *name)
73{
74	char buf[MAXHOSTNAMELEN];
75	char *pos;
76	size_t bufsize;
77
78	/*
79	 * First check if the give name matches our full hostname.
80	 */
81	if (gethostname(buf, sizeof(buf)) < 0) {
82		pjdlog_errno(LOG_ERR, "gethostname() failed");
83		return (-1);
84	}
85	if (strcmp(buf, name) == 0)
86		return (1);
87
88	/*
89	 * Now check if it matches first part of the host name.
90	 */
91	pos = strchr(buf, '.');
92	if (pos != NULL && pos != buf && strncmp(buf, name, pos - buf) == 0)
93		return (1);
94
95	/*
96	 * At the end check if name is equal to our host's UUID.
97	 */
98	bufsize = sizeof(buf);
99	if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
100		pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
101		return (-1);
102	}
103	if (strcasecmp(buf, name) == 0)
104		return (1);
105
106	/*
107	 * Looks like this isn't about us.
108	 */
109	return (0);
110}
111
112static int
113node_names(char **namesp)
114{
115	static char names[MAXHOSTNAMELEN * 3];
116	char buf[MAXHOSTNAMELEN];
117	char *pos;
118	size_t bufsize;
119
120	if (gethostname(buf, sizeof(buf)) < 0) {
121		pjdlog_errno(LOG_ERR, "gethostname() failed");
122		return (-1);
123	}
124
125	/* First component of the host name. */
126	pos = strchr(buf, '.');
127	if (pos != NULL && pos != buf) {
128		(void)strlcpy(names, buf, MIN((size_t)(pos - buf + 1),
129		    sizeof(names)));
130		(void)strlcat(names, ", ", sizeof(names));
131	}
132
133	/* Full host name. */
134	(void)strlcat(names, buf, sizeof(names));
135	(void)strlcat(names, ", ", sizeof(names));
136
137	/* Host UUID. */
138	bufsize = sizeof(buf);
139	if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) {
140		pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed");
141		return (-1);
142	}
143	(void)strlcat(names, buf, sizeof(names));
144
145	*namesp = names;
146
147	return (0);
148}
149
150void
151yyerror(const char *str)
152{
153
154	pjdlog_error("Unable to parse configuration file at line %d near '%s': %s",
155	    lineno, yytext, str);
156}
157
158struct hastd_config *
159yy_config_parse(const char *config, bool exitonerror)
160{
161	int ret;
162
163	curres = NULL;
164	mynode = false;
165	depth = 0;
166	lineno = 0;
167
168	depth0_timeout = HAST_TIMEOUT;
169	depth0_replication = HAST_REPLICATION_MEMSYNC;
170	strlcpy(depth0_control, HAST_CONTROL, sizeof(depth0_control));
171	strlcpy(depth0_listen, HASTD_LISTEN, sizeof(depth0_listen));
172	depth0_exec[0] = '\0';
173
174	lconfig = calloc(1, sizeof(*lconfig));
175	if (lconfig == NULL) {
176		pjdlog_error("Unable to allocate memory for configuration.");
177		if (exitonerror)
178			exit(EX_TEMPFAIL);
179		return (NULL);
180	}
181
182	TAILQ_INIT(&lconfig->hc_resources);
183
184	yyin = fopen(config, "r");
185	if (yyin == NULL) {
186		pjdlog_errno(LOG_ERR, "Unable to open configuration file %s",
187		    config);
188		yy_config_free(lconfig);
189		if (exitonerror)
190			exit(EX_OSFILE);
191		return (NULL);
192	}
193	yyrestart(yyin);
194	ret = yyparse();
195	fclose(yyin);
196	if (ret != 0) {
197		yy_config_free(lconfig);
198		if (exitonerror)
199			exit(EX_CONFIG);
200		return (NULL);
201	}
202
203	/*
204	 * Let's see if everything is set up.
205	 */
206	if (lconfig->hc_controladdr[0] == '\0') {
207		strlcpy(lconfig->hc_controladdr, depth0_control,
208		    sizeof(lconfig->hc_controladdr));
209	}
210	if (lconfig->hc_listenaddr[0] == '\0') {
211		strlcpy(lconfig->hc_listenaddr, depth0_listen,
212		    sizeof(lconfig->hc_listenaddr));
213	}
214	TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
215		assert(curres->hr_provname[0] != '\0');
216		assert(curres->hr_localpath[0] != '\0');
217		assert(curres->hr_remoteaddr[0] != '\0');
218
219		if (curres->hr_replication == -1) {
220			/*
221			 * Replication is not set at resource-level.
222			 * Use global or default setting.
223			 */
224			curres->hr_replication = depth0_replication;
225		}
226		if (curres->hr_timeout == -1) {
227			/*
228			 * Timeout is not set at resource-level.
229			 * Use global or default setting.
230			 */
231			curres->hr_timeout = depth0_timeout;
232		}
233		if (curres->hr_exec[0] == '\0') {
234			/*
235			 * Exec is not set at resource-level.
236			 * Use global or default setting.
237			 */
238			strlcpy(curres->hr_exec, depth0_exec,
239			    sizeof(curres->hr_exec));
240		}
241	}
242
243	return (lconfig);
244}
245
246void
247yy_config_free(struct hastd_config *config)
248{
249	struct hast_resource *res;
250
251	while ((res = TAILQ_FIRST(&config->hc_resources)) != NULL) {
252		TAILQ_REMOVE(&config->hc_resources, res, hr_next);
253		free(res);
254	}
255	free(config);
256}
257%}
258
259%token CONTROL LISTEN PORT REPLICATION TIMEOUT EXEC EXTENTSIZE RESOURCE NAME LOCAL REMOTE ON
260%token FULLSYNC MEMSYNC ASYNC
261%token NUM STR OB CB
262
263%type <num> replication_type
264
265%union
266{
267	int num;
268	char *str;
269}
270
271%token <num> NUM
272%token <str> STR
273
274%%
275
276statements:
277	|
278	statements statement
279	;
280
281statement:
282	control_statement
283	|
284	listen_statement
285	|
286	replication_statement
287	|
288	timeout_statement
289	|
290	exec_statement
291	|
292	node_statement
293	|
294	resource_statement
295	;
296
297control_statement:	CONTROL STR
298	{
299		switch (depth) {
300		case 0:
301			if (strlcpy(depth0_control, $2,
302			    sizeof(depth0_control)) >=
303			    sizeof(depth0_control)) {
304				pjdlog_error("control argument is too long.");
305				free($2);
306				return (1);
307			}
308			break;
309		case 1:
310			if (!mynode)
311				break;
312			if (strlcpy(lconfig->hc_controladdr, $2,
313			    sizeof(lconfig->hc_controladdr)) >=
314			    sizeof(lconfig->hc_controladdr)) {
315				pjdlog_error("control argument is too long.");
316				free($2);
317				return (1);
318			}
319			break;
320		default:
321			assert(!"control at wrong depth level");
322		}
323		free($2);
324	}
325	;
326
327listen_statement:	LISTEN STR
328	{
329		switch (depth) {
330		case 0:
331			if (strlcpy(depth0_listen, $2,
332			    sizeof(depth0_listen)) >=
333			    sizeof(depth0_listen)) {
334				pjdlog_error("listen argument is too long.");
335				free($2);
336				return (1);
337			}
338			break;
339		case 1:
340			if (!mynode)
341				break;
342			if (strlcpy(lconfig->hc_listenaddr, $2,
343			    sizeof(lconfig->hc_listenaddr)) >=
344			    sizeof(lconfig->hc_listenaddr)) {
345				pjdlog_error("listen argument is too long.");
346				free($2);
347				return (1);
348			}
349			break;
350		default:
351			assert(!"listen at wrong depth level");
352		}
353		free($2);
354	}
355	;
356
357replication_statement:	REPLICATION replication_type
358	{
359		switch (depth) {
360		case 0:
361			depth0_replication = $2;
362			break;
363		case 1:
364			if (curres != NULL)
365				curres->hr_replication = $2;
366			break;
367		default:
368			assert(!"replication at wrong depth level");
369		}
370	}
371	;
372
373replication_type:
374	FULLSYNC	{ $$ = HAST_REPLICATION_FULLSYNC; }
375	|
376	MEMSYNC		{ $$ = HAST_REPLICATION_MEMSYNC; }
377	|
378	ASYNC		{ $$ = HAST_REPLICATION_ASYNC; }
379	;
380
381timeout_statement:	TIMEOUT NUM
382	{
383		switch (depth) {
384		case 0:
385			depth0_timeout = $2;
386			break;
387		case 1:
388			if (curres != NULL)
389				curres->hr_timeout = $2;
390			break;
391		default:
392			assert(!"timeout at wrong depth level");
393		}
394	}
395	;
396
397exec_statement:		EXEC STR
398	{
399		switch (depth) {
400		case 0:
401			if (strlcpy(depth0_exec, $2, sizeof(depth0_exec)) >=
402			    sizeof(depth0_exec)) {
403				pjdlog_error("Exec path is too long.");
404				free($2);
405				return (1);
406			}
407			break;
408		case 1:
409			if (curres == NULL)
410				break;
411			if (strlcpy(curres->hr_exec, $2,
412			    sizeof(curres->hr_exec)) >=
413			    sizeof(curres->hr_exec)) {
414				pjdlog_error("Exec path is too long.");
415				free($2);
416				return (1);
417			}
418			break;
419		default:
420			assert(!"exec at wrong depth level");
421		}
422		free($2);
423	}
424	;
425
426node_statement:		ON node_start OB node_entries CB
427	{
428		mynode = false;
429	}
430	;
431
432node_start:	STR
433	{
434		switch (isitme($1)) {
435		case -1:
436			free($1);
437			return (1);
438		case 0:
439			break;
440		case 1:
441			mynode = true;
442			break;
443		default:
444			assert(!"invalid isitme() return value");
445		}
446		free($1);
447	}
448	;
449
450node_entries:
451	|
452	node_entries node_entry
453	;
454
455node_entry:
456	control_statement
457	|
458	listen_statement
459	;
460
461resource_statement:	RESOURCE resource_start OB resource_entries CB
462	{
463		if (curres != NULL) {
464			/*
465			 * There must be section for this node, at least with
466			 * remote address configuration.
467			 */
468			if (!hadmynode) {
469				char *names;
470
471				if (node_names(&names) != 0)
472					return (1);
473				pjdlog_error("No resource %s configuration for this node (acceptable node names: %s).",
474				    curres->hr_name, names);
475				return (1);
476			}
477
478			/*
479			 * Let's see there are some resource-level settings
480			 * that we can use for node-level settings.
481			 */
482			if (curres->hr_provname[0] == '\0' &&
483			    depth1_provname[0] != '\0') {
484				/*
485				 * Provider name is not set at node-level,
486				 * but is set at resource-level, use it.
487				 */
488				strlcpy(curres->hr_provname, depth1_provname,
489				    sizeof(curres->hr_provname));
490			}
491			if (curres->hr_localpath[0] == '\0' &&
492			    depth1_localpath[0] != '\0') {
493				/*
494				 * Path to local provider is not set at
495				 * node-level, but is set at resource-level,
496				 * use it.
497				 */
498				strlcpy(curres->hr_localpath, depth1_localpath,
499				    sizeof(curres->hr_localpath));
500			}
501
502			/*
503			 * If provider name is not given, use resource name
504			 * as provider name.
505			 */
506			if (curres->hr_provname[0] == '\0') {
507				strlcpy(curres->hr_provname, curres->hr_name,
508				    sizeof(curres->hr_provname));
509			}
510
511			/*
512			 * Remote address has to be configured at this point.
513			 */
514			if (curres->hr_remoteaddr[0] == '\0') {
515				pjdlog_error("Remote address not configured for resource %s.",
516				    curres->hr_name);
517				return (1);
518			}
519			/*
520			 * Path to local provider has to be configured at this
521			 * point.
522			 */
523			if (curres->hr_localpath[0] == '\0') {
524				pjdlog_error("Path to local component not configured for resource %s.",
525				    curres->hr_name);
526				return (1);
527			}
528
529			/* Put it onto resource list. */
530			TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next);
531			curres = NULL;
532		}
533	}
534	;
535
536resource_start:	STR
537	{
538		/* Check if there is no duplicate entry. */
539		TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) {
540			if (strcmp(curres->hr_name, $1) == 0) {
541				pjdlog_error("Resource %s configured more than once.",
542				    curres->hr_name);
543				free($1);
544				return (1);
545			}
546		}
547
548		/*
549		 * Clear those, so we can tell if they were set at
550		 * resource-level or not.
551		 */
552		depth1_provname[0] = '\0';
553		depth1_localpath[0] = '\0';
554		hadmynode = false;
555
556		curres = calloc(1, sizeof(*curres));
557		if (curres == NULL) {
558			pjdlog_error("Unable to allocate memory for resource.");
559			free($1);
560			return (1);
561		}
562		if (strlcpy(curres->hr_name, $1,
563		    sizeof(curres->hr_name)) >=
564		    sizeof(curres->hr_name)) {
565			pjdlog_error("Resource name is too long.");
566			free($1);
567			return (1);
568		}
569		free($1);
570		curres->hr_role = HAST_ROLE_INIT;
571		curres->hr_previous_role = HAST_ROLE_INIT;
572		curres->hr_replication = -1;
573		curres->hr_timeout = -1;
574		curres->hr_exec[0] = '\0';
575		curres->hr_provname[0] = '\0';
576		curres->hr_localpath[0] = '\0';
577		curres->hr_localfd = -1;
578		curres->hr_remoteaddr[0] = '\0';
579		curres->hr_ggateunit = -1;
580	}
581	;
582
583resource_entries:
584	|
585	resource_entries resource_entry
586	;
587
588resource_entry:
589	replication_statement
590	|
591	timeout_statement
592	|
593	exec_statement
594	|
595	name_statement
596	|
597	local_statement
598	|
599	resource_node_statement
600	;
601
602name_statement:		NAME STR
603	{
604		switch (depth) {
605		case 1:
606			if (strlcpy(depth1_provname, $2,
607			    sizeof(depth1_provname)) >=
608			    sizeof(depth1_provname)) {
609				pjdlog_error("name argument is too long.");
610				free($2);
611				return (1);
612			}
613			break;
614		case 2:
615			if (!mynode)
616				break;
617			assert(curres != NULL);
618			if (strlcpy(curres->hr_provname, $2,
619			    sizeof(curres->hr_provname)) >=
620			    sizeof(curres->hr_provname)) {
621				pjdlog_error("name argument is too long.");
622				free($2);
623				return (1);
624			}
625			break;
626		default:
627			assert(!"name at wrong depth level");
628		}
629		free($2);
630	}
631	;
632
633local_statement:	LOCAL STR
634	{
635		switch (depth) {
636		case 1:
637			if (strlcpy(depth1_localpath, $2,
638			    sizeof(depth1_localpath)) >=
639			    sizeof(depth1_localpath)) {
640				pjdlog_error("local argument is too long.");
641				free($2);
642				return (1);
643			}
644			break;
645		case 2:
646			if (!mynode)
647				break;
648			assert(curres != NULL);
649			if (strlcpy(curres->hr_localpath, $2,
650			    sizeof(curres->hr_localpath)) >=
651			    sizeof(curres->hr_localpath)) {
652				pjdlog_error("local argument is too long.");
653				free($2);
654				return (1);
655			}
656			break;
657		default:
658			assert(!"local at wrong depth level");
659		}
660		free($2);
661	}
662	;
663
664resource_node_statement:ON resource_node_start OB resource_node_entries CB
665	{
666		mynode = false;
667	}
668	;
669
670resource_node_start:	STR
671	{
672		if (curres != NULL) {
673			switch (isitme($1)) {
674			case -1:
675				free($1);
676				return (1);
677			case 0:
678				break;
679			case 1:
680				mynode = hadmynode = true;
681				break;
682			default:
683				assert(!"invalid isitme() return value");
684			}
685		}
686		free($1);
687	}
688	;
689
690resource_node_entries:
691	|
692	resource_node_entries resource_node_entry
693	;
694
695resource_node_entry:
696	name_statement
697	|
698	local_statement
699	|
700	remote_statement
701	;
702
703remote_statement:	REMOTE STR
704	{
705		assert(depth == 2);
706		if (mynode) {
707			assert(curres != NULL);
708			if (strlcpy(curres->hr_remoteaddr, $2,
709			    sizeof(curres->hr_remoteaddr)) >=
710			    sizeof(curres->hr_remoteaddr)) {
711				pjdlog_error("remote argument is too long.");
712				free($2);
713				return (1);
714			}
715		}
716		free($2);
717	}
718	;
719