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