diff --git a/src/attr.c b/src/attr.c index 7dae7f6..96c5e06 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,488 +1,490 @@ /* * Copyright (C) 2015 Dejan Muhamedagic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include "attr.h" #include "booth.h" #include "ticket.h" #include "pacemaker.h" void print_geostore_usage(void) { printf( "Usage:\n" " geostore {list|set|get|delete} [-t ticket] [options] attr [value]\n" "\n" " list: List all attributes\n" " set: Set attribute to a value\n" " get: Get attribute's value\n" " delete: Delete attribute\n" "\n" " -t Ticket where attribute resides\n" " (required, if more than one ticket is configured)\n" "\n" "Options:\n" " -c FILE Specify config file [default " BOOTH_DEFAULT_CONF "]\n" " Can be a path or just a name without \".conf\" suffix\n" " -s Connect to a different site\n" " -h Print this help\n" "\n" "Examples:\n" "\n" " # geostore list -t ticket-A -s 10.121.8.183\n" " # geostore set -s 10.121.8.183 sr_status ACTIVE\n" " # geostore get -t ticket-A -s 10.121.8.183 sr_status\n" " # geostore delete -s 10.121.8.183 sr_status\n" "\n" "See the geostore(8) man page for more details.\n" ); } /* * the client side */ /* cl has all the input parameters: * ticket, attr name, attr value */ -int test_attr_reply(cmd_result_t reply_code, cmd_request_t cmd) +int test_attr_reply(struct command_line *cl, cmd_result_t reply_code) { int rv = 0; const char *op_str = ""; - switch (cmd) { + switch (cl->type) { case ATTR_SET: op_str = "set"; break; case ATTR_GET: op_str = "get"; break; case ATTR_LIST: op_str = "list"; break; case ATTR_DEL: op_str = "delete"; break; default: log_error("internal error reading reply result!"); return -1; } switch (reply_code) { case RLT_ASYNC: log_info("%s command sent, result will be returned " "asynchronously.", op_str); rv = 0; break; case RLT_SYNC_SUCC: case RLT_SUCCESS: - if (cmd == ATTR_SET) + if (cl->type == ATTR_SET) log_info("%s succeeded!", op_str); rv = 0; break; case RLT_SYNC_FAIL: log_info("%s failed!", op_str); rv = -1; break; case RLT_INVALID_ARG: log_error("ticket \"%s\" does not exist", - cl.attr_msg.attr.tkt_id); + cl->attr_msg.attr.tkt_id); rv = 1; break; case RLT_NO_SUCH_ATTR: log_error("attribute \"%s\" not set", - cl.attr_msg.attr.name); + cl->attr_msg.attr.name); rv = 1; break; case RLT_AUTH: log_error("authentication error"); rv = -1; break; default: log_error("got an error code: %x", rv); rv = -1; } return rv; } /* read the server's reply * need to first get the header which contains the length of the * reply * return codes: * -2: header not received * -1: header received, but message too short * >=0: success */ static int read_server_reply( struct booth_transport const *tpt, struct booth_site *site, char *msg) { struct boothc_header *header; int rv; int len; header = (struct boothc_header *)msg; rv = tpt->recv(site, header, sizeof(*header)); if (rv < 0) { return -2; } len = ntohl(header->length); rv = tpt->recv(site, msg+len, len-sizeof(*header)); if (rv < 0) { return -1; } return rv; } -int do_attr_command(struct booth_config *conf_ptr, cmd_request_t cmd) +int do_attr_command(struct command_line *cl, struct booth_config *conf_ptr) { struct booth_site *site = NULL; struct boothc_header *header; struct booth_transport const *tpt = NULL; int len, rv = -1; char *msg = NULL; assert(conf_ptr != NULL && conf_ptr->transport != NULL); + assert(cl != NULL); - if (!*cl.site) + if (*cl->site == '\0') site = local; else { - if (!find_site_by_name(conf_ptr, cl.site, &site, 1)) { - log_error("Site \"%s\" not configured.", cl.site); + if (!find_site_by_name(conf_ptr, cl->site, &site, 1)) { + log_error("Site \"%s\" not configured.", cl->site); goto out_close; } } if (site->type == ARBITRATOR) { if (site == local) { log_error("We're just an arbitrator, no attributes here."); } else { - log_error("%s is just an arbitrator, no attributes there.", cl.site); + log_error("%s is just an arbitrator, no attributes there.", + cl->site); } goto out_close; } tpt = *conf_ptr->transport + TCP; - init_header(conf_ptr, &cl.attr_msg.header, cmd, 0, cl.options, 0, 0, - sizeof(cl.attr_msg)); + init_header(conf_ptr, &cl->attr_msg.header, cl->type, 0, cl->options, + 0, 0, sizeof(cl->attr_msg)); rv = tpt->open(site); if (rv < 0) goto out_close; - rv = tpt->send(conf_ptr, site, &cl.attr_msg, sendmsglen(&cl.attr_msg)); + rv = tpt->send(conf_ptr, site, &cl->attr_msg, sendmsglen(&cl->attr_msg)); if (rv < 0) goto out_close; msg = malloc(MAX_MSG_LEN); if (!msg) { log_error("out of memory"); rv = -1; goto out_close; } rv = read_server_reply(tpt, site, msg); header = (struct boothc_header *)msg; if (rv < 0) { if (rv == -1) - (void)test_attr_reply(ntohl(header->result), cmd); + (void) test_attr_reply(cl, ntohl(header->result)); goto out_close; } len = ntohl(header->length); if (check_boothc_header(header, len) < 0) { log_error("message from %s receive error", site_string(site)); rv = -1; goto out_close; } if (check_auth(conf_ptr, site, msg, len)) { log_error("%s failed to authenticate", site_string(site)); rv = -1; goto out_close; } - rv = test_attr_reply(ntohl(header->result), cmd); + rv = test_attr_reply(cl, ntohl(header->result)); out_close: if (tpt && site) tpt->close(site); if (msg) free(msg); return rv; } /* * the server side */ /* need to invert gboolean, our success is 0 */ #define gbool2rlt(i) (i ? RLT_SUCCESS : RLT_SYNC_FAIL) static void free_geo_attr(gpointer data) { struct geo_attr *a = (struct geo_attr *)data; if (!a) return; g_free(a->val); g_free(a); } int store_geo_attr(struct ticket_config *tk, const char *name, const char *val, int notime) { struct geo_attr *a; GDestroyNotify free_geo_attr_notify = free_geo_attr; if (!tk) return -1; /* * allocate new, if attr doesn't already exist * copy the attribute value * send status */ if (!tk->attr) tk->attr = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, free_geo_attr_notify); if (!tk->attr) { log_error("out of memory"); return -1; } if (strnlen(name, BOOTH_NAME_LEN) == BOOTH_NAME_LEN) tk_log_warn("name of the attribute too long (%d+ bytes), skipped", BOOTH_NAME_LEN); else if (strnlen(val, BOOTH_ATTRVAL_LEN) == BOOTH_ATTRVAL_LEN) tk_log_warn("value of the attribute too long (%d+ bytes), skipped", BOOTH_ATTRVAL_LEN); else { a = (struct geo_attr *)calloc(1, sizeof(struct geo_attr)); if (!a) { log_error("out of memory"); return -1; } a->val = g_strdup(val); if (!notime) get_time(&a->update_ts); g_hash_table_insert(tk->attr, g_strdup(name), a); } return 0; } static cmd_result_t attr_set(struct booth_config *conf_ptr, struct ticket_config *tk, struct boothc_attr_msg *msg) { int rc; assert(conf_ptr != NULL); rc = store_geo_attr(tk, msg->attr.name, msg->attr.val, 0); if (rc) { return RLT_SYNC_FAIL; } (void) conf_ptr->ticket_handler->set_attr(tk, msg->attr.name, msg->attr.val); return RLT_SUCCESS; } static cmd_result_t attr_del(struct booth_config *conf_ptr, struct ticket_config *tk, struct boothc_attr_msg *msg) { gboolean rv; gpointer orig_key, value; assert(conf_ptr != NULL); /* * lookup attr * deallocate, if found * send status */ if (!tk->attr) return RLT_NO_SUCH_ATTR; rv = g_hash_table_lookup_extended(tk->attr, msg->attr.name, &orig_key, &value); if (!rv) return RLT_NO_SUCH_ATTR; rv = g_hash_table_remove(tk->attr, msg->attr.name); (void) conf_ptr->ticket_handler->del_attr(tk, msg->attr.name); return gbool2rlt(rv); } static void append_attr(gpointer key, gpointer value, gpointer user_data) { char *attr_name = (char *)key; struct geo_attr *a = (struct geo_attr *)value; GString *data = (GString *)user_data; char time_str[64]; time_t ts; if (is_time_set(&a->update_ts)) { ts = wall_ts(&a->update_ts); strftime(time_str, sizeof(time_str), "%F %T", localtime(&ts)); } else { time_str[0] = '\0'; } g_string_append_printf(data, "%s %s %s\n", attr_name, a->val, time_str); } static cmd_result_t attr_get(struct booth_config *conf_ptr, struct ticket_config *tk, int fd, struct boothc_attr_msg *msg) { cmd_result_t rv = RLT_SUCCESS; struct boothc_hdr_msg hdr; struct geo_attr *a; GString *attr_val; /* * lookup attr * send value */ a = (struct geo_attr *)g_hash_table_lookup(tk->attr, msg->attr.name); if (!a) return RLT_NO_SUCH_ATTR; attr_val = g_string_new(NULL); if (!attr_val) { log_error("out of memory"); return RLT_SYNC_FAIL; } g_string_printf(attr_val, "%s\n", a->val); init_header(conf_ptr, &hdr.header, ATTR_GET, 0, 0, RLT_SUCCESS, 0, sizeof(hdr) + attr_val->len); if (send_header_plus(conf_ptr, fd, &hdr, attr_val->str, attr_val->len)) rv = RLT_SYNC_FAIL; if (attr_val) g_string_free(attr_val, FALSE); return rv; } static cmd_result_t attr_list(struct booth_config *conf_ptr, struct ticket_config *tk, int fd, struct boothc_attr_msg *msg) { GString *data; cmd_result_t rv; struct boothc_hdr_msg hdr; /* * list all attributes for the ticket * send the list */ data = g_string_sized_new(512); if (!data) { log_error("out of memory"); return RLT_SYNC_FAIL; } g_hash_table_foreach(tk->attr, append_attr, data); init_header(conf_ptr, &hdr.header, ATTR_LIST, 0, 0, RLT_SUCCESS, 0, sizeof(hdr) + data->len); rv = send_header_plus(conf_ptr, fd, &hdr, data->str, data->len); if (data) g_string_free(data, FALSE); return rv; } int process_attr_request(struct booth_config *conf_ptr, struct client *req_client, void *buf) { cmd_result_t rv = RLT_SYNC_FAIL; struct ticket_config *tk; int cmd; struct boothc_attr_msg *msg; struct boothc_hdr_msg hdr; msg = (struct boothc_attr_msg *)buf; cmd = ntohl(msg->header.cmd); if (!check_ticket(conf_ptr, msg->attr.tkt_id, &tk)) { log_warn("client referenced unknown ticket %s", msg->attr.tkt_id); rv = RLT_INVALID_ARG; goto reply_now; } switch (cmd) { case ATTR_LIST: rv = attr_list(conf_ptr, tk, req_client->fd, msg); if (rv) goto reply_now; return 1; case ATTR_GET: rv = attr_get(conf_ptr, tk, req_client->fd, msg); if (rv) goto reply_now; return 1; case ATTR_SET: rv = attr_set(conf_ptr, tk, msg); break; case ATTR_DEL: rv = attr_del(conf_ptr, tk, msg); break; } reply_now: init_header(conf_ptr, &hdr.header, CL_RESULT, 0, 0, rv, 0, sizeof(hdr)); send_header_plus(conf_ptr, req_client->fd, &hdr, NULL, 0); return 1; } /* read attr message from another site */ /* this is a NOOP and it should never be invoked * only clients retrieve/manage attributes and they connect * directly to the target site */ int attr_recv(struct booth_config *conf_ptr, void *buf, struct booth_site *source) { struct boothc_attr_msg *msg; struct ticket_config *tk; msg = (struct boothc_attr_msg *)buf; log_warn("unexpected attribute message from %s", site_string(source)); if (!check_ticket(conf_ptr, msg->attr.tkt_id, &tk)) { log_warn("got invalid ticket name %s from %s", msg->attr.tkt_id, site_string(source)); source->invalid_cnt++; return -1; } return 0; } diff --git a/src/attr.h b/src/attr.h index 2671211..d35fcad 100644 --- a/src/attr.h +++ b/src/attr.h @@ -1,74 +1,84 @@ /* * Copyright (C) 2015 Dejan Muhamedagic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _ATTR_H #define _ATTR_H #define ATTR_PROG "geostore" #include "b_config.h" #include "log.h" #include #include #include "booth.h" #include "timer.h" #include void print_geostore_usage(void); -int test_attr_reply(cmd_result_t reply_code, cmd_request_t cmd); + +/** + * @internal + * Late handling of the response towards the client + * + * @param[in] cl parsed command line form + * @param[in] reply_code what the inner handling returns + * + * @return 0 on success, -1 on failure, 1 when "cannot serve" + */ +int test_attr_reply(struct command_line *cl, cmd_result_t reply_code); /** * @internal * Carry out a geo-atribute related command * + * @param[in] cl parsed command line structure * @param[inout] conf_ptr config object to refer to - * @param[in] cmd what to perform * * @return 0 or negative value (-1 or -errno) on error */ -int do_attr_command(struct booth_config *conf_ptr, cmd_request_t cmd); +int do_attr_command(struct command_line *cl, struct booth_config *conf_ptr); /** * @internal * Facade to handle geostore related operations * * @param[inout] conf_ptr config object to refer to * @param[in] req_client client structure of the sender * @param[in] buf message itself * * @return 1 or see #attr_list, #attr_get, #attr_set, #attr_del */ int process_attr_request(struct booth_config *conf_ptr, struct client *req_client, void *buf); /** * @internal * Second stage of incoming datagram handling (after authentication) * * @param[inout] conf_ptr config object to refer to * @param[in] buf message itself * @param[in] source site structure of the sender * * @return -1 on error, 0 otherwise */ int attr_recv(struct booth_config *conf_ptr, void *buf, struct booth_site *source); int store_geo_attr(struct ticket_config *tk, const char *name, const char *val, int notime); #endif /* _ATTR_H */ diff --git a/src/booth.h b/src/booth.h index 68fc748..5dc2795 100644 --- a/src/booth.h +++ b/src/booth.h @@ -1,416 +1,427 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _BOOTH_H #define _BOOTH_H #include #include #include #include #include #include #include "timer.h" struct booth_config; #define BOOTH_RUN_DIR "/var/run/booth/" #define BOOTH_LOG_DIR "/var/log" #define BOOTH_LOGFILE_NAME "booth.log" #define BOOTH_DEFAULT_CONF_DIR "/etc/booth/" #define BOOTH_DEFAULT_CONF_NAME "booth" #define BOOTH_DEFAULT_CONF_EXT ".conf" #define BOOTH_DEFAULT_CONF \ BOOTH_DEFAULT_CONF_DIR BOOTH_DEFAULT_CONF_NAME BOOTH_DEFAULT_CONF_EXT #define DAEMON_NAME "boothd" #define BOOTH_PATH_LEN 127 #define BOOTH_MAX_KEY_LEN 64 #define BOOTH_MIN_KEY_LEN 8 /* hash size is 160 bits (sha1), but add a bit more space in case * stronger hashes are required */ #define BOOTH_MAC_SIZE 24 /* tolerate packets which are not older than 10 minutes */ #define BOOTH_DEFAULT_MAX_TIME_SKEW 600 #define BOOTH_DEFAULT_PORT 9929 #define BOOTHC_MAGIC 0x5F1BA08C #define BOOTHC_VERSION 0x00010003 /** Timeout value for poll(). * Determines frequency of periodic jobs, eg. when send-retries are done. * See process_tickets(). */ #define POLL_TIMEOUT 100 /** @{ */ /** The on-network data structures and constants. */ #define BOOTH_NAME_LEN 64 #define BOOTH_ATTRVAL_LEN 128 #define CHAR2CONST(a,b,c,d) ((a << 24) | (b << 16) | (c << 8) | d) /* Says that the ticket shouldn't be active anywhere. * NONE wouldn't be specific enough. */ #define NO_ONE ((uint32_t)-1) /* Says that another one should recover. */ #define TICKET_LOST CHAR2CONST('L', 'O', 'S', 'T') typedef char boothc_site[BOOTH_NAME_LEN]; typedef char boothc_ticket[BOOTH_NAME_LEN]; typedef char boothc_attr[BOOTH_NAME_LEN]; typedef char boothc_attr_value[BOOTH_ATTRVAL_LEN]; /* message option bits */ enum { BOOTH_OPT_AUTH = 1, /* authentication */ BOOTH_OPT_ATTR = 4, /* attr message type, otherwise ticket */ }; struct boothc_header { /** Various options, message type, authentication */ uint32_t opts; /** Generation info (used for authentication) * This is something that would need to be monotone * incremental. CLOCK_MONOTONIC should fit the purpose. On * failover, however, it may happen that the new host has a * clock which is significantly behind the clock of old host. * We'll need to relax a bit for the nodes which are starting * (just accept all OP_STATUS). */ uint32_t secs; /* seconds */ uint32_t usecs; /* microseconds */ /** BOOTHC_MAGIC */ uint32_t magic; /** BOOTHC_VERSION */ uint32_t version; /** Packet source; site_id. See add_site(). */ uint32_t from; /** Length including header */ uint32_t length; /** The command respectively protocol state. See cmd_request_t. */ uint32_t cmd; /** The matching request (what do we reply to). See cmd_request_t. */ uint32_t request; /** Command options. */ uint32_t options; /** The reason for this RPC. */ uint32_t reason; /** Result of operation. 0 == OK */ uint32_t result; char data[0]; } __attribute__((packed)); struct ticket_msg { /** Ticket name. */ boothc_ticket id; /** Current leader. May be NO_ONE. See add_site(). * For a OP_REQ_VOTE this is */ uint32_t leader; /** Current term. */ uint32_t term; uint32_t term_valid_for; /* Perhaps we need to send a status along, too - like * starting, running, stopping, error, ...? */ } __attribute__((packed)); struct attr_msg { /** Ticket name. */ boothc_ticket tkt_id; /** Attribute name. */ boothc_attr name; /** The value. */ boothc_attr_value val; } __attribute__((packed)); /* GEO attributes * attributes should be regularly updated. */ struct geo_attr { /** Update timestamp. */ timetype update_ts; /** The value. */ char *val; /** Who set it (currently unused) struct booth_site *origin; */ } __attribute__((packed)); struct hmac { /** hash id, currently set to constant BOOTH_HASH */ uint32_t hid; /** the calculated hash, BOOTH_MAC_SIZE is big enough to * accommodate the hash of type hid */ unsigned char hash[BOOTH_MAC_SIZE]; } __attribute__((packed)); struct boothc_hdr_msg { struct boothc_header header; struct hmac hmac; } __attribute__((packed)); struct boothc_ticket_msg { struct boothc_header header; struct ticket_msg ticket; struct hmac hmac; } __attribute__((packed)); struct boothc_attr_msg { struct boothc_header header; struct attr_msg attr; struct hmac hmac; } __attribute__((packed)); typedef enum { /* 0x43 = "C"ommands */ CMD_LIST = CHAR2CONST('C', 'L', 's', 't'), CMD_GRANT = CHAR2CONST('C', 'G', 'n', 't'), CMD_REVOKE = CHAR2CONST('C', 'R', 'v', 'k'), CMD_PEERS = CHAR2CONST('P', 'e', 'e', 'r'), /* Replies */ CL_RESULT = CHAR2CONST('R', 's', 'l', 't'), CL_LIST = CHAR2CONST('R', 'L', 's', 't'), CL_GRANT = CHAR2CONST('R', 'G', 'n', 't'), CL_REVOKE = CHAR2CONST('R', 'R', 'v', 'k'), /* get status from another server */ OP_STATUS = CHAR2CONST('S', 't', 'a', 't'), OP_MY_INDEX = CHAR2CONST('M', 'I', 'd', 'x'), /* reply to status */ /* Raft */ OP_REQ_VOTE = CHAR2CONST('R', 'V', 'o', 't'), /* start election */ OP_VOTE_FOR = CHAR2CONST('V', 't', 'F', 'r'), /* reply to REQ_VOTE */ OP_HEARTBEAT= CHAR2CONST('H', 'r', 't', 'B'), /* Heartbeat */ OP_ACK = CHAR2CONST('A', 'c', 'k', '.'), /* Ack for heartbeats and revokes */ OP_UPDATE = CHAR2CONST('U', 'p', 'd', 'E'), /* Update ticket */ OP_REVOKE = CHAR2CONST('R', 'e', 'v', 'k'), /* Revoke ticket */ OP_REJECTED = CHAR2CONST('R', 'J', 'C', '!'), /* Attributes */ ATTR_SET = CHAR2CONST('A', 'S', 'e', 't'), ATTR_GET = CHAR2CONST('A', 'G', 'e', 't'), ATTR_DEL = CHAR2CONST('A', 'D', 'e', 'l'), ATTR_LIST = CHAR2CONST('A', 'L', 's', 't'), } cmd_request_t; typedef enum { /* for compatibility with other functions */ RLT_SUCCESS = 0, RLT_ASYNC = CHAR2CONST('A', 's', 'y', 'n'), RLT_MORE = CHAR2CONST('M', 'o', 'r', 'e'), RLT_SYNC_SUCC = CHAR2CONST('S', 'c', 'c', 's'), RLT_SYNC_FAIL = CHAR2CONST('F', 'a', 'i', 'l'), RLT_INVALID_ARG = CHAR2CONST('I', 'A', 'r', 'g'), RLT_NO_SUCH_ATTR = CHAR2CONST('N', 'A', 't', 'r'), RLT_CIB_PENDING = CHAR2CONST('P', 'e', 'n', 'd'), RLT_EXT_FAILED = CHAR2CONST('X', 'P', 'r', 'g'), RLT_ATTR_PREREQ = CHAR2CONST('A', 'P', 'r', 'q'), RLT_TICKET_IDLE = CHAR2CONST('T', 'i', 'd', 'l'), RLT_OVERGRANT = CHAR2CONST('O', 'v', 'e', 'r'), RLT_PROBABLY_SUCCESS = CHAR2CONST('S', 'u', 'c', '?'), RLT_BUSY = CHAR2CONST('B', 'u', 's', 'y'), RLT_AUTH = CHAR2CONST('A', 'u', 't', 'h'), RLT_TERM_OUTDATED = CHAR2CONST('T', 'O', 'd', 't'), RLT_TERM_STILL_VALID = CHAR2CONST('T', 'V', 'l', 'd'), RLT_YOU_OUTDATED = CHAR2CONST('O', 'u', 't', 'd'), RLT_REDIRECT = CHAR2CONST('R', 'e', 'd', 'r'), } cmd_result_t; typedef enum { /* for compatibility with other functions */ OR_JUST_SO = 0, OR_AGAIN = CHAR2CONST('A', 'a', 'a', 'a'), OR_TKT_LOST = CHAR2CONST('T', 'L', 's', 't'), OR_REACQUIRE = CHAR2CONST('R', 'a', 'c', 'q'), OR_ADMIN = CHAR2CONST('A', 'd', 'm', 'n'), OR_LOCAL_FAIL = CHAR2CONST('L', 'o', 'c', 'F'), OR_STEPDOWN = CHAR2CONST('S', 'p', 'd', 'n'), OR_SPLIT = CHAR2CONST('S', 'p', 'l', 't'), } cmd_reason_t; /* bitwise command options */ typedef enum { OPT_IMMEDIATE = 1, /* immediate grant */ OPT_WAIT = 2, /* wait for the elections' outcome */ OPT_WAIT_COMMIT = 4, /* wait for the ticket commit to CIB */ } cmd_options_t; /** @} */ /** @{ */ struct booth_site { /** Calculated ID. See add_site(). */ int site_id; int type; int local; /** Roles, like ACCEPTOR, PROPOSER, or LEARNER. Not really used ATM. */ int role; boothc_site addr_string; int tcp_fd; int udp_fd; /* 0-based, used for indexing into per-ticket weights. * -1 for no_leader. */ int index; uint64_t bitmask; unsigned short family; union { struct sockaddr_in sa4; struct sockaddr_in6 sa6; }; int saddrlen; int addrlen; /** statistics */ time_t last_recv; unsigned int sent_cnt; unsigned int sent_err_cnt; unsigned int resend_cnt; unsigned int recv_cnt; unsigned int recv_err_cnt; unsigned int sec_cnt; unsigned int invalid_cnt; /** last timestamp seen from this site */ uint32_t last_secs; uint32_t last_usecs; } __attribute__((packed)); extern struct booth_site *local; extern struct booth_site *const no_leader; /** @} */ struct booth_transport; struct client { int fd; const struct booth_transport *transport; struct boothc_ticket_msg *msg; int offset; /* bytes read so far into msg */ void (*workfn)(struct booth_config *conf_ptr, int); void (*deadfn)(int); }; extern struct client *clients; extern struct pollfd *pollfds; /** * @internal * For an established-connection socket, finalize the handling callbacks * * @param[in] file descriptor of the respective client socket * @param[inout] tpt precooked transport handling callbacks to finalize * @param[in] workfn callback to process incoming messages * @param[in] workfn callback to handle termination of the connection * * @return number of clients tracked (incl. this one) */ int client_add(int fd, const struct booth_transport *tpt, void (*workfn)(struct booth_config *conf_ptr, int ci), void (*deadfn)(int ci)); int find_client_by_fd(int fd); -void safe_copy(char *dest, char *value, size_t buflen, const char *description); + +/** + * @internal + * Like strncpy, but with explicit protection and better diagnostics + * + * @param[out] dest where to copy the string to + * @param[in] value where to copy the string from + * @param[in] buflen nmaximum size of #dest (incl. trailing '\0', or sizeof) + * @param[in] description how to refer to the target as + * + * @return number of clients tracked (incl. this one) + */ +void safe_copy(char *dest, const char *value, size_t buflen, + const char *description); /** * @internal * Re-read and reflect possibly new contents of the authentication key file * * @note XXX UNUSED * * @param[inout] conf_ptr config object to refer to * * @return 0 in case of success, -1 otherwise */ int update_authkey(struct booth_config *conf_ptr); /** * @internal * Response to "get all servers we know about" * * @param[inout] conf_ptr config object to refer to * @param[in] fd file descriptor of the socket to respond to */ void list_peers(struct booth_config *conf_ptr, int fd); struct command_line { int type; /* ACT_ */ int op; /* OP_ */ int options; /* OPT_ */ char configfile[BOOTH_PATH_LEN]; char lockfile[BOOTH_PATH_LEN]; char site[BOOTH_NAME_LEN]; struct boothc_ticket_msg msg; struct boothc_attr_msg attr_msg; }; -extern struct command_line cl; - /* http://gcc.gnu.org/onlinedocs/gcc/Typeof.html */ #define min(a__,b__) \ ({ typeof (a__) _a = (a__); \ typeof (b__) _b = (b__); \ _a < _b ? _a : _b; }) #define max(a__,b__) \ ({ typeof (a__) _a = (a__); \ typeof (b__) _b = (b__); \ _a > _b ? _a : _b; }) #endif /* _BOOTH_H */ diff --git a/src/config.c b/src/config.c index 0e10aca..0fd86b0 100644 --- a/src/config.c +++ b/src/config.c @@ -1,1029 +1,1033 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "b_config.h" #include "booth.h" #include "config.h" #include "raft.h" #include "ticket.h" #include "log.h" static int ticket_size = 0; static int ticket_realloc(struct booth_config *conf_ptr) { const int added = 5; int had, want; void *p; assert(conf_ptr != NULL); had = conf_ptr->ticket_allocated; want = had + added; p = realloc(conf_ptr->ticket, sizeof(struct ticket_config) * want); if (!p) { log_error("can't alloc more tickets"); return -ENOMEM; } conf_ptr->ticket = p; memset(conf_ptr->ticket + had, 0, sizeof(struct ticket_config) * added); conf_ptr->ticket_allocated = want; return 0; } static void hostname_to_ip(char * hostname) { struct hostent *he; struct in_addr **addr_list; if ((he = gethostbyname(hostname)) == NULL) { log_error("can't find IP for the host \"%s\"", hostname); return; } addr_list = (struct in_addr **) he->h_addr_list; /* Return the first found address */ if (addr_list[0] != NULL) { strncpy(hostname, inet_ntoa(*addr_list[0]), BOOTH_NAME_LEN - 1); /* buffer overflow will not happen (IPv6 notation < 63 chars), but suppress the warnings */ hostname[BOOTH_NAME_LEN - 1] = '\0'; } else { log_error("no IP addresses found for the host \"%s\"", hostname); } } static int add_site(struct booth_config *conf_ptr, char *addr_string, int type) { int rv; struct booth_site *site; uLong nid; uint32_t mask; int i; assert(conf_ptr != NULL); rv = 1; if (conf_ptr->site_count == MAX_NODES) { log_error("too many nodes"); goto out; } if (strnlen(addr_string, sizeof(conf_ptr->site[0].addr_string)) >= sizeof(conf_ptr->site[0].addr_string)) { log_error("site address \"%s\" too long", addr_string); goto out; } site = conf_ptr->site + conf_ptr->site_count; site->family = AF_INET; site->type = type; /* buffer overflow will not hapen (we've already checked that addr_string will fit incl. terminating '\0' above), but suppress the warnings with copying everything but the boundary byte, which is valid as-is, since this last byte will be safely pre-zeroed from the struct booth_config initialization */ strncpy(site->addr_string, addr_string, sizeof(site->addr_string) - 1); if (!(inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) && !(inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0)) { /* Not a valid address, so let us try to convert it into an IP address */ hostname_to_ip(site->addr_string); } site->index = conf_ptr->site_count; site->bitmask = 1 << conf_ptr->site_count; /* Catch site overflow */ assert(site->bitmask); conf_ptr->all_bits |= site->bitmask; if (type == SITE) conf_ptr->sites_bits |= site->bitmask; site->tcp_fd = -1; conf_ptr->site_count++; rv = 0; memset(&site->sa6, 0, sizeof(site->sa6)); nid = crc32(0L, NULL, 0); /* Using the ASCII representation in site->addr_string (both sizeof() * and strlen()) gives quite a lot of collisions; a brute-force run * from 0.0.0.0 to 24.0.0.0 gives ~4% collisions, and this tends to * increase even more. * Whether there'll be a collision in real-life, with 3 or 5 nodes, is * another question ... but for now get the ID from the binary * representation - that had *no* collisions up to 32.0.0.0. * Note that POSIX mandates inet_pton to arange the address pointed * to by "dst" in network byte order, assuring little/big-endianess * mutual compatibility. */ if (inet_pton(AF_INET, site->addr_string, &site->sa4.sin_addr) > 0) { site->family = AF_INET; site->sa4.sin_family = site->family; site->sa4.sin_port = htons(conf_ptr->port); site->saddrlen = sizeof(site->sa4); site->addrlen = sizeof(site->sa4.sin_addr); site->site_id = crc32(nid, (void*)&site->sa4.sin_addr, site->addrlen); } else if (inet_pton(AF_INET6, site->addr_string, &site->sa6.sin6_addr) > 0) { site->family = AF_INET6; site->sa6.sin6_family = site->family; site->sa6.sin6_flowinfo = 0; site->sa6.sin6_port = htons(conf_ptr->port); site->saddrlen = sizeof(site->sa6); site->addrlen = sizeof(site->sa6.sin6_addr); site->site_id = crc32(nid, (void*)&site->sa6.sin6_addr, site->addrlen); } else { log_error("Address string \"%s\" is bad", site->addr_string); rv = EINVAL; } /* Make sure we will never collide with NO_ONE, * or be negative (to get "get_local_id() < 0" working). */ mask = 1 << (sizeof(site->site_id)*8 -1); assert(NO_ONE & mask); site->site_id &= ~mask; /* Test for collisions with other sites */ for(i=0; iindex; i++) if (conf_ptr->site[i].site_id == site->site_id) { log_error("Got a site-ID collision. Please file a bug on https://github.com/ClusterLabs/booth/issues/new, attaching the configuration file."); exit(1); } out: return rv; } inline static char *skip_while_in(const char *cp, int (*fn)(int), const char *allowed) { /* strchr() returns a pointer to the terminator if *cp == 0. */ while (*cp && (fn(*cp) || strchr(allowed, *cp))) cp++; /* discard "const" qualifier */ return (char*)cp; } inline static char *skip_while(char *cp, int (*fn)(int)) { while (fn(*cp)) cp++; return cp; } inline static char *skip_until(char *cp, char expected) { while (*cp && *cp != expected) cp++; return cp; } static inline int is_end_of_line(char *cp) { char c = *cp; return c == '\n' || c == 0 || c == '#'; } static int add_ticket(struct booth_config *conf_ptr, const char *name, struct ticket_config **tkp, const struct ticket_config *def) { int rv; struct ticket_config *tk; assert(conf_ptr != NULL); if (conf_ptr->ticket_count == conf_ptr->ticket_allocated) { rv = ticket_realloc(conf_ptr); if (rv < 0) return rv; } tk = conf_ptr->ticket + conf_ptr->ticket_count; conf_ptr->ticket_count++; if (!check_max_len_valid(name, sizeof(tk->name))) { log_error("ticket name \"%s\" too long.", name); return -EINVAL; } if (find_ticket_by_name(conf_ptr, name, NULL)) { log_error("ticket name \"%s\" used again.", name); return -EINVAL; } if (* skip_while_in(name, isalnum, "-/")) { log_error("ticket name \"%s\" invalid; only alphanumeric names.", name); return -EINVAL; } strcpy(tk->name, name); tk->timeout = def->timeout; tk->term_duration = def->term_duration; tk->retries = def->retries; memcpy(tk->weight, def->weight, sizeof(tk->weight)); tk->mode = def->mode; if (tkp) *tkp = tk; return 0; } static int postproc_ticket(struct ticket_config *tk) { if (!tk) return 1; if (!tk->renewal_freq) { tk->renewal_freq = tk->term_duration/2; } if (tk->timeout*(tk->retries+1) >= tk->renewal_freq) { log_error("%s: total amount of time to " "retry sending packets cannot exceed " "renewal frequency " "(%d*(%d+1) >= %d)", tk->name, tk->timeout, tk->retries, tk->renewal_freq); return 0; } return 1; } /* returns number of weights, or -1 on bad input. */ static int parse_weights(const char *input, int weights[MAX_NODES]) { int i, v; char *cp; for(i=0; i= MAX_ARGS) { log_error("too many arguments for the acquire-handler"); free(tk_test.path); return -1; } tk_test.argv[i++] = p; } while (p); return 0; } struct toktab grant_type[] = { { "auto", GRANT_AUTO}, { "manual", GRANT_MANUAL}, { NULL, 0}, }; struct toktab attr_op[] = { {"eq", ATTR_OP_EQ}, {"ne", ATTR_OP_NE}, {NULL, 0}, }; static int lookup_tokval(char *key, struct toktab *tab) { struct toktab *tp; for (tp = tab; tp->str; tp++) { if (!strcmp(tp->str, key)) return tp->val; } return 0; } /* attribute prerequisite */ static int parse_attr_prereq(char *val, struct ticket_config *tk) { struct attr_prereq *ap = NULL; char *p; ap = (struct attr_prereq *)calloc(1, sizeof(struct attr_prereq)); if (!ap) { log_error("out of memory"); return -1; } p = strtok(val, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } ap->grant_type = lookup_tokval(p, grant_type); if (!ap->grant_type) { log_error("%s is not a grant type", p); goto err_out; } p = strtok(NULL, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } if (!(ap->attr_name = strdup(p))) { log_error("out of memory"); goto err_out; } p = strtok(NULL, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } ap->op = lookup_tokval(p, attr_op); if (!ap->op) { log_error("%s is not an attribute operation", p); goto err_out; } p = strtok(NULL, " \t"); if (!p) { log_error("not enough arguments to attr-prereq"); goto err_out; } if (!(ap->attr_val = strdup(p))) { log_error("out of memory"); goto err_out; } tk->attr_prereqs = g_list_append(tk->attr_prereqs, ap); if (!tk->attr_prereqs) { log_error("out of memory"); goto err_out; } return 0; err_out: if (ap) { if (ap->attr_val) free(ap->attr_val); if (ap->attr_name) free(ap->attr_name); free(ap); } return -1; } extern int poll_timeout; int read_config(struct booth_config **conf_pptr, const booth_transport_table_t *transport, const struct ticket_handler *ticket_handler, const char *path, int type) { char line[1024]; FILE *fp; char *s, *key, *val, *end_of_key; const char *error; char *cp, *cp2; int i; int lineno = 0; int got_transport = 0; int min_timeout = 0; struct ticket_config defaults = { { 0 } }; struct ticket_config *current_tk = NULL; assert(conf_pptr != NULL); free(*conf_pptr); fp = fopen(path, "r"); if (!fp) { log_error("failed to open %s: %s", path, strerror(errno)); *conf_pptr = NULL; return -1; } *conf_pptr = malloc(sizeof(struct booth_config) + TICKET_ALLOC * sizeof(struct ticket_config)); if (*conf_pptr == NULL) { fclose(fp); log_error("failed to alloc memory for booth config"); return -ENOMEM; } memset(*conf_pptr, 0, sizeof(struct booth_config) + TICKET_ALLOC * sizeof(struct ticket_config)); ticket_size = TICKET_ALLOC; (*conf_pptr)->transport = transport; (*conf_pptr)->ticket_handler = ticket_handler; (*conf_pptr)->proto = UDP; (*conf_pptr)->port = BOOTH_DEFAULT_PORT; (*conf_pptr)->maxtimeskew = BOOTH_DEFAULT_MAX_TIME_SKEW; (*conf_pptr)->authkey[0] = '\0'; /* Provide safe defaults. -1 is reserved, though. */ (*conf_pptr)->uid = -2; (*conf_pptr)->gid = -2; strcpy((*conf_pptr)->site_user, "hacluster"); strcpy((*conf_pptr)->site_group, "haclient"); strcpy((*conf_pptr)->arb_user, "nobody"); strcpy((*conf_pptr)->arb_group, "nobody"); parse_weights("", defaults.weight); defaults.clu_test.path = NULL; defaults.clu_test.pid = 0; defaults.clu_test.status = 0; defaults.clu_test.progstate = EXTPROG_IDLE; defaults.term_duration = DEFAULT_TICKET_EXPIRY; defaults.timeout = DEFAULT_TICKET_TIMEOUT; defaults.retries = DEFAULT_RETRIES; defaults.acquire_after = 0; defaults.mode = TICKET_MODE_AUTO; error = ""; log_debug("reading config file %s", path); while (fgets(line, sizeof(line), fp)) { lineno++; s = skip_while(line, isspace); if (is_end_of_line(s) || *s == '#') continue; key = s; /* Key */ end_of_key = skip_while_in(key, isalnum, "-_"); if (end_of_key == key) { error = "No key"; goto err; } if (!*end_of_key) goto exp_equal; /* whitespace, and something else but nothing more? */ s = skip_while(end_of_key, isspace); if (*s != '=') { exp_equal: error = "Expected '=' after key"; goto err; } s++; /* It's my buffer, and I terminate if I want to. */ /* But not earlier than that, because we had to check for = */ *end_of_key = 0; /* Value tokenizing */ s = skip_while(s, isspace); switch (*s) { case '"': case '\'': val = s+1; s = skip_until(val, *s); /* Terminate value */ if (!*s) { error = "Unterminated quoted string"; goto err; } /* Remove and skip quote */ *s = 0; s++; if (*(s = skip_while(s, isspace)) && *s != '#') { error = "Surplus data after value"; goto err; } *s = 0; break; case 0: no_value: error = "No value"; goto err; break; default: val = s; /* Rest of line. */ i = strlen(s); /* i > 0 because of "case 0" above. */ while (i > 0 && isspace(s[i-1])) i--; s += i; *s = 0; } if (val == s) goto no_value; if (strlen(key) > BOOTH_NAME_LEN || strlen(val) > BOOTH_NAME_LEN) { error = "key/value too long"; goto err; } if (strcmp(key, "transport") == 0) { if (got_transport) { error = "config file has multiple transport lines"; goto err; } if (strcasecmp(val, "UDP") == 0) (*conf_pptr)->proto = UDP; else if (strcasecmp(val, "SCTP") == 0) (*conf_pptr)->proto = SCTP; else { error = "invalid transport protocol"; goto err; } got_transport = 1; continue; } if (strcmp(key, "port") == 0) { (*conf_pptr)->port = atoi(val); continue; } if (strcmp(key, "name") == 0) { safe_copy((*conf_pptr)->name, val, BOOTH_NAME_LEN, "name"); continue; } #if HAVE_LIBGCRYPT || HAVE_LIBMHASH if (strcmp(key, "authfile") == 0) { safe_copy((*conf_pptr)->authfile, val, BOOTH_PATH_LEN, "authfile"); continue; } if (strcmp(key, "maxtimeskew") == 0) { (*conf_pptr)->maxtimeskew = atoi(val); continue; } #endif if (strcmp(key, "site") == 0) { if (add_site(*conf_pptr, val, SITE)) goto err; continue; } if (strcmp(key, "arbitrator") == 0) { if (add_site(*conf_pptr, val, ARBITRATOR)) goto err; continue; } if (strcmp(key, "site-user") == 0) { safe_copy((*conf_pptr)->site_user, optarg, BOOTH_NAME_LEN, "site-user"); continue; } if (strcmp(key, "site-group") == 0) { safe_copy((*conf_pptr)->site_group, optarg, BOOTH_NAME_LEN, "site-group"); continue; } if (strcmp(key, "arbitrator-user") == 0) { safe_copy((*conf_pptr)->arb_user, optarg, BOOTH_NAME_LEN, "arbitrator-user"); continue; } if (strcmp(key, "arbitrator-group") == 0) { safe_copy((*conf_pptr)->arb_group, optarg, BOOTH_NAME_LEN, "arbitrator-group"); continue; } if (strcmp(key, "debug") == 0) { if (type != CLIENT && type != GEOSTORE) debug_level = max(debug_level, atoi(val)); continue; } if (strcmp(key, "ticket") == 0) { if (current_tk && strcmp(current_tk->name, "__defaults__")) { if (!postproc_ticket(current_tk)) { goto err; } } if (!strcmp(val, "__defaults__")) { current_tk = &defaults; } else if (add_ticket(*conf_pptr, val, ¤t_tk, &defaults)) { goto err; } continue; } /* current_tk must be allocated at this point, otherwise * we don't know to which ticket the key refers */ if (!current_tk) { error = "Unexpected keyword"; goto err; } if (strcmp(key, "expire") == 0) { current_tk->term_duration = read_time(val); if (current_tk->term_duration <= 0) { error = "Expected time >0 for expire"; goto err; } continue; } if (strcmp(key, "timeout") == 0) { current_tk->timeout = read_time(val); if (current_tk->timeout <= 0) { error = "Expected time >0 for timeout"; goto err; } if (!min_timeout) { min_timeout = current_tk->timeout; } else { min_timeout = min(min_timeout, current_tk->timeout); } continue; } if (strcmp(key, "retries") == 0) { current_tk->retries = strtol(val, &s, 0); if (*s || s == val || current_tk->retries<3 || current_tk->retries > 100) { error = "Expected plain integer value in the range [3, 100] for retries"; goto err; } continue; } if (strcmp(key, "renewal-freq") == 0) { current_tk->renewal_freq = read_time(val); if (current_tk->renewal_freq <= 0) { error = "Expected time >0 for renewal-freq"; goto err; } continue; } if (strcmp(key, "acquire-after") == 0) { current_tk->acquire_after = read_time(val); if (current_tk->acquire_after < 0) { error = "Expected time >=0 for acquire-after"; goto err; } continue; } if (strcmp(key, "before-acquire-handler") == 0) { if (parse_extprog(val, current_tk)) { goto err; } continue; } if (strcmp(key, "attr-prereq") == 0) { if (parse_attr_prereq(val, current_tk)) { goto err; } continue; } if (strcmp(key, "mode") == 0) { current_tk->mode = retrieve_ticket_mode(val); continue; } if (strcmp(key, "weights") == 0) { if (parse_weights(val, current_tk->weight) < 0) goto err; continue; } error = "Unknown keyword"; goto err; } fclose(fp); if (((*conf_pptr)->site_count % 2) == 0) { log_warn("Odd number of nodes is strongly recommended!"); } /* Default: make config name match config filename. */ if (!(*conf_pptr)->name[0]) { cp = strrchr(path, '/'); cp = cp ? cp+1 : (char *)path; cp2 = strrchr(cp, '.'); if (!cp2) cp2 = cp + strlen(cp); if (cp2-cp >= BOOTH_NAME_LEN) { log_error("booth config file name too long"); goto out; } strncpy((*conf_pptr)->name, cp, cp2-cp); *((*conf_pptr)->name+(cp2-cp)) = '\0'; } if (!postproc_ticket(current_tk)) { goto out; } + safe_copy((*conf_pptr)->path_to_self, path, + sizeof((*conf_pptr)->path_to_self), + "path to config file itself"); + poll_timeout = min(POLL_TIMEOUT, min_timeout/10); if (!poll_timeout) poll_timeout = POLL_TIMEOUT; return 0; err: fclose(fp); out: log_error("%s in config file line %d", error, lineno); free(*conf_pptr); *conf_pptr = NULL; return -1; } int check_config(struct booth_config *conf_ptr, int type) { struct passwd *pw; struct group *gr; char *cp, *input; if (conf_ptr == NULL) return -1; input = (type == ARBITRATOR) ? conf_ptr->arb_user : conf_ptr->site_user; if (!*input) goto u_inval; if (isdigit(input[0])) { conf_ptr->uid = strtol(input, &cp, 0); if (*cp != 0) { u_inval: log_error("User \"%s\" cannot be resolved into a UID.", input); return ENOENT; } } else { pw = getpwnam(input); if (!pw) goto u_inval; conf_ptr->uid = pw->pw_uid; } input = (type == ARBITRATOR) ? conf_ptr->arb_group : conf_ptr->site_group; if (!*input) goto g_inval; if (isdigit(input[0])) { conf_ptr->gid = strtol(input, &cp, 0); if (*cp != 0) { g_inval: log_error("Group \"%s\" cannot be resolved into a UID.", input); return ENOENT; } } else { gr = getgrnam(input); if (!gr) goto g_inval; conf_ptr->gid = gr->gr_gid; } return 0; } static int get_other_site(struct booth_config *conf_ptr, struct booth_site **node) { struct booth_site *n; int i; *node = NULL; if (conf_ptr == NULL) return 0; FOREACH_NODE(conf_ptr, i, n) { if (n != local && n->type == SITE) { if (!*node) { *node = n; } else { return 0; } } } return !*node ? 0 : 1; } int find_site_by_name(struct booth_config *conf_ptr, const char *site, struct booth_site **node, int any_type) { struct booth_site *n; int i; if (conf_ptr == NULL) return 0; if (!strcmp(site, OTHER_SITE)) return get_other_site(conf_ptr, node); FOREACH_NODE(conf_ptr, i, n) { if ((n->type == SITE || any_type) && strncmp(n->addr_string, site, sizeof(n->addr_string)) == 0) { *node = n; return 1; } } return 0; } int find_site_by_id(struct booth_config *conf_ptr, uint32_t site_id, struct booth_site **node) { struct booth_site *n; int i; if (site_id == NO_ONE) { *node = no_leader; return 1; } if (conf_ptr == NULL) return 0; FOREACH_NODE(conf_ptr, i, n) { if (n->site_id == site_id) { *node = n; return 1; } } return 0; } const char *type_to_string(int type) { switch (type) { case ARBITRATOR: return "arbitrator"; case SITE: return "site"; case CLIENT: return "client"; case GEOSTORE: return "attr"; } return "??invalid-type??"; } diff --git a/src/config.h b/src/config.h index 37bd95d..243bbe0 100644 --- a/src/config.h +++ b/src/config.h @@ -1,393 +1,395 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef _CONFIG_H #define _CONFIG_H #include #include struct booth_config; #include "booth.h" #include "timer.h" #include "raft.h" #include "transport.h" /** @{ */ /** Definitions for in-RAM data. */ #define MAX_NODES 16 #define MAX_ARGS 16 #define TICKET_ALLOC 16 #define OTHER_SITE "other" typedef enum { EXTPROG_IDLE, EXTPROG_RUNNING, EXTPROG_EXITED, EXTPROG_IGNORE, } extprog_state_e; #define tk_test tk->clu_test typedef enum { ATTR_OP_EQ = 1, ATTR_OP_NE, } attr_op_e; typedef enum { GRANT_AUTO = 1, GRANT_MANUAL, } grant_type_e; typedef enum { TICKET_MODE_AUTO = 1, TICKET_MODE_MANUAL, } ticket_mode_e; struct toktab { const char *str; int val; }; struct attr_prereq { grant_type_e grant_type; /* grant type */ attr_op_e op; /* attribute operation */ char *attr_name; char *attr_val; }; struct ticket_config { /** \name Configuration items. * @{ */ /** Name of ticket. */ boothc_ticket name; /** How long a term lasts if not refreshed (in ms) */ int term_duration; /** Network related timeouts (in ms) */ int timeout; /** Retries before giving up. */ int retries; /** If >0, time to wait for a site to get fenced. * The ticket may be acquired after that timespan by * another site. */ int acquire_after; /* How often to renew the ticket (in ms) */ int renewal_freq; /* Program to ask whether it makes sense to * acquire the ticket */ struct clu_test { char *path; int is_dir; char *argv[MAX_ARGS]; pid_t pid; int status; /* child exit status */ extprog_state_e progstate; /* program running/idle/waited on */ } clu_test; /** Node weights. */ int weight[MAX_NODES]; /* Mode operation of the ticket. * Set to MANUAL to make sure that the ticket will be manipulated * only by manual commands of the administrator. In such a case * automatic elections will be disabled. * Manual tickets do not have to be renewed every some time. * The leader will continue to send heartbeat messages to other sites. */ ticket_mode_e mode; /** @} */ /** \name Runtime values. * @{ */ /** Current state. */ server_state_e state; /** Next state. Used at startup. */ server_state_e next_state; /** When something has to be done */ timetype next_cron; /** Current leader. This is effectively the log[] in Raft. */ struct booth_site *leader; /** Leader that got lost. */ struct booth_site *lost_leader; /** Is the ticket granted? */ int is_granted; /** Which site considered itself a leader. * For manual tickets it is possible, that * more than one site will act as a leader. * This array is used for tracking that situation * and notifying the user about the issue. * * Possible values for every site: * 0: the site does not claim to be the leader * 1: the site considers itself a leader and * is sending or used to send heartbeat messages * * The site will be marked as '1' until this site * receives revoke confirmation. * * If more than one site has '1', the geo cluster is * considered to have multiple leadership and proper * warning are generated. */ int sites_where_granted[MAX_NODES]; /** Timestamp of leadership expiration */ timetype term_expires; /** End of election period */ timetype election_end; struct booth_site *voted_for; /** Who the various sites vote for. * NO_OWNER = no vote yet. */ struct booth_site *votes_for[MAX_NODES]; /* bitmap */ uint64_t votes_received; /** Last voting round that was seen. */ uint32_t current_term; /** Do ticket updates whenever we get enough heartbeats. * But do that only once. * This is reset to 0 whenever we broadcast heartbeat and set * to 1 once enough acks are received. * Increased to 2 when the ticket is commited to the CIB (see * delay_commit). */ uint32_t ticket_updated; /** Outcome of whatever ticket request was processed. * Can also be an intermediate stage. */ uint32_t outcome; /** @} */ /** */ uint32_t last_applied; uint32_t next_index[MAX_NODES]; uint32_t match_index[MAX_NODES]; /* Why did we start the elections? */ cmd_reason_t election_reason; /* if it is potentially dangerous to grant the ticket * immediately, then this is set to some point in time, * usually (now + term_duration + acquire_after) */ timetype delay_commit; /* the last request RPC we sent */ uint32_t last_request; /* if we expect some acks, then set this to the id of * the RPC which others will send us; it is cleared once all * replies were received */ uint32_t acks_expected; /* bitmask of servers which sent acks */ uint64_t acks_received; /* timestamp of the request */ timetype req_sent_at; /* we need to wait for MY_INDEX from other servers, * hold the ticket processing for a while until they reply */ int start_postpone; /** Last renewal time */ timetype last_renewal; /* Do we need to update the copy in the CIB? * Normally, the ticket is written only when it changes via * the UPDATE RPC (for followers) and on expiration update * (for leaders) */ int update_cib; /* Is this ticket in election? */ int in_election; /* don't log warnings unnecessarily */ int expect_more_rejects; /** \name Needed while proposals are being done. * @{ */ /* Need to keep the previous valid ticket in case we moved to * start new elections and another server asks for the ticket * status. It would be wrong to send our candidate ticket. */ struct ticket_config *last_valid_tk; /** Attributes, user defined */ GHashTable *attr; /** Attribute prerequisites */ GList *attr_prereqs; /** Whom to vote for the next time. * Needed to push a ticket to someone else. */ #if 0 /** Bitmap of sites that acknowledge that state. */ uint64_t proposal_acknowledges; /** When an incompletely acknowledged proposal gets done. * If all peers agree, that happens sooner. * See switch_state_to(). */ struct timeval proposal_switch; /** Timestamp of proposal expiration. */ time_t proposal_expires; #endif /** Number of send retries left. * Used on the new owner. * Starts at 0, counts up. */ int retry_number; /** @} */ }; struct booth_config { char name[BOOTH_NAME_LEN]; /** File containing the authentication file. */ char authfile[BOOTH_PATH_LEN]; struct stat authstat; char authkey[BOOTH_MAX_KEY_LEN]; int authkey_len; /** Maximum time skew between peers allowed */ int maxtimeskew; transport_layer_t proto; uint16_t port; /** Stores the OR of sites bitmasks. */ uint64_t sites_bits; /** Stores the OR of all members' bitmasks. */ uint64_t all_bits; char site_user[BOOTH_NAME_LEN]; char site_group[BOOTH_NAME_LEN]; char arb_user[BOOTH_NAME_LEN]; char arb_group[BOOTH_NAME_LEN]; uid_t uid; gid_t gid; int site_count; struct booth_site site[MAX_NODES]; int ticket_count; int ticket_allocated; struct ticket_config *ticket; + char path_to_self[BOOTH_PATH_LEN]; + const booth_transport_table_t *transport; const struct ticket_handler *ticket_handler; }; #define is_auth_req(b_) ((b_)->authkey[0] != '\0') /** * @internal * Parse booth configuration file and store as structured data * * @param[inout] conf_pptr config object to free-alloc cycle & fill accordingly * @param[in] transport transport handlers table * @param[in] path where the configuration file is expected * @param[in] type role currently being acted as * * @return 0 or negative value (-1 or -errno) on error */ int read_config(struct booth_config **conf_pptr, const booth_transport_table_t *transport, const struct ticket_handler *ticket_handler, const char *path, int type); /** * @internal * Check booth configuration * * Currently it means checking that login user/group indeed exists, * while converting it to respective numeric values for further use. * * @param[inout] conf_ptr config object to check * @param[in] type role currently being acted as * * @return 0 or negative value (-1 or -errno) on error */ int check_config(struct booth_config *conf_ptr, int type); /** * @internal * Find site in booth configuration by resolved host name * * @param[inout] conf_ptr config object to refer to * @param[in] site name to match against previously resolved host names * @param[out] node relevant tracked data when found * @param[in] any_type whether or not to consider also non-site members * * @return 0 if nothing found, or 1 when found (node assigned accordingly) */ int find_site_by_name(struct booth_config *conf_ptr, const char *site, struct booth_site **node, int any_type); /** * @internal * Find site in booth configuration by a hash (id) * * @param[inout] conf_ptr config object to refer to * @param[in] site_id hash (id) to match against previously resolved ones * @param[out] node relevant tracked data when found * * @return 0 if nothing found, or 1 when found (node assigned accordingly) */ int find_site_by_id(struct booth_config *conf_ptr, uint32_t site_id, struct booth_site **node); const char *type_to_string(int type); #endif /* _CONFIG_H */ diff --git a/src/handler.c b/src/handler.c index 2b5d955..634275e 100644 --- a/src/handler.c +++ b/src/handler.c @@ -1,280 +1,280 @@ /* * Copyright (C) 2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include "ticket.h" #include "config.h" #include "inline-fn.h" #include "log.h" #include "pacemaker.h" #include "booth.h" #include "handler.h" static int set_booth_env(struct booth_config *conf_ptr, struct ticket_config *tk) { int rv; char expires[16]; assert(conf_ptr != NULL); sprintf(expires, "%" PRId64, (int64_t)wall_ts(&tk->term_expires)); rv = setenv("BOOTH_TICKET", tk->name, 1) || setenv("BOOTH_LOCAL", local->addr_string, 1) || setenv("BOOTH_CONF_NAME", conf_ptr->name, 1) || - setenv("BOOTH_CONF_PATH", cl.configfile, 1) || + setenv("BOOTH_CONF_PATH", conf_ptr->path_to_self, 1) || setenv("BOOTH_TICKET_EXPIRES", expires, 1); if (rv) { log_error("Cannot set environment: %s", strerror(errno)); } return rv; } static void closefiles(void) { int fd; /* close all descriptors except stdin/out/err */ for (fd = getdtablesize() - 1; fd > STDERR_FILENO; fd--) { close(fd); } } static void run_ext_prog(struct booth_config *conf_ptr, struct ticket_config *tk, char *prog) { if (set_booth_env(conf_ptr, tk)) { _exit(1); } closefiles(); /* don't leak open files */ tk_log_debug("running handler %s", prog); execv(prog, tk_test.argv); tk_log_error("%s: execv failed (%s)", prog, strerror(errno)); _exit(1); } static int prog_filter(const struct dirent *dp) { return (*dp->d_name != '.'); } static pid_t curr_pid; static int ignore_status; static int test_exit_status(struct ticket_config *tk, char *prog, int status, int log_msg) { int rv = -1; if (WIFEXITED(status)) { rv = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { rv = 128 + WTERMSIG(status); } if (rv) { if (log_msg) { tk_log_warn("handler \"%s\" failed: %s", prog, interpret_rv(status)); tk_log_warn("we are not allowed to acquire ticket"); } } else { tk_log_debug("handler \"%s\" exited with success", prog); } return rv; } static void reset_test_state(struct ticket_config *tk) { tk_test.pid = 0; set_progstate(tk, EXTPROG_IDLE); } int tk_test_exit_status(struct ticket_config *tk) { int rv; rv = test_exit_status(tk, tk_test.path, tk_test.status, !tk_test.is_dir); reset_test_state(tk); return rv; } void wait_child(struct booth_config *conf_ptr) { int i, status; struct ticket_config *tk; /* use waitpid(2) and not wait(2) in order not to interfere * with popen(2)/pclose(2) and system(2) used in pacemaker.c */ FOREACH_TICKET(conf_ptr, i, tk) { if (tk_test.path && tk_test.pid > 0 && (tk_test.progstate == EXTPROG_RUNNING || tk_test.progstate == EXTPROG_IGNORE) && waitpid(tk_test.pid, &status, WNOHANG) == tk_test.pid) { if (tk_test.progstate == EXTPROG_IGNORE) { /* not interested in the outcome */ reset_test_state(tk); } else { tk_test.status = status; set_progstate(tk, EXTPROG_EXITED); } } } } /* the parent may want to have us stop processing scripts, say * when the ticket gets revoked */ static void ignore_rest(int sig) { signal(SIGTERM, SIG_IGN); log_info("external programs handler caught TERM, ignoring status of external test programs"); ignore_status = 1; if (curr_pid > 0) { (void)kill(curr_pid, SIGTERM); } } void ext_prog_timeout(struct ticket_config *tk) { tk_log_warn("handler timed out"); } int is_ext_prog_running(struct ticket_config *tk) { if (!tk_test.path) return 0; return (tk_test.pid > 0 && tk_test.progstate == EXTPROG_RUNNING); } void ignore_ext_test(struct ticket_config *tk) { if (is_ext_prog_running(tk)) { (void)kill(tk_test.pid, SIGTERM); set_progstate(tk, EXTPROG_IGNORE); } else if (tk_test.progstate == EXTPROG_EXITED) { /* external prog exited, but the status not yet examined; * we're not interested in checking the status anymore */ reset_test_state(tk); } } static void process_ext_dir(struct booth_config *conf_ptr, struct ticket_config *tk) { char prog[FILENAME_MAX+1]; int rv, n_progs, i, status; struct dirent **proglist, *dp; signal(SIGTERM, (__sighandler_t)ignore_rest); signal(SIGCHLD, SIG_DFL); signal(SIGUSR1, SIG_DFL); signal(SIGINT, SIG_DFL); tk_log_debug("running programs in directory %s", tk_test.path); n_progs = scandir(tk_test.path, &proglist, prog_filter, alphasort); if (n_progs == -1) { tk_log_error("%s: scandir failed (%s)", tk_test.path, strerror(errno)); _exit(1); } for (i = 0; i < n_progs; i++) { if (ignore_status) break; dp = proglist[i]; if (strlen(dp->d_name) + strlen(tk_test.path) + 1 > FILENAME_MAX) { tk_log_error("%s: name exceeds max length (%s)", tk_test.path, dp->d_name); _exit(1); } strcpy(prog, tk_test.path); strcat(prog, "/"); strcat(prog, dp->d_name); switch(curr_pid=fork()) { case -1: log_error("fork: %s", strerror(errno)); _exit(1); case 0: /* child */ run_ext_prog(conf_ptr, tk, prog); break; /* run_ext_prog effectively noreturn */ default: /* parent */ while (waitpid(curr_pid, &status, 0) != curr_pid) ; curr_pid = 0; if (!ignore_status) { rv = test_exit_status(tk, prog, status, 1); if (rv) _exit(rv); } } } _exit(0); } /* run some external program * return codes: * RUNCMD_ERR: executing program failed (or some other failure) * RUNCMD_MORE: program forked, results later */ int run_handler(struct booth_config *conf_ptr, struct ticket_config *tk) { int rv = 0; pid_t pid; struct stat stbuf; if (!tk_test.path) return 0; if (stat(tk_test.path, &stbuf)) { tk_log_error("%s: stat failed (%s)", tk_test.path, strerror(errno)); return RUNCMD_ERR; } tk_test.is_dir = (stbuf.st_mode & S_IFDIR); switch(pid=fork()) { case -1: log_error("fork: %s", strerror(errno)); return RUNCMD_ERR; case 0: /* child */ if (tk_test.is_dir) { process_ext_dir(conf_ptr, tk); } else { run_ext_prog(conf_ptr, tk, tk_test.path); } default: /* parent */ tk_test.pid = pid; set_progstate(tk, EXTPROG_RUNNING); rv = RUNCMD_MORE; /* program runs */ } return rv; } diff --git a/src/main.c b/src/main.c index 417eaea..ea75937 100644 --- a/src/main.c +++ b/src/main.c @@ -1,1661 +1,1666 @@ /* * Copyright (C) 2011 Jiaju Zhang * Copyright (C) 2013-2014 Philipp Marek * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "b_config.h" #if HAVE_LIBGCRYPT #include #endif #ifndef NAMETAG_LIBSYSTEMD #include #else #include "alt/nametag_libsystemd.h" #endif #ifdef COREDUMP_NURSING #include #include #endif #include "log.h" #include "booth.h" #include "config.h" #include "transport.h" #include "inline-fn.h" #include "pacemaker.h" #include "ticket.h" #include "request.h" #include "attr.h" #include "handler.h" #define RELEASE_VERSION "1.0" #define RELEASE_STR RELEASE_VERSION " (build " BOOTH_BUILD_VERSION ")" #define CLIENT_NALLOC 32 extern const booth_transport_table_t booth__transport; extern struct ticket_handler booth__pcmk_ticket_handler; static int daemonize = 1; int enable_stderr = 0; timetype start_time; static struct booth_config *booth_conf; +static struct command_line cmd_line; /** Structure for "clients". * Filehandles with incoming data get registered here (and in pollfds), * along with their callbacks. * Because these can be reallocated with every new fd, addressing * happens _only_ by their numeric index. */ struct client *clients = NULL; struct pollfd *pollfds = NULL; static int client_maxi; static int client_size = 0; static const struct booth_site _no_leader = { .addr_string = "none", .site_id = NO_ONE, .index = -1, }; struct booth_site *const no_leader = (struct booth_site*) &_no_leader; typedef enum { BOOTHD_STARTED=0, BOOTHD_STARTING } BOOTH_DAEMON_STATE; int poll_timeout; -struct command_line cl; static void client_alloc(void) { int i; if (!(clients = realloc( clients, (client_size + CLIENT_NALLOC) * sizeof(*clients)) ) || !(pollfds = realloc( pollfds, (client_size + CLIENT_NALLOC) * sizeof(*pollfds)) )) { log_error("can't alloc for client array"); exit(1); } for (i = client_size; i < client_size + CLIENT_NALLOC; i++) { clients[i].workfn = NULL; clients[i].deadfn = NULL; clients[i].fd = -1; pollfds[i].fd = -1; pollfds[i].revents = 0; } client_size += CLIENT_NALLOC; } static void client_dead(int ci) { struct client *c = clients + ci; if (c->fd != -1) { log_debug("removing client %d", c->fd); close(c->fd); } c->fd = -1; c->workfn = NULL; if (c->msg) { free(c->msg); c->msg = NULL; c->offset = 0; } pollfds[ci].fd = -1; } int client_add(int fd, const struct booth_transport *tpt, void (*workfn)(struct booth_config *conf_ptr, int ci), void (*deadfn)(int ci)) { int i; struct client *c; if (client_size - 1 <= client_maxi ) { client_alloc(); } for (i = 0; i < client_size; i++) { c = clients + i; if (c->fd != -1) continue; c->workfn = workfn; if (deadfn) c->deadfn = deadfn; else c->deadfn = client_dead; c->transport = tpt; c->fd = fd; c->msg = NULL; c->offset = 0; pollfds[i].fd = fd; pollfds[i].events = POLLIN; if (i > client_maxi) client_maxi = i; return i; } assert(!"no client"); } int find_client_by_fd(int fd) { int i; if (fd < 0) return -1; for (i = 0; i <= client_maxi; i++) { if (clients[i].fd == fd) return i; } return -1; } static int format_peers(struct booth_config *conf_ptr, char **pdata, unsigned int *len) { struct booth_site *s; char *data, *cp; char time_str[64]; int i, alloc; assert(conf_ptr != NULL); *pdata = NULL; *len = 0; alloc = conf_ptr->site_count * (BOOTH_NAME_LEN + 256); data = malloc(alloc); if (!data) return -ENOMEM; cp = data; FOREACH_NODE(conf_ptr, i, s) { if (s == local) continue; strftime(time_str, sizeof(time_str), "%F %T", localtime(&s->last_recv)); cp += snprintf(cp, alloc - (cp - data), "%-12s %s, last recv: %s\n", type_to_string(s->type), s->addr_string, time_str); cp += snprintf(cp, alloc - (cp - data), "\tSent pkts:%u error:%u resends:%u\n", s->sent_cnt, s->sent_err_cnt, s->resend_cnt); cp += snprintf(cp, alloc - (cp - data), "\tRecv pkts:%u error:%u authfail:%u invalid:%u\n\n", s->recv_cnt, s->recv_err_cnt, s->sec_cnt, s->invalid_cnt); if (alloc - (cp - data) <= 0) { free(data); return -ENOMEM; } } *pdata = data; *len = cp - data; return 0; } void list_peers(struct booth_config *conf_ptr, int fd) { char *data; unsigned int olen; struct boothc_hdr_msg hdr; if (format_peers(conf_ptr, &data, &olen) < 0) goto out; init_header(conf_ptr, &hdr.header, CL_LIST, 0, 0, RLT_SUCCESS, 0, sizeof(hdr) + olen); (void) send_header_plus(conf_ptr, fd, &hdr, data, olen); out: if (data) free(data); } /* trim trailing spaces if the key is ascii */ static void trim_key(struct booth_config *conf_ptr) { char *p; int i; assert(conf_ptr != NULL); for (i = 0, p = conf_ptr->authkey; i < conf_ptr->authkey_len; i++, p++) if (!isascii(*p)) return; p = conf_ptr->authkey; while (conf_ptr->authkey_len > 0 && isspace(*p)) { p++; conf_ptr->authkey_len--; } memmove(conf_ptr->authkey, p, conf_ptr->authkey_len); p = conf_ptr->authkey + conf_ptr->authkey_len - 1; while (conf_ptr->authkey_len > 0 && isspace(*p)) { conf_ptr->authkey_len--; p--; } } static int read_authkey(struct booth_config *conf_ptr) { int fd; assert(conf_ptr != NULL); conf_ptr->authkey[0] = '\0'; fd = open(conf_ptr->authfile, O_RDONLY); if (fd < 0) { log_error("cannot open %s: %s", conf_ptr->authfile, strerror(errno)); return -1; } if (fstat(fd, &conf_ptr->authstat) < 0) { log_error("cannot stat authentication file %s (%d): %s", conf_ptr->authfile, fd, strerror(errno)); close(fd); return -1; } if (conf_ptr->authstat.st_mode & (S_IRGRP | S_IROTH)) { log_error("%s: file shall not be readable for anyone but the owner", conf_ptr->authfile); close(fd); return -1; } conf_ptr->authkey_len = read(fd, conf_ptr->authkey, BOOTH_MAX_KEY_LEN); close(fd); trim_key(conf_ptr); log_debug("read key of size %d in authfile %s", conf_ptr->authkey_len, conf_ptr->authfile); /* make sure that the key is of minimum length */ return (conf_ptr->authkey_len >= BOOTH_MIN_KEY_LEN) ? 0 : -1; } /* XXX UNUSED */ int update_authkey(struct booth_config *conf_ptr) { struct stat statbuf; assert(conf_ptr != NULL); if (stat(conf_ptr->authfile, &statbuf) < 0) { log_error("cannot stat authentication file %s: %s", conf_ptr->authfile, strerror(errno)); return -1; } if (statbuf.st_mtime > conf_ptr->authstat.st_mtime) { return read_authkey(conf_ptr); } return 0; } -static int setup_config(struct booth_config **conf_pptr, int type) +static int setup_config(struct command_line *cl, struct booth_config **conf_pptr) { int rv; assert(conf_pptr != NULL); rv = read_config(conf_pptr, &booth__transport, - &booth__pcmk_ticket_handler, cl.configfile, type); + &booth__pcmk_ticket_handler, cl->configfile, + cl->type); if (rv < 0) goto out; if (is_auth_req(*conf_pptr)) { rv = read_authkey(*conf_pptr); if (rv < 0) goto out; #if HAVE_LIBGCRYPT if (!gcry_check_version(NULL)) { log_error("gcry_check_version"); rv = -ENOENT; goto out; } gcry_control(GCRYCTL_DISABLE_SECMEM, 0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); #endif } /* Set "local" pointer, ignoring errors. */ - if (cl.type == DAEMON && cl.site[0]) { - if (!find_site_by_name(*conf_pptr, cl.site, &local, 1)) { + if (cl->type == DAEMON && cl->site[0]) { + if (!find_site_by_name(*conf_pptr, cl->site, &local, 1)) { log_error("Cannot find \"%s\" in the configuration.", - cl.site); + cl->site); return -EINVAL; } local->local = 1; } else find_myself(*conf_pptr, NULL, - type == CLIENT || type == GEOSTORE); + cl->type == CLIENT || cl->type == GEOSTORE); - rv = check_config(*conf_pptr, type); + rv = check_config(*conf_pptr, cl->type); if (rv < 0) goto out; /* Per default the PID file name is derived from the * configuration name. */ - if (!cl.lockfile[0]) { - snprintf(cl.lockfile, sizeof(cl.lockfile) - 1, + if (!cl->lockfile[0]) { + snprintf(cl->lockfile, sizeof(cl->lockfile)-1, "%s/%s.pid", BOOTH_RUN_DIR, (*conf_pptr)->name); } out: return rv; } static int setup_transport(struct booth_config *conf_ptr) { int rv; assert(conf_ptr != NULL && conf_ptr->transport != NULL); rv = transport(conf_ptr)->init(conf_ptr, message_recv); if (rv < 0) { log_error("failed to init booth_transport %s", transport(conf_ptr)->name); goto out; } rv = (*conf_ptr->transport)[TCP].init(conf_ptr, NULL); if (rv < 0) { log_error("failed to init booth_transport[TCP]"); goto out; } out: return rv; } -static int write_daemon_state(struct booth_config *conf_ptr, int fd, int state) +static int write_daemon_state(struct command_line *cl, + struct booth_config *conf_ptr, int fd, int state) { char buffer[1024]; int rv, size; size = sizeof(buffer) - 1; rv = snprintf(buffer, size, "booth_pid=%d " "booth_state=%s " "booth_type=%s " "booth_cfg_name='%s' " "booth_id=%d " "booth_addr_string='%s' " "booth_port=%d\n", getpid(), ( state == BOOTHD_STARTED ? "started" : state == BOOTHD_STARTING ? "starting" : "invalid"), type_to_string(local->type), conf_ptr->name, get_local_id(), site_string(local), site_port(local)); if (rv < 0 || rv == size) { log_error("Buffer filled up in write_daemon_state()."); return -1; } size = rv; rv = ftruncate(fd, 0); if (rv < 0) { log_error("lockfile %s truncate error %d: %s", - cl.lockfile, errno, strerror(errno)); + cl->lockfile, errno, strerror(errno)); return rv; } rv = lseek(fd, 0, SEEK_SET); if (rv < 0) { log_error("lseek set fd(%d) offset to 0 error, return(%d), message(%s)", fd, rv, strerror(errno)); rv = -1; return rv; } rv = write(fd, buffer, size); if (rv != size) { log_error("write to fd(%d, %d) returned %d, errno %d, message(%s)", fd, size, rv, errno, strerror(errno)); return -1; } return 0; } -static int loop(struct booth_config *conf_ptr, int fd) +static int loop(struct command_line *cl, struct booth_config *conf_ptr, int fd) { void (*workfn) (struct booth_config *conf_ptr, int ci); void (*deadfn) (int ci); int rv, i; rv = setup_transport(conf_ptr); if (rv < 0) goto fail; rv = setup_ticket(conf_ptr); if (rv < 0) goto fail; - rv = write_daemon_state(conf_ptr, fd, BOOTHD_STARTED); + rv = write_daemon_state(cl, conf_ptr, fd, BOOTHD_STARTED); if (rv != 0) { log_error("write daemon state %d to lockfile error %s: %s", - BOOTHD_STARTED, cl.lockfile, strerror(errno)); + BOOTHD_STARTED, cl->lockfile, strerror(errno)); goto fail; } log_info("BOOTH %s daemon started, node id is 0x%08X (%d).", type_to_string(local->type), local->site_id, local->site_id); while (1) { rv = poll(pollfds, client_maxi + 1, poll_timeout); if (rv == -1 && errno == EINTR) continue; if (rv < 0) { log_error("poll failed: %s (%d)", strerror(errno), errno); goto fail; } for (i = 0; i <= client_maxi; i++) { if (clients[i].fd < 0) continue; if (pollfds[i].revents & POLLIN) { workfn = clients[i].workfn; if (workfn) workfn(conf_ptr, i); } if (pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { deadfn = clients[i].deadfn; if (deadfn) deadfn(i); } } process_tickets(conf_ptr); } return 0; fail: return -1; } -static int test_reply(cmd_result_t reply_code, cmd_request_t cmd) +static int test_reply(struct command_line *cl, cmd_result_t reply_code) { int rv = 0; const char *op_str = ""; - if (cmd == CMD_GRANT) + if (cl->type == CMD_GRANT) op_str = "grant"; - else if (cmd == CMD_REVOKE) + else if (cl->type == CMD_REVOKE) op_str = "revoke"; - else if (cmd == CMD_LIST) + else if (cl->type == CMD_LIST) op_str = "list"; - else if (cmd == CMD_PEERS) + else if (cl->type == CMD_PEERS) op_str = "peers"; else { log_error("internal error reading reply result!"); return -1; } switch (reply_code) { case RLT_OVERGRANT: log_info("You're granting a granted ticket. " "If you wanted to migrate a ticket, " "use revoke first, then use grant."); rv = -1; break; case RLT_TICKET_IDLE: log_info("ticket is not owned"); rv = 0; break; case RLT_ASYNC: log_info("%s command sent, result will be returned " "asynchronously. Please use \"booth list\" to " "see the outcome.", op_str); rv = 0; break; case RLT_CIB_PENDING: log_info("%s succeeded (CIB commit pending)", op_str); /* wait for the CIB commit? */ - rv = (cl.options & OPT_WAIT_COMMIT) ? 3 : 0; + rv = (cl->options & OPT_WAIT_COMMIT) ? 3 : 0; break; case RLT_MORE: rv = 2; break; case RLT_SYNC_SUCC: case RLT_SUCCESS: - if (cmd != CMD_LIST && cmd != CMD_PEERS) + if (cl->type != CMD_LIST && cl->type != CMD_PEERS) log_info("%s succeeded!", op_str); rv = 0; break; case RLT_SYNC_FAIL: log_info("%s failed!", op_str); rv = -1; break; case RLT_INVALID_ARG: - log_error("ticket \"%s\" does not exist", - cl.msg.ticket.id); + log_error("ticket \"%s\" does not exist", cl->msg.ticket.id); rv = -1; break; case RLT_AUTH: log_error("authentication error"); rv = -1; break; case RLT_EXT_FAILED: log_error("before-acquire-handler for ticket \"%s\" failed, grant denied", - cl.msg.ticket.id); + cl->msg.ticket.id); rv = -1; break; case RLT_ATTR_PREREQ: log_error("attr-prereq for ticket \"%s\" failed, grant denied", - cl.msg.ticket.id); + cl->msg.ticket.id); rv = -1; break; case RLT_REDIRECT: /* talk to another site */ rv = 1; break; default: log_error("got an error code: %x", rv); rv = -1; } return rv; } -static int query_get_string_answer(struct booth_config *conf_ptr, - cmd_request_t cmd) +static int query_get_string_answer(struct command_line *cl, + struct booth_config *conf_ptr) { struct booth_site *site; struct boothc_hdr_msg reply; struct boothc_header *header; char *data; int data_len; int rv; struct booth_transport const *tpt; - int (*test_reply_f) (cmd_result_t reply_code, cmd_request_t cmd); + int (*test_reply_f) (struct command_line *, cmd_result_t reply_code); size_t msg_size; void *request; assert(conf_ptr != NULL && conf_ptr->transport != NULL); - if (cl.type == GEOSTORE) { + if (cl->type == GEOSTORE) { test_reply_f = test_attr_reply; - msg_size = sizeof(cl.attr_msg); - request = &cl.attr_msg; + msg_size = sizeof(cl->attr_msg); + request = &cl->attr_msg; } else { test_reply_f = test_reply; - msg_size = sizeof(cl.msg); - request = &cl.msg; + msg_size = sizeof(cl->msg); + request = &cl->msg; } header = (struct boothc_header *)request; data = NULL; - init_header(conf_ptr, header, cmd, 0, cl.options, 0, 0, msg_size); + init_header(conf_ptr, header, cl->op, 0, cl->options, 0, 0, msg_size); - if (!*cl.site) + if (*cl->site == '\0') site = local; - else if (!find_site_by_name(conf_ptr, cl.site, &site, 1)) { - log_error("cannot find site \"%s\"", cl.site); + else if (!find_site_by_name(conf_ptr, cl->site, &site, 1)) { + log_error("cannot find site \"%s\"", cl->site); rv = ENOENT; goto out; } tpt = *conf_ptr->transport + TCP; rv = tpt->open(site); if (rv < 0) goto out_close; rv = tpt->send(conf_ptr, site, request, msg_size); if (rv < 0) goto out_close; rv = tpt->recv_auth(conf_ptr, site, &reply, sizeof(reply)); if (rv < 0) goto out_close; data_len = ntohl(reply.header.length) - rv; /* no attribute, or no ticket found */ if (!data_len) { goto out_test_reply; } data = malloc(data_len+1); if (!data) { rv = -ENOMEM; goto out_close; } rv = tpt->recv(site, data, data_len); if (rv < 0) goto out_close; *(data+data_len) = '\0'; *(data + data_len) = '\0'; (void)fputs(data, stdout); fflush(stdout); rv = 0; out_test_reply: - rv = test_reply_f(ntohl(reply.header.result), cmd); + rv = test_reply_f(cl, ntohl(reply.header.result)); out_close: tpt->close(site); out: if (data) free(data); return rv; } -static int do_command(struct booth_config *conf_ptr, - cmd_request_t cmd) +static int do_command(struct command_line *cl, struct booth_config *conf_ptr) { struct booth_site *site; struct boothc_ticket_msg reply; struct booth_transport const *tpt; uint32_t leader_id; int rv; int reply_cnt = 0, msg_logged = 0; const char *op_str = ""; assert(conf_ptr != NULL && conf_ptr->transport != NULL); - if (cmd == CMD_GRANT) + if (cl->type == CMD_GRANT) op_str = "grant"; - else if (cmd == CMD_REVOKE) + else if (cl->type == CMD_REVOKE) op_str = "revoke"; rv = 0; site = NULL; /* Always use TCP for client - at least for now. */ tpt = *conf_ptr->transport + TCP; - if (!*cl.site) + if (*cl->site == '\0') site = local; else { - if (!find_site_by_name(conf_ptr, cl.site, &site, 1)) { - log_error("Site \"%s\" not configured.", cl.site); + if (!find_site_by_name(conf_ptr, cl->site, &site, 1)) { + log_error("Site \"%s\" not configured.", cl->site); goto out_close; } } if (site->type == ARBITRATOR) { if (site == local) { log_error("We're just an arbitrator, cannot grant/revoke tickets here."); } else { - log_error("%s is just an arbitrator, cannot grant/revoke tickets there.", cl.site); + log_error("%s is just an arbitrator, cannot grant/revoke tickets there.", + cl->site); } goto out_close; } assert(site->type == SITE); /* We don't check for existence of ticket, so that asking can be * done without local configuration, too. * Although, that means that the UDP port has to be specified, too. */ - if (!cl.msg.ticket.id[0]) { + if (!cl->msg.ticket.id[0]) { /* If the loaded configuration has only a single ticket defined, use that. */ if (conf_ptr->ticket_count == 1) { - strncpy(cl.msg.ticket.id, conf_ptr->ticket[0].name, - sizeof(cl.msg.ticket.id)); + strncpy(cl->msg.ticket.id, conf_ptr->ticket[0].name, + sizeof(cl->msg.ticket.id)); } else { log_error("No ticket given."); goto out_close; } } redirect: - init_header(conf_ptr, &cl.msg.header, cmd, 0, cl.options, 0, 0, sizeof(cl.msg)); + init_header(conf_ptr, &cl->msg.header, cl->type, 0, cl->options, 0, 0, + sizeof(cl->msg)); rv = tpt->open(site); if (rv < 0) goto out_close; - rv = tpt->send(conf_ptr, site, &cl.msg, sendmsglen(&cl.msg)); + rv = tpt->send(conf_ptr, site, &cl->msg, sendmsglen(&cl->msg)); if (rv < 0) goto out_close; read_more: rv = tpt->recv_auth(conf_ptr, site, &reply, sizeof(reply)); if (rv < 0) { /* print any errors depending on the code sent by the * server */ - (void)test_reply(ntohl(reply.header.result), cmd); + (void) test_reply(cl, ntohl(reply.header.result)); goto out_close; } - rv = test_reply(ntohl(reply.header.result), cmd); + rv = test_reply(cl, ntohl(reply.header.result)); if (rv == 1) { tpt->close(site); leader_id = ntohl(reply.ticket.leader); if (!find_site_by_id(conf_ptr, leader_id, &site)) { log_error("Message with unknown redirect site %x received", leader_id); rv = -1; goto out_close; } goto redirect; } else if (rv == 2 || rv == 3) { /* the server has more to say */ /* don't wait too long */ - if (reply_cnt > 1 && !(cl.options & OPT_WAIT)) { + if (reply_cnt > 1 && !(cl->options & OPT_WAIT)) { rv = 0; log_info("Giving up on waiting for the definite result. " "Please use \"booth list\" later to " "see the outcome."); goto out_close; } if (reply_cnt == 0) { log_info("%s request sent, " "waiting for the result ...", op_str); msg_logged++; } else if (rv == 3 && msg_logged < 2) { log_info("waiting for the CIB commit ..."); msg_logged++; } reply_cnt++; goto read_more; } out_close: if (site) tpt->close(site); return rv; } -static int _lockfile(int mode, int *fdp, pid_t *locked_by) +static int _lockfile(struct command_line *cl, int mode, int *fdp, + pid_t *locked_by) { struct flock lock; int fd, rv; /* After reboot the directory may not yet exist. * Try to create it, but ignore errors. */ - if (strncmp(cl.lockfile, BOOTH_RUN_DIR, + if (strncmp(cl->lockfile, BOOTH_RUN_DIR, strlen(BOOTH_RUN_DIR)) == 0) mkdir(BOOTH_RUN_DIR, 0775); if (locked_by) *locked_by = 0; *fdp = -1; - fd = open(cl.lockfile, mode, 0664); + fd = open(cl->lockfile, mode, 0664); if (fd < 0) return errno; *fdp = fd; lock.l_type = F_WRLCK; lock.l_start = 0; lock.l_whence = SEEK_SET; lock.l_len = 0; lock.l_pid = 0; if (fcntl(fd, F_SETLK, &lock) == 0) return 0; rv = errno; if (locked_by) if (fcntl(fd, F_GETLK, &lock) == 0) *locked_by = lock.l_pid; return rv; } static inline int is_root(void) { return geteuid() == 0; } -static int create_lockfile(struct booth_config *conf_ptr) +static int create_lockfile(struct command_line *cl, + struct booth_config *conf_ptr) { int rv, fd; fd = -1; - rv = _lockfile(O_CREAT | O_WRONLY, &fd, NULL); + rv = _lockfile(cl, O_CREAT | O_WRONLY, &fd, NULL); if (fd == -1) { log_error("lockfile %s open error %d: %s", - cl.lockfile, rv, strerror(rv)); + cl->lockfile, rv, strerror(rv)); return -1; } if (rv < 0) { log_error("lockfile %s setlk error %d: %s", - cl.lockfile, rv, strerror(rv)); + cl->lockfile, rv, strerror(rv)); goto fail; } - rv = write_daemon_state(conf_ptr, fd, BOOTHD_STARTING); + rv = write_daemon_state(cl, conf_ptr, fd, BOOTHD_STARTING); if (rv != 0) { log_error("write daemon state %d to lockfile error %s: %s", - BOOTHD_STARTING, cl.lockfile, strerror(errno)); + BOOTHD_STARTING, cl->lockfile, strerror(errno)); goto fail; } if (is_root()) { if (fchown(fd, conf_ptr->uid, conf_ptr->gid) < 0) log_error("fchown() on lockfile said %d: %s", errno, strerror(errno)); } return fd; fail: close(fd); return -1; } static void unlink_lockfile(int fd) { - unlink(cl.lockfile); + unlink(cmd_line.lockfile); close(fd); } static void print_usage(void) { printf( "Usage:\n" " booth list [options]\n" " booth {grant|revoke} [options] \n" " booth status [options]\n" "\n" " list: List all tickets\n" " grant: Grant ticket to site\n" " revoke: Revoke ticket\n" "\n" "Options:\n" " -c FILE Specify config file [default " BOOTH_DEFAULT_CONF "]\n" " Can be a path or just a name without \".conf\" suffix\n" " -s Connect/grant to a different site\n" " -F Try to grant the ticket immediately\n" " even if not all sites are reachable\n" " For manual tickets:\n" " grant a manual ticket even if it has been already granted\n" " -w Wait forever for the outcome of the request\n" " -C Wait until the ticket is committed to the CIB (grant only)\n" " -h Print this help\n" "\n" "Examples:\n" "\n" " # booth list (list tickets)\n" " # booth grant ticket-A (grant ticket here)\n" " # booth grant -s 10.121.8.183 ticket-A (grant ticket to site 10.121.8.183)\n" " # booth revoke ticket-A (revoke ticket)\n" "\n" "See the booth(8) man page for more details.\n" ); } #define OPTION_STRING "c:Dl:t:s:FhSwC" #define ATTR_OPTION_STRING "c:Dt:s:h" -void safe_copy(char *dest, char *value, size_t buflen, const char *description) { +void safe_copy(char *dest, const char *value, size_t buflen, + const char *description) +{ int content_len = buflen - 1; if (strlen(value) >= content_len) { fprintf(stderr, "'%s' exceeds maximum %s length of %d\n", value, description, content_len); exit(EXIT_FAILURE); } strncpy(dest, value, content_len); dest[content_len] = 0; } static int host_convert(char *hostname, char *ip_str, size_t ip_size) { struct addrinfo *result = NULL, hints = {0}; int re = -1; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_DGRAM; re = getaddrinfo(hostname, NULL, &hints, &result); if (re == 0) { struct in_addr addr = ((struct sockaddr_in *)result->ai_addr)->sin_addr; const char *re_ntop = inet_ntop(AF_INET, &addr, ip_str, ip_size); if (re_ntop == NULL) { re = -1; } } freeaddrinfo(result); return re; } #define cparg(dest, descr) do { \ if (optind >= argc) \ goto missingarg; \ safe_copy(dest, argv[optind], sizeof(dest), descr); \ optind++; \ } while(0) -static int read_arguments(int argc, char **argv) +static int read_arguments(struct command_line *cl, int argc, char **argv) { int optchar; char *arg1 = argv[1]; char *op = NULL; char *cp; const char *opt_string = OPTION_STRING; char site_arg[INET_ADDRSTRLEN] = {0}; int left; - cl.type = 0; + cl->type = 0; if ((cp = strstr(argv[0], ATTR_PROG)) && !strcmp(cp, ATTR_PROG)) { - cl.type = GEOSTORE; + cl->type = GEOSTORE; op = argv[1]; optind = 2; opt_string = ATTR_OPTION_STRING; } else if (argc > 1 && (strcmp(arg1, "arbitrator") == 0 || strcmp(arg1, "site") == 0 || strcmp(arg1, "start") == 0 || strcmp(arg1, "daemon") == 0)) { - cl.type = DAEMON; + cl->type = DAEMON; optind = 2; } else if (argc > 1 && (strcmp(arg1, "status") == 0)) { - cl.type = STATUS; + cl->type = STATUS; optind = 2; } else if (argc > 1 && (strcmp(arg1, "client") == 0)) { - cl.type = CLIENT; + cl->type = CLIENT; if (argc < 3) { print_usage(); exit(EXIT_FAILURE); } op = argv[2]; optind = 3; } - if (!cl.type) { - cl.type = CLIENT; + if (!cl->type) { + cl->type = CLIENT; op = argv[1]; optind = 2; } if (argc < 2 || !strcmp(arg1, "help") || !strcmp(arg1, "--help") || !strcmp(arg1, "-h")) { - if (cl.type == GEOSTORE) + if (cl->type == GEOSTORE) print_geostore_usage(); else print_usage(); exit(EXIT_SUCCESS); } if (!strcmp(arg1, "version") || !strcmp(arg1, "--version") || !strcmp(arg1, "-V")) { printf("%s %s\n", argv[0], RELEASE_STR); exit(EXIT_SUCCESS); } - if (cl.type == CLIENT) { + if (cl->type == CLIENT) { if (!strcmp(op, "list")) - cl.op = CMD_LIST; + cl->op = CMD_LIST; else if (!strcmp(op, "grant")) - cl.op = CMD_GRANT; + cl->op = CMD_GRANT; else if (!strcmp(op, "revoke")) - cl.op = CMD_REVOKE; + cl->op = CMD_REVOKE; else if (!strcmp(op, "peers")) - cl.op = CMD_PEERS; + cl->op = CMD_PEERS; else { fprintf(stderr, "client operation \"%s\" is unknown\n", op); exit(EXIT_FAILURE); } - } else if (cl.type == GEOSTORE) { + } else if (cl->type == GEOSTORE) { if (!strcmp(op, "list")) - cl.op = ATTR_LIST; + cl->op = ATTR_LIST; else if (!strcmp(op, "set")) - cl.op = ATTR_SET; + cl->op = ATTR_SET; else if (!strcmp(op, "get")) - cl.op = ATTR_GET; + cl->op = ATTR_GET; else if (!strcmp(op, "delete")) - cl.op = ATTR_DEL; + cl->op = ATTR_DEL; else { fprintf(stderr, "attribute operation \"%s\" is unknown\n", op); exit(EXIT_FAILURE); } } while (optind < argc) { optchar = getopt(argc, argv, opt_string); switch (optchar) { case 'c': if (strchr(optarg, '/')) { - safe_copy(cl.configfile, optarg, - sizeof(cl.configfile), "config file"); + safe_copy(cl->configfile, optarg, + sizeof(cl->configfile), "config file"); } else { /* If no "/" in there, use with default directory. */ - strcpy(cl.configfile, BOOTH_DEFAULT_CONF_DIR); - cp = cl.configfile + strlen(BOOTH_DEFAULT_CONF_DIR); - assert(cp > cl.configfile); + strcpy(cl->configfile, BOOTH_DEFAULT_CONF_DIR); + cp = cl->configfile + strlen(BOOTH_DEFAULT_CONF_DIR); + assert(cp > cl->configfile); assert(*(cp-1) == '/'); /* Write at the \0, ie. after the "/" */ safe_copy(cp, optarg, - (sizeof(cl.configfile) - - (cp - cl.configfile) - + (sizeof(cl->configfile) - + (cp - cl->configfile) - strlen(BOOTH_DEFAULT_CONF_EXT)), "config name"); /* If no extension, append ".conf". * Space is available, see -strlen() above. */ if (!strchr(cp, '.')) strcat(cp, BOOTH_DEFAULT_CONF_EXT); } break; case 'D': debug_level++; break; case 'S': daemonize = 0; enable_stderr = 1; break; case 'l': - safe_copy(cl.lockfile, optarg, sizeof(cl.lockfile), "lock file"); + safe_copy(cl->lockfile, optarg, sizeof(cl->lockfile), + "lock file"); break; case 't': - if (cl.op == CMD_GRANT || cl.op == CMD_REVOKE) { - safe_copy(cl.msg.ticket.id, optarg, - sizeof(cl.msg.ticket.id), "ticket name"); - } else if (cl.type == GEOSTORE) { - safe_copy(cl.attr_msg.attr.tkt_id, optarg, - sizeof(cl.attr_msg.attr.tkt_id), "ticket name"); + if (cl->op == CMD_GRANT || cl->op == CMD_REVOKE) { + safe_copy(cl->msg.ticket.id, optarg, + sizeof(cl->msg.ticket.id), "ticket name"); + } else if (cl->type == GEOSTORE) { + safe_copy(cl->attr_msg.attr.tkt_id, optarg, + sizeof(cl->attr_msg.attr.tkt_id), "ticket name"); } else { print_usage(); exit(EXIT_FAILURE); } break; case 's': /* For testing and debugging: allow "-s site" also for * daemon start, so that the address that should be used * can be set manually. * This makes it easier to start multiple processes * on one machine. */ - if (cl.type == CLIENT || cl.type == GEOSTORE || - (cl.type == DAEMON && debug_level)) { + if (cl->type == CLIENT || cl->type == GEOSTORE || + (cl->type == DAEMON && debug_level)) { if (strcmp(optarg, OTHER_SITE) && host_convert(optarg, site_arg, INET_ADDRSTRLEN) == 0) { - safe_copy(cl.site, site_arg, sizeof(cl.site), "site name"); + safe_copy(cl->site, site_arg, sizeof(cl->site), "site name"); } else { - safe_copy(cl.site, optarg, sizeof(cl.site), "site name"); + safe_copy(cl->site, optarg, sizeof(cl->site), "site name"); } } else { log_error("\"-s\" not allowed in daemon mode."); exit(EXIT_FAILURE); } break; case 'F': - if (cl.type != CLIENT || cl.op != CMD_GRANT) { + if (cl->type != CLIENT || cl->op != CMD_GRANT) { log_error("use \"-F\" only for client grant"); exit(EXIT_FAILURE); } - cl.options |= OPT_IMMEDIATE; + cl->options |= OPT_IMMEDIATE; break; case 'w': - if (cl.type != CLIENT || - (cl.op != CMD_GRANT && cl.op != CMD_REVOKE)) { + if (cl->type != CLIENT || + (cl->op != CMD_GRANT && cl->op != CMD_REVOKE)) { log_error("use \"-w\" only for grant and revoke"); exit(EXIT_FAILURE); } - cl.options |= OPT_WAIT; + cl->options |= OPT_WAIT; break; case 'C': - if (cl.type != CLIENT || cl.op != CMD_GRANT) { + if (cl->type != CLIENT || cl->op != CMD_GRANT) { log_error("use \"-C\" only for grant"); exit(EXIT_FAILURE); } - cl.options |= OPT_WAIT | OPT_WAIT_COMMIT; + cl->options |= OPT_WAIT | OPT_WAIT_COMMIT; break; case 'h': - if (cl.type == GEOSTORE) + if (cl->type == GEOSTORE) print_geostore_usage(); else print_usage(); exit(EXIT_SUCCESS); break; case ':': case '?': fprintf(stderr, "Please use '-h' for usage.\n"); exit(EXIT_FAILURE); break; case -1: /* No more parameters on cmdline, only arguments. */ goto extra_args; default: goto unknown; }; } return 0; extra_args: - if (cl.type == CLIENT && !cl.msg.ticket.id[0]) { - cparg(cl.msg.ticket.id, "ticket name"); - } else if (cl.type == GEOSTORE) { - if (cl.op != ATTR_LIST) { - cparg(cl.attr_msg.attr.name, "attribute name"); + if (cl->type == CLIENT && !cl->msg.ticket.id[0]) { + cparg(cl->msg.ticket.id, "ticket name"); + } else if (cl->type == GEOSTORE) { + if (cl->op != ATTR_LIST) { + cparg(cl->attr_msg.attr.name, "attribute name"); } - if (cl.op == ATTR_SET) { - cparg(cl.attr_msg.attr.val, "attribute value"); + if (cl->op == ATTR_SET) { + cparg(cl->attr_msg.attr.val, "attribute value"); } } if (optind == argc) return 0; left = argc - optind; fprintf(stderr, "Superfluous argument%s: %s%s\n", left == 1 ? "" : "s", argv[optind], left == 1 ? "" : "..."); exit(EXIT_FAILURE); unknown: fprintf(stderr, "unknown option: %s\n", argv[optind]); exit(EXIT_FAILURE); missingarg: fprintf(stderr, "not enough arguments\n"); exit(EXIT_FAILURE); } static void set_scheduler(void) { struct sched_param sched_param; struct rlimit rlimit; int rv; rlimit.rlim_cur = RLIM_INFINITY; rlimit.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_MEMLOCK, &rlimit); rv = mlockall(MCL_CURRENT | MCL_FUTURE); if (rv < 0) { log_error("mlockall failed"); } rv = sched_get_priority_max(SCHED_RR); if (rv != -1) { sched_param.sched_priority = rv; rv = sched_setscheduler(0, SCHED_RR, &sched_param); if (rv == -1) log_error("could not set SCHED_RR priority %d: %s (%d)", sched_param.sched_priority, strerror(errno), errno); } else { log_error("could not get maximum scheduler priority err %d", errno); } } static int set_procfs_val(const char *path, const char *val) { int rc = -1; FILE *fp = fopen(path, "w"); if (fp) { if (fprintf(fp, "%s", val) > 0) rc = 0; fclose(fp); } return rc; } -static int do_status(struct booth_config **conf_pptr, int type) +static int do_status(struct command_line *cl, struct booth_config **conf_pptr) { pid_t pid; int rv, status_lock_fd, ret; const char *reason = NULL; char lockfile_data[1024], *cp; assert(conf_pptr != NULL); ret = PCMK_OCF_NOT_RUNNING; - rv = setup_config(conf_pptr, type); + rv = setup_config(cl, conf_pptr); if (rv) { reason = "Error reading configuration."; ret = PCMK_OCF_UNKNOWN_ERROR; goto quit; } if (!local) { reason = "No Service IP active here."; goto quit; } - rv = _lockfile(O_RDWR, &status_lock_fd, &pid); + rv = _lockfile(cl, O_RDWR, &status_lock_fd, &pid); if (status_lock_fd == -1) { reason = "No PID file."; goto quit; } if (rv == 0) { close(status_lock_fd); reason = "PID file not locked."; goto quit; } if (pid) { fprintf(stdout, "booth_lockpid=%d ", pid); fflush(stdout); } rv = read(status_lock_fd, lockfile_data, sizeof(lockfile_data) - 1); if (rv < 4) { close(status_lock_fd); reason = "Cannot read lockfile data."; ret = PCMK_LSB_UNKNOWN_ERROR; goto quit; } lockfile_data[rv] = 0; close(status_lock_fd); /* Make sure it's only a single line */ cp = strchr(lockfile_data, '\r'); if (cp) *cp = 0; cp = strchr(lockfile_data, '\n'); if (cp) *cp = 0; rv = setup_tcp_listener(1); if (rv == 0) { reason = "TCP port not in use."; goto quit; } fprintf(stdout, "booth_lockfile='%s' %s\n", - cl.lockfile, lockfile_data); + cl->lockfile, lockfile_data); if (!daemonize) fprintf(stderr, "Booth at %s port %d seems to be running.\n", site_string(local), site_port(local)); return 0; quit: log_debug("not running: %s", reason); /* Ie. "DEBUG" */ if (!daemonize) fprintf(stderr, "not running: %s\n", reason); return ret; } static int limit_this_process(struct booth_config *conf_ptr) { int rv; if (!is_root()) return 0; if (setregid(conf_ptr->gid, conf_ptr->gid) < 0) { rv = errno; log_error("setregid() didn't work: %s", strerror(rv)); return rv; } if (setreuid(conf_ptr->uid, conf_ptr->uid) < 0) { rv = errno; log_error("setreuid() didn't work: %s", strerror(rv)); return rv; } return 0; } static int lock_fd = -1; static void server_exit(void) { int rv; if (lock_fd >= 0) { /* We might not be able to delete it, but at least * make it empty. */ rv = ftruncate(lock_fd, 0); (void)rv; unlink_lockfile(lock_fd); } log_info("exiting"); } static void sig_exit_handler(int sig) { log_info("caught signal %d", sig); exit(0); } static void wait_child_adaptor(int sig) { wait_child(booth_conf); } -static int do_server(struct booth_config **conf_pptr, int type) +static int do_server(struct command_line *cl, struct booth_config **conf_pptr) { int rv = -1; static char log_ent[128] = DAEMON_NAME "-"; assert(conf_pptr != NULL); - rv = setup_config(conf_pptr, type); + rv = setup_config(cl, conf_pptr); if (rv < 0) return rv; if (!local) { log_error("Cannot find myself in the configuration."); exit(EXIT_FAILURE); } if (daemonize) { if (daemon(0, 0) < 0) { perror("daemon error"); exit(EXIT_FAILURE); } } /* The lockfile must be written to _after_ the call to daemon(), so * that the lockfile contains the pid of the daemon, not the parent. */ - lock_fd = create_lockfile(*conf_pptr); + lock_fd = create_lockfile(cl, *conf_pptr); if (lock_fd < 0) return lock_fd; atexit(server_exit); strcat(log_ent, type_to_string(local->type)); cl_log_set_entity(log_ent); cl_log_enable_stderr(enable_stderr ? TRUE : FALSE); cl_log_set_facility(HA_LOG_FACILITY); cl_inherit_logging_environment(0); log_info("BOOTH %s %s daemon is starting", type_to_string(local->type), RELEASE_STR); signal(SIGUSR1, (__sighandler_t)tickets_log_info); signal(SIGTERM, (__sighandler_t)sig_exit_handler); signal(SIGINT, (__sighandler_t)sig_exit_handler); /* we'll handle errors there and then */ signal(SIGPIPE, SIG_IGN); set_scheduler(); /* we don't want to be killed by the OOM-killer */ if (set_procfs_val("/proc/self/oom_score_adj", "-999")) (void)set_procfs_val("/proc/self/oom_adj", "-16"); set_proc_title("%s %s %s for [%s]:%d", - DAEMON_NAME, cl.configfile, type_to_string(local->type), + DAEMON_NAME, cl->configfile, type_to_string(local->type), site_string(local), site_port(local)); rv = limit_this_process(*conf_pptr); if (rv) return rv; #ifdef COREDUMP_NURSING if (cl_enable_coredumps(TRUE) < 0){ log_error("enabling core dump failed"); } cl_cdtocoredir(); prctl(PR_SET_DUMPABLE, (unsigned long)TRUE, 0UL, 0UL, 0UL); #else if (chdir(BOOTH_CORE_DIR) < 0) { log_error("cannot change working directory to %s", BOOTH_CORE_DIR); } #endif signal(SIGCHLD, (__sighandler_t) wait_child_adaptor); - rv = loop(*conf_pptr, lock_fd); + rv = loop(cl, *conf_pptr, lock_fd); return rv; } -static int do_client(struct booth_config **conf_pptr) +static int do_client(struct command_line *cl, struct booth_config **conf_pptr) { int rv; - rv = setup_config(conf_pptr, CLIENT); + rv = setup_config(cl, conf_pptr); if (rv < 0) { log_error("cannot read config"); goto out; } - switch (cl.op) { + switch (cl->op) { case CMD_LIST: case CMD_PEERS: - rv = query_get_string_answer(*conf_pptr, cl.op); + rv = query_get_string_answer(cl, *conf_pptr); break; case CMD_GRANT: case CMD_REVOKE: - rv = do_command(*conf_pptr, cl.op); + rv = do_command(cl, *conf_pptr); break; } out: return rv; } -static int do_attr(struct booth_config **conf_pptr) +static int do_attr(struct command_line *cl, struct booth_config **conf_pptr) { int rv = -1; assert(conf_pptr != NULL); - rv = setup_config(conf_pptr, GEOSTORE); + rv = setup_config(cl, conf_pptr); if (rv < 0) { log_error("cannot read config"); goto out; } /* We don't check for existence of ticket, so that asking can be * done without local configuration, too. * Although, that means that the UDP port has to be specified, too. */ - if (!cl.attr_msg.attr.tkt_id[0]) { + if (!cl->attr_msg.attr.tkt_id[0]) { /* If the loaded configuration has only a single ticket defined, use that. */ if ((*conf_pptr)->ticket_count == 1) { - strncpy(cl.attr_msg.attr.tkt_id, + strncpy(cl->attr_msg.attr.tkt_id, (*conf_pptr)->ticket[0].name, - sizeof(cl.attr_msg.attr.tkt_id)); + sizeof(cl->attr_msg.attr.tkt_id)); } else { rv = 1; log_error("No ticket given."); goto out; } } - switch (cl.op) { + switch (cl->op) { case ATTR_LIST: case ATTR_GET: - rv = query_get_string_answer(*conf_pptr, cl.op); + rv = query_get_string_answer(cl, *conf_pptr); break; case ATTR_SET: case ATTR_DEL: - rv = do_attr_command((*conf_pptr), cl.op); + rv = do_attr_command(cl, *conf_pptr); break; } out: return rv; } int main(int argc, char *argv[], char *envp[]) { int rv; const char *cp; #ifdef LOGGING_LIBQB enum qb_log_target_slot i; #endif init_set_proc_title(argc, argv, envp); get_time(&start_time); - memset(&cl, 0, sizeof(cl)); - strncpy(cl.configfile, - BOOTH_DEFAULT_CONF, BOOTH_PATH_LEN - 1); - cl.lockfile[0] = 0; + memset(&cmd_line, 0, sizeof(cmd_line)); + strncpy(cmd_line.configfile, BOOTH_DEFAULT_CONF, BOOTH_PATH_LEN - 1); debug_level = 0; cp = ((cp = strstr(argv[0], ATTR_PROG)) && !strcmp(cp, ATTR_PROG) ? ATTR_PROG : "booth"); #ifndef LOGGING_LIBQB cl_log_set_entity(cp); #else qb_log_init(cp, LOG_USER, LOG_DEBUG); /* prio driven by debug_level */ for (i = QB_LOG_TARGET_START; i < QB_LOG_TARGET_MAX; i++) { if (i == QB_LOG_SYSLOG || i == QB_LOG_BLACKBOX) continue; qb_log_format_set(i, "%t %H %N: [%P]: %p: %b"); } (void) qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_DEBUG); #endif cl_log_enable_stderr(TRUE); cl_log_set_facility(0); - rv = read_arguments(argc, argv); + rv = read_arguments(&cmd_line, argc, argv); if (rv < 0) goto out; - switch (cl.type) { + switch (cmd_line.type) { case STATUS: - rv = do_status(&booth_conf, cl.type); + rv = do_status(&cmd_line, &booth_conf); break; case ARBITRATOR: case DAEMON: case SITE: - rv = do_server(&booth_conf, cl.type); + rv = do_server(&cmd_line, &booth_conf); break; case CLIENT: - rv = do_client(&booth_conf); + rv = do_client(&cmd_line, &booth_conf); break; case GEOSTORE: - rv = do_attr(&booth_conf); + rv = do_attr(&cmd_line, &booth_conf); break; } out: #ifdef LOGGING_LIBQB qb_log_fini(); #endif /* Normalize values. 0x100 would be seen as "OK" by waitpid(). */ return (rv >= 0 && rv < 0x70) ? rv : 1; }