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