%{ /*- * Copyright (c) 2009-2010 The FreeBSD Foundation * Copyright (c) 2011 Pawel Jakub Dawidek * All rights reserved. * * This software was developed by Pawel Jakub Dawidek under sponsorship from * the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #include /* MAXHOSTNAMELEN */ #include #include #include #include #include #include #include #include #include #include #include #include "hast.h" extern int depth; extern int lineno; extern FILE *yyin; extern char *yytext; static struct hastd_config *lconfig; static struct hast_resource *curres; static bool mynode, hadmynode; static char depth0_control[HAST_ADDRSIZE]; static char depth0_pidfile[PATH_MAX]; static char depth0_listen_tcp4[HAST_ADDRSIZE]; static char depth0_listen_tcp6[HAST_ADDRSIZE]; static TAILQ_HEAD(, hastd_listen) depth0_listen; static int depth0_replication; static int depth0_checksum; static int depth0_compression; static int depth0_timeout; static char depth0_exec[PATH_MAX]; static int depth0_metaflush; static char depth1_provname[PATH_MAX]; static char depth1_localpath[PATH_MAX]; static int depth1_metaflush; extern void yyerror(const char *); extern int yylex(void); extern void yyrestart(FILE *); static int isitme(const char *name); static bool family_supported(int family); static int node_names(char **namesp); %} %token CONTROL PIDFILE LISTEN REPLICATION CHECKSUM COMPRESSION METAFLUSH %token TIMEOUT EXEC RESOURCE NAME LOCAL REMOTE SOURCE ON OFF %token FULLSYNC MEMSYNC ASYNC NONE CRC32 SHA256 HOLE LZF %token NUM STR OB CB %type remote_str %type replication_type %type checksum_type %type compression_type %type boolean %union { int num; char *str; } %token NUM %token STR %% statements: | statements statement ; statement: control_statement | pidfile_statement | listen_statement | replication_statement | checksum_statement | compression_statement | timeout_statement | exec_statement | metaflush_statement | node_statement | resource_statement ; control_statement: CONTROL STR { switch (depth) { case 0: if (strlcpy(depth0_control, $2, sizeof(depth0_control)) >= sizeof(depth0_control)) { pjdlog_error("control argument is too long."); free($2); return (1); } break; case 1: if (!mynode) break; if (strlcpy(lconfig->hc_controladdr, $2, sizeof(lconfig->hc_controladdr)) >= sizeof(lconfig->hc_controladdr)) { pjdlog_error("control argument is too long."); free($2); return (1); } break; default: PJDLOG_ABORT("control at wrong depth level"); } free($2); } ; pidfile_statement: PIDFILE STR { switch (depth) { case 0: if (strlcpy(depth0_pidfile, $2, sizeof(depth0_pidfile)) >= sizeof(depth0_pidfile)) { pjdlog_error("pidfile argument is too long."); free($2); return (1); } break; case 1: if (!mynode) break; if (strlcpy(lconfig->hc_pidfile, $2, sizeof(lconfig->hc_pidfile)) >= sizeof(lconfig->hc_pidfile)) { pjdlog_error("pidfile argument is too long."); free($2); return (1); } break; default: PJDLOG_ABORT("pidfile at wrong depth level"); } free($2); } ; listen_statement: LISTEN STR { struct hastd_listen *lst; lst = calloc(1, sizeof(*lst)); if (lst == NULL) { pjdlog_error("Unable to allocate memory for listen address."); free($2); return (1); } if (strlcpy(lst->hl_addr, $2, sizeof(lst->hl_addr)) >= sizeof(lst->hl_addr)) { pjdlog_error("listen argument is too long."); free($2); free(lst); return (1); } switch (depth) { case 0: TAILQ_INSERT_TAIL(&depth0_listen, lst, hl_next); break; case 1: if (mynode) TAILQ_INSERT_TAIL(&depth0_listen, lst, hl_next); else free(lst); break; default: PJDLOG_ABORT("listen at wrong depth level"); } free($2); } ; replication_statement: REPLICATION replication_type { switch (depth) { case 0: depth0_replication = $2; break; case 1: PJDLOG_ASSERT(curres != NULL); curres->hr_replication = $2; curres->hr_original_replication = $2; break; default: PJDLOG_ABORT("replication at wrong depth level"); } } ; replication_type: FULLSYNC { $$ = HAST_REPLICATION_FULLSYNC; } | MEMSYNC { $$ = HAST_REPLICATION_MEMSYNC; } | ASYNC { $$ = HAST_REPLICATION_ASYNC; } ; checksum_statement: CHECKSUM checksum_type { switch (depth) { case 0: depth0_checksum = $2; break; case 1: PJDLOG_ASSERT(curres != NULL); curres->hr_checksum = $2; break; default: PJDLOG_ABORT("checksum at wrong depth level"); } } ; checksum_type: NONE { $$ = HAST_CHECKSUM_NONE; } | CRC32 { $$ = HAST_CHECKSUM_CRC32; } | SHA256 { $$ = HAST_CHECKSUM_SHA256; } ; compression_statement: COMPRESSION compression_type { switch (depth) { case 0: depth0_compression = $2; break; case 1: PJDLOG_ASSERT(curres != NULL); curres->hr_compression = $2; break; default: PJDLOG_ABORT("compression at wrong depth level"); } } ; compression_type: NONE { $$ = HAST_COMPRESSION_NONE; } | HOLE { $$ = HAST_COMPRESSION_HOLE; } | LZF { $$ = HAST_COMPRESSION_LZF; } ; timeout_statement: TIMEOUT NUM { if ($2 <= 0) { pjdlog_error("Negative or zero timeout."); return (1); } switch (depth) { case 0: depth0_timeout = $2; break; case 1: PJDLOG_ASSERT(curres != NULL); curres->hr_timeout = $2; break; default: PJDLOG_ABORT("timeout at wrong depth level"); } } ; exec_statement: EXEC STR { switch (depth) { case 0: if (strlcpy(depth0_exec, $2, sizeof(depth0_exec)) >= sizeof(depth0_exec)) { pjdlog_error("Exec path is too long."); free($2); return (1); } break; case 1: PJDLOG_ASSERT(curres != NULL); if (strlcpy(curres->hr_exec, $2, sizeof(curres->hr_exec)) >= sizeof(curres->hr_exec)) { pjdlog_error("Exec path is too long."); free($2); return (1); } break; default: PJDLOG_ABORT("exec at wrong depth level"); } free($2); } ; metaflush_statement: METAFLUSH boolean { switch (depth) { case 0: depth0_metaflush = $2; break; case 1: PJDLOG_ASSERT(curres != NULL); depth1_metaflush = $2; break; case 2: if (!mynode) break; PJDLOG_ASSERT(curres != NULL); curres->hr_metaflush = $2; break; default: PJDLOG_ABORT("metaflush at wrong depth level"); } } ; boolean: ON { $$ = 1; } | OFF { $$ = 0; } ; node_statement: ON node_start OB node_entries CB { mynode = false; } ; node_start: STR { switch (isitme($1)) { case -1: free($1); return (1); case 0: break; case 1: mynode = true; break; default: PJDLOG_ABORT("invalid isitme() return value"); } free($1); } ; node_entries: | node_entries node_entry ; node_entry: control_statement | pidfile_statement | listen_statement ; resource_statement: RESOURCE resource_start OB resource_entries CB { if (curres != NULL) { /* * There must be section for this node, at least with * remote address configuration. */ if (!hadmynode) { char *names; if (node_names(&names) != 0) return (1); pjdlog_error("No resource %s configuration for this node (acceptable node names: %s).", curres->hr_name, names); return (1); } /* * Let's see if there are some resource-level settings * that we can use for node-level settings. */ if (curres->hr_provname[0] == '\0' && depth1_provname[0] != '\0') { /* * Provider name is not set at node-level, * but is set at resource-level, use it. */ strlcpy(curres->hr_provname, depth1_provname, sizeof(curres->hr_provname)); } if (curres->hr_localpath[0] == '\0' && depth1_localpath[0] != '\0') { /* * Path to local provider is not set at * node-level, but is set at resource-level, * use it. */ strlcpy(curres->hr_localpath, depth1_localpath, sizeof(curres->hr_localpath)); } if (curres->hr_metaflush == -1 && depth1_metaflush != -1) { /* * Metaflush is not set at node-level, * but is set at resource-level, use it. */ curres->hr_metaflush = depth1_metaflush; } /* * If provider name is not given, use resource name * as provider name. */ if (curres->hr_provname[0] == '\0') { strlcpy(curres->hr_provname, curres->hr_name, sizeof(curres->hr_provname)); } /* * Remote address has to be configured at this point. */ if (curres->hr_remoteaddr[0] == '\0') { pjdlog_error("Remote address not configured for resource %s.", curres->hr_name); return (1); } /* * Path to local provider has to be configured at this * point. */ if (curres->hr_localpath[0] == '\0') { pjdlog_error("Path to local component not configured for resource %s.", curres->hr_name); return (1); } /* Put it onto resource list. */ TAILQ_INSERT_TAIL(&lconfig->hc_resources, curres, hr_next); curres = NULL; } } ; resource_start: STR { /* Check if there is no duplicate entry. */ TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) { if (strcmp(curres->hr_name, $1) == 0) { pjdlog_error("Resource %s configured more than once.", curres->hr_name); free($1); return (1); } } /* * Clear those, so we can tell if they were set at * resource-level or not. */ depth1_provname[0] = '\0'; depth1_localpath[0] = '\0'; depth1_metaflush = -1; hadmynode = false; curres = calloc(1, sizeof(*curres)); if (curres == NULL) { pjdlog_error("Unable to allocate memory for resource."); free($1); return (1); } if (strlcpy(curres->hr_name, $1, sizeof(curres->hr_name)) >= sizeof(curres->hr_name)) { pjdlog_error("Resource name is too long."); free(curres); free($1); return (1); } free($1); curres->hr_role = HAST_ROLE_INIT; curres->hr_previous_role = HAST_ROLE_INIT; curres->hr_replication = -1; curres->hr_original_replication = -1; curres->hr_checksum = -1; curres->hr_compression = -1; curres->hr_version = 1; curres->hr_timeout = -1; curres->hr_exec[0] = '\0'; curres->hr_provname[0] = '\0'; curres->hr_localpath[0] = '\0'; curres->hr_localfd = -1; curres->hr_localflush = true; curres->hr_metaflush = -1; curres->hr_remoteaddr[0] = '\0'; curres->hr_sourceaddr[0] = '\0'; curres->hr_ggateunit = -1; } ; resource_entries: | resource_entries resource_entry ; resource_entry: replication_statement | checksum_statement | compression_statement | timeout_statement | exec_statement | metaflush_statement | name_statement | local_statement | resource_node_statement ; name_statement: NAME STR { switch (depth) { case 1: if (strlcpy(depth1_provname, $2, sizeof(depth1_provname)) >= sizeof(depth1_provname)) { pjdlog_error("name argument is too long."); free($2); return (1); } break; case 2: if (!mynode) break; PJDLOG_ASSERT(curres != NULL); if (strlcpy(curres->hr_provname, $2, sizeof(curres->hr_provname)) >= sizeof(curres->hr_provname)) { pjdlog_error("name argument is too long."); free($2); return (1); } break; default: PJDLOG_ABORT("name at wrong depth level"); } free($2); } ; local_statement: LOCAL STR { switch (depth) { case 1: if (strlcpy(depth1_localpath, $2, sizeof(depth1_localpath)) >= sizeof(depth1_localpath)) { pjdlog_error("local argument is too long."); free($2); return (1); } break; case 2: if (!mynode) break; PJDLOG_ASSERT(curres != NULL); if (strlcpy(curres->hr_localpath, $2, sizeof(curres->hr_localpath)) >= sizeof(curres->hr_localpath)) { pjdlog_error("local argument is too long."); free($2); return (1); } break; default: PJDLOG_ABORT("local at wrong depth level"); } free($2); } ; resource_node_statement:ON resource_node_start OB resource_node_entries CB { mynode = false; } ; resource_node_start: STR { if (curres != NULL) { switch (isitme($1)) { case -1: free($1); return (1); case 0: break; case 1: mynode = hadmynode = true; break; default: PJDLOG_ABORT("invalid isitme() return value"); } } free($1); } ; resource_node_entries: | resource_node_entries resource_node_entry ; resource_node_entry: name_statement | local_statement | remote_statement | source_statement | metaflush_statement ; remote_statement: REMOTE remote_str { PJDLOG_ASSERT(depth == 2); if (mynode) { PJDLOG_ASSERT(curres != NULL); if (strlcpy(curres->hr_remoteaddr, $2, sizeof(curres->hr_remoteaddr)) >= sizeof(curres->hr_remoteaddr)) { pjdlog_error("remote argument is too long."); free($2); return (1); } } free($2); } ; remote_str: NONE { $$ = strdup("none"); } | STR { } ; source_statement: SOURCE STR { PJDLOG_ASSERT(depth == 2); if (mynode) { PJDLOG_ASSERT(curres != NULL); if (strlcpy(curres->hr_sourceaddr, $2, sizeof(curres->hr_sourceaddr)) >= sizeof(curres->hr_sourceaddr)) { pjdlog_error("source argument is too long."); free($2); return (1); } } free($2); } ; %% static int isitme(const char *name) { char buf[MAXHOSTNAMELEN]; unsigned long hostid; char *pos; size_t bufsize; /* * First check if the given name matches our full hostname. */ if (gethostname(buf, sizeof(buf)) < 0) { pjdlog_errno(LOG_ERR, "gethostname() failed"); return (-1); } if (strcmp(buf, name) == 0) return (1); /* * Check if it matches first part of the host name. */ pos = strchr(buf, '.'); if (pos != NULL && (size_t)(pos - buf) == strlen(name) && strncmp(buf, name, pos - buf) == 0) { return (1); } /* * Check if it matches host UUID. */ bufsize = sizeof(buf); if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) { pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed"); return (-1); } if (strcasecmp(buf, name) == 0) return (1); /* * Check if it matches hostid. */ bufsize = sizeof(hostid); if (sysctlbyname("kern.hostid", &hostid, &bufsize, NULL, 0) < 0) { pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostid) failed"); return (-1); } (void)snprintf(buf, sizeof(buf), "hostid%lu", hostid); if (strcmp(buf, name) == 0) return (1); /* * Looks like this isn't about us. */ return (0); } static bool family_supported(int family) { int sock; sock = socket(family, SOCK_STREAM, 0); if (sock == -1 && errno == EPROTONOSUPPORT) return (false); if (sock >= 0) (void)close(sock); return (true); } static int node_names(char **namesp) { static char names[MAXHOSTNAMELEN * 3]; char buf[MAXHOSTNAMELEN]; unsigned long hostid; char *pos; size_t bufsize; if (gethostname(buf, sizeof(buf)) < 0) { pjdlog_errno(LOG_ERR, "gethostname() failed"); return (-1); } /* First component of the host name. */ pos = strchr(buf, '.'); if (pos != NULL && pos != buf) { (void)strlcpy(names, buf, MIN((size_t)(pos - buf + 1), sizeof(names))); (void)strlcat(names, ", ", sizeof(names)); } /* Full host name. */ (void)strlcat(names, buf, sizeof(names)); (void)strlcat(names, ", ", sizeof(names)); /* Host UUID. */ bufsize = sizeof(buf); if (sysctlbyname("kern.hostuuid", buf, &bufsize, NULL, 0) < 0) { pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostuuid) failed"); return (-1); } (void)strlcat(names, buf, sizeof(names)); (void)strlcat(names, ", ", sizeof(names)); /* Host ID. */ bufsize = sizeof(hostid); if (sysctlbyname("kern.hostid", &hostid, &bufsize, NULL, 0) < 0) { pjdlog_errno(LOG_ERR, "sysctlbyname(kern.hostid) failed"); return (-1); } (void)snprintf(buf, sizeof(buf), "hostid%lu", hostid); (void)strlcat(names, buf, sizeof(names)); *namesp = names; return (0); } void yyerror(const char *str) { pjdlog_error("Unable to parse configuration file at line %d near '%s': %s", lineno, yytext, str); } struct hastd_config * yy_config_parse(const char *config, bool exitonerror) { int ret; curres = NULL; mynode = false; depth = 0; lineno = 0; depth0_timeout = HAST_TIMEOUT; depth0_replication = HAST_REPLICATION_MEMSYNC; depth0_checksum = HAST_CHECKSUM_NONE; depth0_compression = HAST_COMPRESSION_HOLE; strlcpy(depth0_control, HAST_CONTROL, sizeof(depth0_control)); strlcpy(depth0_pidfile, HASTD_PIDFILE, sizeof(depth0_pidfile)); TAILQ_INIT(&depth0_listen); strlcpy(depth0_listen_tcp4, HASTD_LISTEN_TCP4, sizeof(depth0_listen_tcp4)); strlcpy(depth0_listen_tcp6, HASTD_LISTEN_TCP6, sizeof(depth0_listen_tcp6)); depth0_exec[0] = '\0'; depth0_metaflush = 1; lconfig = calloc(1, sizeof(*lconfig)); if (lconfig == NULL) { pjdlog_error("Unable to allocate memory for configuration."); if (exitonerror) exit(EX_TEMPFAIL); return (NULL); } TAILQ_INIT(&lconfig->hc_listen); TAILQ_INIT(&lconfig->hc_resources); yyin = fopen(config, "r"); if (yyin == NULL) { pjdlog_errno(LOG_ERR, "Unable to open configuration file %s", config); yy_config_free(lconfig); if (exitonerror) exit(EX_OSFILE); return (NULL); } yyrestart(yyin); ret = yyparse(); fclose(yyin); if (ret != 0) { yy_config_free(lconfig); if (exitonerror) exit(EX_CONFIG); return (NULL); } /* * Let's see if everything is set up. */ if (lconfig->hc_controladdr[0] == '\0') { strlcpy(lconfig->hc_controladdr, depth0_control, sizeof(lconfig->hc_controladdr)); } if (lconfig->hc_pidfile[0] == '\0') { strlcpy(lconfig->hc_pidfile, depth0_pidfile, sizeof(lconfig->hc_pidfile)); } if (!TAILQ_EMPTY(&depth0_listen)) TAILQ_CONCAT(&lconfig->hc_listen, &depth0_listen, hl_next); if (TAILQ_EMPTY(&lconfig->hc_listen)) { struct hastd_listen *lst; if (family_supported(AF_INET)) { lst = calloc(1, sizeof(*lst)); if (lst == NULL) { pjdlog_error("Unable to allocate memory for listen address."); yy_config_free(lconfig); if (exitonerror) exit(EX_TEMPFAIL); return (NULL); } (void)strlcpy(lst->hl_addr, depth0_listen_tcp4, sizeof(lst->hl_addr)); TAILQ_INSERT_TAIL(&lconfig->hc_listen, lst, hl_next); } else { pjdlog_debug(1, "No IPv4 support in the kernel, not listening on IPv4 address."); } if (family_supported(AF_INET6)) { lst = calloc(1, sizeof(*lst)); if (lst == NULL) { pjdlog_error("Unable to allocate memory for listen address."); yy_config_free(lconfig); if (exitonerror) exit(EX_TEMPFAIL); return (NULL); } (void)strlcpy(lst->hl_addr, depth0_listen_tcp6, sizeof(lst->hl_addr)); TAILQ_INSERT_TAIL(&lconfig->hc_listen, lst, hl_next); } else { pjdlog_debug(1, "No IPv6 support in the kernel, not listening on IPv6 address."); } if (TAILQ_EMPTY(&lconfig->hc_listen)) { pjdlog_error("No address to listen on."); yy_config_free(lconfig); if (exitonerror) exit(EX_TEMPFAIL); return (NULL); } } TAILQ_FOREACH(curres, &lconfig->hc_resources, hr_next) { PJDLOG_ASSERT(curres->hr_provname[0] != '\0'); PJDLOG_ASSERT(curres->hr_localpath[0] != '\0'); PJDLOG_ASSERT(curres->hr_remoteaddr[0] != '\0'); if (curres->hr_replication == -1) { /* * Replication is not set at resource-level. * Use global or default setting. */ curres->hr_replication = depth0_replication; curres->hr_original_replication = depth0_replication; } if (curres->hr_checksum == -1) { /* * Checksum is not set at resource-level. * Use global or default setting. */ curres->hr_checksum = depth0_checksum; } if (curres->hr_compression == -1) { /* * Compression is not set at resource-level. * Use global or default setting. */ curres->hr_compression = depth0_compression; } if (curres->hr_timeout == -1) { /* * Timeout is not set at resource-level. * Use global or default setting. */ curres->hr_timeout = depth0_timeout; } if (curres->hr_exec[0] == '\0') { /* * Exec is not set at resource-level. * Use global or default setting. */ strlcpy(curres->hr_exec, depth0_exec, sizeof(curres->hr_exec)); } if (curres->hr_metaflush == -1) { /* * Metaflush is not set at resource-level. * Use global or default setting. */ curres->hr_metaflush = depth0_metaflush; } } return (lconfig); } void yy_config_free(struct hastd_config *config) { struct hastd_listen *lst; struct hast_resource *res; while ((lst = TAILQ_FIRST(&depth0_listen)) != NULL) { TAILQ_REMOVE(&depth0_listen, lst, hl_next); free(lst); } while ((lst = TAILQ_FIRST(&config->hc_listen)) != NULL) { TAILQ_REMOVE(&config->hc_listen, lst, hl_next); free(lst); } while ((res = TAILQ_FIRST(&config->hc_resources)) != NULL) { TAILQ_REMOVE(&config->hc_resources, res, hr_next); free(res); } free(config); }