Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F3151548
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
77 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/Makefile.am b/src/Makefile.am
index 6c8b6d6..0b6780f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,57 +1,59 @@
MAINTAINERCLEANFILES = Makefile.in
AM_CFLAGS = -fPIC -Werror -funsigned-char -Wno-pointer-sign
AM_CPPFLAGS = -I$(top_builddir)/include
sbin_PROGRAMS = boothd
-boothd_SOURCES = config.c main.c raft.c ticket.c transport.c \
- pacemaker.c handler.c request.c attr.c manual.c
+boothd_SOURCES = attr.c config.c handler.c main.c \
+ manual.c pacemaker.c raft.c request.c \
+ ticket.c transport.c utils.c
-noinst_HEADERS = booth.h pacemaker.h \
- config.h log.h raft.h ticket.h transport.h handler.h request.h attr.h manual.h
+noinst_HEADERS = attr.h booth.h config.h handler.h log.h \
+ manual.h pacemaker.h raft.h request.h \
+ ticket.h transport.h utils.h
if BUILD_TIMER_C
boothd_SOURCES += timer.c
endif
if BUILD_AUTH_C
boothd_SOURCES += auth.c
endif
boothd_LDFLAGS = $(OS_DYFLAGS) -L./
boothd_LDADD = -lm $(GLIB_LIBS) $(ZLIB_LIBS)
boothd_CFLAGS = $(GLIB_CFLAGS)
if !LOGGING_LIBQB
boothd_LDADD += -lplumb
else
boothd_LDADD += $(LIBQB_LIBS)
boothd_SOURCES += alt/logging_libqb.c
noinst_HEADERS += alt/logging_libqb.h
endif
if !RANGE2RANDOM_GLIB
boothd_LDADD += -lplumb
else
boothd_LDADD += $(GLIB_LIBS)
boothd_SOURCES += alt/range2random_glib.c
noinst_HEADERS += alt/range2random_glib.h
endif
if !NAMETAG_LIBSYSTEMD
boothd_LDADD += -lplumbgpl
else
boothd_LDADD += $(LIBSYSTEMD_LIBS)
boothd_SOURCES += alt/nametag_libsystemd.c
noinst_HEADERS += alt/nametag_libsystemd.h
endif
if COREDUMP_NURSING
boothd_LDADD += -lplumb
endif
lint:
-splint $(INCLUDES) $(LINT_FLAGS) $(CFLAGS) *.c
diff --git a/src/booth.h b/src/booth.h
index 72eb074..d449ee3 100644
--- a/src/booth.h
+++ b/src/booth.h
@@ -1,424 +1,410 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* 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 <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <glib.h>
#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 *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);
-/**
- * @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;
};
/* 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 aa7063f..577644b 100644
--- a/src/config.c
+++ b/src/config.c
@@ -1,1041 +1,1042 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* 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 <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <assert.h>
#include <zlib.h>
#include <sys/types.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include "b_config.h"
#include "booth.h"
#include "config.h"
+#include "log.h"
#include "raft.h"
#include "ticket.h"
-#include "log.h"
+#include "utils.h"
static int ticket_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;
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; i<site->index; 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_NODES; i++) {
/* End of input? */
if (*input == 0)
break;
v = strtol(input, &cp, 0);
if (input == cp) {
log_error("No integer weight value at \"%s\"", input);
return -1;
}
weights[i] = v;
while (*cp) {
/* Separator characters */
if (isspace(*cp) ||
strchr(",;:-+", *cp))
cp++;
/* Next weight */
else if (isdigit(*cp))
break;
/* Rest */
else {
log_error("Invalid character at \"%s\"", cp);
return -1;
}
}
input = cp;
}
/* Fill rest of vector. */
for(v=i; v<MAX_NODES; v++) {
weights[v] = 0;
}
return i;
}
/* returns TICKET_MODE_AUTO if failed to parse the ticket mode. */
static ticket_mode_e retrieve_ticket_mode(const char *input)
{
if (strcasecmp(input, "manual") == 0) {
return TICKET_MODE_MANUAL;
}
return TICKET_MODE_AUTO;
}
/* scan val for time; time is [0-9]+(ms)?, i.e. either in seconds
* or milliseconds
* returns -1 on failure, otherwise time in ms
*/
static long read_time(char *val)
{
long t;
char *ep;
t = strtol(val, &ep, 10);
if (ep == val) { /* matched none */
t = -1L;
} else if (*ep == '\0') { /* matched all */
t = t*1000L; /* in seconds, convert to ms */
} else if (strcmp(ep, "ms")) { /* ms not exactly matched */
t = -1L;
} /* otherwise, time in ms */
/* if second fractions configured, send finer resolution
* times (i.e. term_valid_for) */
if (t % 1000L) {
TIME_MULT = 1000;
}
return t;
}
/* make arguments for execv(2)
* tk_test.path points to the path
* tk_test.argv is argument vector (starts with the prog)
* (strtok pokes holes in the configuration parameter value, i.e.
* we don't need to allocate memory for arguments)
*/
static int parse_extprog(char *val, struct ticket_config *tk)
{
char *p;
int i = 0;
if (tk_test.path) {
free(tk_test.path);
}
if (!(tk_test.path = strdup(val))) {
log_error("out of memory");
return -1;
}
p = strtok(tk_test.path, " \t");
tk_test.argv[i++] = p;
do {
p = strtok(NULL, " \t");
if (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;
}
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");
(*conf_pptr)->poll_timeout = min(POLL_TIMEOUT, min_timeout/10);
if ((*conf_pptr)->poll_timeout == 0)
(*conf_pptr)->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;
assert(conf_ptr->local != NULL);
FOREACH_NODE(conf_ptr, i, n) {
if (n != conf_ptr->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/main.c b/src/main.c
index 14bc4da..b4ab0d0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,1661 +1,1648 @@
/*
* Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
* Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
*
* 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <limits.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/poll.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <signal.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <pacemaker/crm/services.h>
#include "b_config.h"
#if HAVE_LIBGCRYPT
#include <gcrypt.h>
#endif
#ifndef NAMETAG_LIBSYSTEMD
#include <clplumbing/setproctitle.h>
#else
#include "alt/nametag_libsystemd.h"
#endif
#ifdef COREDUMP_NURSING
#include <sys/prctl.h>
#include <clplumbing/coredumps.h>
#endif
#include "log.h"
#include "booth.h"
#include "config.h"
#include "transport.h"
#include "inline-fn.h"
#include "pacemaker.h"
#include "ticket.h"
+#include "utils.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;
typedef enum
{
BOOTHD_STARTED=0,
BOOTHD_STARTING
} BOOTH_DAEMON_STATE;
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);
assert(conf_ptr->local != 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 == conf_ptr->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 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,
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,
&(*conf_pptr)->local, 1)) {
log_error("Cannot find \"%s\" in the configuration.",
cl->site);
return -EINVAL;
}
(*conf_pptr)->local->local = 1;
} else
find_myself(*conf_pptr,
cl->type == CLIENT || cl->type == GEOSTORE);
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,
"%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 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(conf_ptr->local->type), conf_ptr->name,
get_local_id(conf_ptr), site_string(conf_ptr->local),
site_port(conf_ptr->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));
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 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(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));
goto fail;
}
log_info("BOOTH %s daemon started, node id is 0x%08X (%d).",
type_to_string(conf_ptr->local->type),
conf_ptr->local->site_id,
conf_ptr->local->site_id);
while (1) {
rv = poll(pollfds, client_maxi + 1, conf_ptr->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(struct command_line *cl, cmd_result_t reply_code)
{
int rv = 0;
const char *op_str = "";
if (cl->type == CMD_GRANT)
op_str = "grant";
else if (cl->type == CMD_REVOKE)
op_str = "revoke";
else if (cl->type == CMD_LIST)
op_str = "list";
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;
break;
case RLT_MORE:
rv = 2;
break;
case RLT_SYNC_SUCC:
case RLT_SUCCESS:
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);
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);
rv = -1;
break;
case RLT_ATTR_PREREQ:
log_error("attr-prereq for ticket \"%s\" failed, grant denied",
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 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) (struct command_line *, cmd_result_t reply_code);
size_t msg_size;
void *request;
assert(conf_ptr != NULL && conf_ptr->transport != NULL);
assert(conf_ptr->local != NULL);
if (cl->type == GEOSTORE) {
test_reply_f = test_attr_reply;
msg_size = sizeof(cl->attr_msg);
request = &cl->attr_msg;
} else {
test_reply_f = test_reply;
msg_size = sizeof(cl->msg);
request = &cl->msg;
}
header = (struct boothc_header *)request;
data = NULL;
init_header(conf_ptr, header, cl->op, 0, cl->options, 0, 0, msg_size);
if (*cl->site == '\0')
site = conf_ptr->local;
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(cl, ntohl(reply.header.result));
out_close:
tpt->close(site);
out:
if (data)
free(data);
return rv;
}
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 (cl->type == CMD_GRANT)
op_str = "grant";
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 == '\0')
site = conf_ptr->local;
else {
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 == conf_ptr->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);
}
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 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));
} else {
log_error("No ticket given.");
goto out_close;
}
}
redirect:
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));
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(cl, ntohl(reply.header.result));
goto out_close;
}
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)) {
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(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,
strlen(BOOTH_RUN_DIR)) == 0)
mkdir(BOOTH_RUN_DIR, 0775);
if (locked_by)
*locked_by = 0;
*fdp = -1;
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 command_line *cl,
struct booth_config *conf_ptr)
{
int rv, fd;
fd = -1;
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));
return -1;
}
if (rv < 0) {
log_error("lockfile %s setlk error %d: %s",
cl->lockfile, rv, strerror(rv));
goto fail;
}
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));
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(cmd_line.lockfile);
close(fd);
}
static void print_usage(void)
{
printf(
"Usage:\n"
" booth list [options]\n"
" booth {grant|revoke} [options] <ticket>\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 <site> 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, 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(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;
if ((cp = strstr(argv[0], ATTR_PROG)) &&
!strcmp(cp, ATTR_PROG)) {
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;
optind = 2;
} else if (argc > 1 && (strcmp(arg1, "status") == 0)) {
cl->type = STATUS;
optind = 2;
} else if (argc > 1 && (strcmp(arg1, "client") == 0)) {
cl->type = CLIENT;
if (argc < 3) {
print_usage();
exit(EXIT_FAILURE);
}
op = argv[2];
optind = 3;
}
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)
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 (!strcmp(op, "list"))
cl->op = CMD_LIST;
else if (!strcmp(op, "grant"))
cl->op = CMD_GRANT;
else if (!strcmp(op, "revoke"))
cl->op = CMD_REVOKE;
else if (!strcmp(op, "peers"))
cl->op = CMD_PEERS;
else {
fprintf(stderr, "client operation \"%s\" is unknown\n",
op);
exit(EXIT_FAILURE);
}
} else if (cl->type == GEOSTORE) {
if (!strcmp(op, "list"))
cl->op = ATTR_LIST;
else if (!strcmp(op, "set"))
cl->op = ATTR_SET;
else if (!strcmp(op, "get"))
cl->op = ATTR_GET;
else if (!strcmp(op, "delete"))
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");
} 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);
assert(*(cp-1) == '/');
/* Write at the \0, ie. after the "/" */
safe_copy(cp, optarg,
(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");
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");
} 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 (strcmp(optarg, OTHER_SITE) &&
host_convert(optarg, site_arg, INET_ADDRSTRLEN) == 0) {
safe_copy(cl->site, site_arg, sizeof(cl->site), "site name");
} else {
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) {
log_error("use \"-F\" only for client grant");
exit(EXIT_FAILURE);
}
cl->options |= OPT_IMMEDIATE;
break;
case 'w':
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;
break;
case 'C':
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;
break;
case 'h':
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->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 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);
assert(*conf_pptr != NULL);
ret = PCMK_OCF_NOT_RUNNING;
rv = setup_config(cl, conf_pptr);
if (rv) {
reason = "Error reading configuration.";
ret = PCMK_OCF_UNKNOWN_ERROR;
goto quit;
}
if ((*conf_pptr)->local == NULL) {
reason = "No Service IP active here.";
goto quit;
}
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((*conf_pptr)->local, 1);
if (rv == 0) {
reason = "TCP port not in use.";
goto quit;
}
fprintf(stdout, "booth_lockfile='%s' %s\n",
cl->lockfile, lockfile_data);
if (!daemonize)
fprintf(stderr, "Booth at %s port %d seems to be running.\n",
site_string((*conf_pptr)->local),
site_port((*conf_pptr)->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 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(cl, conf_pptr);
if (rv < 0)
return rv;
if ((*conf_pptr)->local == NULL) {
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(cl, *conf_pptr);
if (lock_fd < 0)
return lock_fd;
atexit(server_exit);
strcat(log_ent, type_to_string((*conf_pptr)->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((*conf_pptr)->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");
/* whenever changing this, sd_notify_wrapper needs to be updated! */
set_proc_title("%s %s %s for [%s]:%d",
DAEMON_NAME, cl->configfile,
type_to_string((*conf_pptr)->local->type),
site_string((*conf_pptr)->local),
site_port((*conf_pptr)->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(cl, *conf_pptr, lock_fd);
return rv;
}
static int do_client(struct command_line *cl, struct booth_config **conf_pptr)
{
int rv;
rv = setup_config(cl, conf_pptr);
if (rv < 0) {
log_error("cannot read config");
goto out;
}
switch (cl->op) {
case CMD_LIST:
case CMD_PEERS:
rv = query_get_string_answer(cl, *conf_pptr);
break;
case CMD_GRANT:
case CMD_REVOKE:
rv = do_command(cl, *conf_pptr);
break;
}
out:
return rv;
}
static int do_attr(struct command_line *cl, struct booth_config **conf_pptr)
{
int rv = -1;
assert(conf_pptr != NULL);
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 the loaded configuration has only a single ticket defined, use that. */
if ((*conf_pptr)->ticket_count == 1) {
strncpy(cl->attr_msg.attr.tkt_id,
(*conf_pptr)->ticket[0].name,
sizeof(cl->attr_msg.attr.tkt_id));
} else {
rv = 1;
log_error("No ticket given.");
goto out;
}
}
switch (cl->op) {
case ATTR_LIST:
case ATTR_GET:
rv = query_get_string_answer(cl, *conf_pptr);
break;
case ATTR_SET:
case ATTR_DEL:
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(&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(&cmd_line, argc, argv);
if (rv < 0)
goto out;
switch (cmd_line.type) {
case STATUS:
rv = do_status(&cmd_line, &booth_conf);
break;
case ARBITRATOR:
case DAEMON:
case SITE:
rv = do_server(&cmd_line, &booth_conf);
break;
case CLIENT:
rv = do_client(&cmd_line, &booth_conf);
break;
case GEOSTORE:
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;
}
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..8883d1f
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
+ * Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "utils.h"
+
+#include <stdio.h> /* EXIT_FAILURE */
+#include <stdlib.h> /* fprintf */
+#include <string.h> /* strlen, strncpy */
+
+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;
+}
diff --git a/src/utils.h b/src/utils.h
new file mode 100644
index 0000000..4338bd3
--- /dev/null
+++ b/src/utils.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2011 Jiaju Zhang <jjzhang@suse.de>
+ * Copyright (C) 2013-2014 Philipp Marek <philipp.marek@linbit.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#pragma once
+
+#include <stdlib.h> /* size_t */
+
+/**
+ * @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);
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Feb 24, 6:13 AM (1 d, 3 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1463994
Default Alt Text
(77 KB)
Attached To
Mode
rB Booth
Attached
Detach File
Event Timeline
Log In to Comment