diff options
author | Anas Nashif <anas.nashif@intel.com> | 2013-03-05 01:47:43 -0800 |
---|---|---|
committer | Anas Nashif <anas.nashif@intel.com> | 2013-03-05 01:47:43 -0800 |
commit | 44a3c2255bc480c82f34db156553a595606d8a0b (patch) | |
tree | 5e6df96a6c6e40207cb3a711860e16b543918c0d /libdaemon | |
parent | 8bd28eea831fd5215c12e6fcecc8e9a772398ed9 (diff) | |
download | device-mapper-44a3c2255bc480c82f34db156553a595606d8a0b.tar.gz device-mapper-44a3c2255bc480c82f34db156553a595606d8a0b.tar.bz2 device-mapper-44a3c2255bc480c82f34db156553a595606d8a0b.zip |
Imported Upstream version 2.02.98upstream/2.02.98upstream/1.02.77
Diffstat (limited to 'libdaemon')
-rw-r--r-- | libdaemon/Makefile.in | 31 | ||||
-rw-r--r-- | libdaemon/client/Makefile.in | 20 | ||||
-rw-r--r-- | libdaemon/client/config-util.c | 329 | ||||
-rw-r--r-- | libdaemon/client/config-util.h | 67 | ||||
-rw-r--r-- | libdaemon/client/daemon-client.c | 188 | ||||
-rw-r--r-- | libdaemon/client/daemon-client.h | 112 | ||||
-rw-r--r-- | libdaemon/client/daemon-io.c | 92 | ||||
-rw-r--r-- | libdaemon/client/daemon-io.h | 31 | ||||
-rw-r--r-- | libdaemon/server/Makefile.in | 22 | ||||
-rw-r--r-- | libdaemon/server/daemon-log.c | 180 | ||||
-rw-r--r-- | libdaemon/server/daemon-log.h | 33 | ||||
-rw-r--r-- | libdaemon/server/daemon-server.c | 539 | ||||
-rw-r--r-- | libdaemon/server/daemon-server.h | 158 |
13 files changed, 1802 insertions, 0 deletions
diff --git a/libdaemon/Makefile.in b/libdaemon/Makefile.in new file mode 100644 index 0000000..dbe6516 --- /dev/null +++ b/libdaemon/Makefile.in @@ -0,0 +1,31 @@ +# +# Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved. +# +# This file is part of LVM2. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions +# of the GNU General Public License v.2. +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +top_builddir = @top_builddir@ + +.PHONY: client server + +SUBDIRS += client + +ifeq ("@BUILD_LVMETAD@", "yes") + SUBDIRS += server +server: client +endif + +ifeq ($(MAKECMDGOALS),distclean) + SUBDIRS = client server +endif + +include $(top_builddir)/make.tmpl diff --git a/libdaemon/client/Makefile.in b/libdaemon/client/Makefile.in new file mode 100644 index 0000000..d608816 --- /dev/null +++ b/libdaemon/client/Makefile.in @@ -0,0 +1,20 @@ +# Copyright (C) 2011 Red Hat, Inc. All rights reserved. +# +# This file is part of the device-mapper userspace tools. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions +# of the GNU Lesser General Public License v.2.1. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +top_builddir = @top_builddir@ + +LIB_STATIC = libdaemonclient.a +SOURCES = daemon-io.c config-util.c daemon-client.c + +include $(top_builddir)/make.tmpl diff --git a/libdaemon/client/config-util.c b/libdaemon/client/config-util.c new file mode 100644 index 0000000..1c83134 --- /dev/null +++ b/libdaemon/client/config-util.c @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "dm-logging.h" +#include "config-util.h" +#include "libdevmapper.h" + +int buffer_append_vf(struct buffer *buf, va_list ap) +{ + char *append; + char *next; + int keylen; + int64_t value; + char *string; + char *block; + + while ((next = va_arg(ap, char *))) { + append = NULL; + if (!strchr(next, '=')) { + log_error(INTERNAL_ERROR "Bad format string at '%s'", next); + goto fail; + } + keylen = strchr(next, '=') - next; + if (strstr(next, "%d") || strstr(next, "%" PRId64)) { + value = va_arg(ap, int64_t); + if (dm_asprintf(&append, "%.*s= %" PRId64 "\n", keylen, next, value) < 0) + goto fail; + } else if (strstr(next, "%s")) { + string = va_arg(ap, char *); + if (dm_asprintf(&append, "%.*s= \"%s\"\n", keylen, next, string) < 0) + goto fail; + } else if (strstr(next, "%b")) { + if (!(block = va_arg(ap, char *))) + continue; + if (dm_asprintf(&append, "%.*s%s", keylen, next, block) < 0) + goto fail; + } else if (dm_asprintf(&append, "%s", next) < 0) + goto fail; + + if (!append || + !buffer_append(buf, append)) + goto fail; + + dm_free(append); + } + + return 1; +fail: + dm_free(append); + return 0; +} + +int buffer_append_f(struct buffer *buf, ...) +{ + int res; + va_list ap; + + va_start(ap, buf); + res = buffer_append_vf(buf, ap); + va_end(ap); + + return res; +} + +int set_flag(struct dm_config_tree *cft, struct dm_config_node *parent, + const char *field, const char *flag, int want) +{ + struct dm_config_value *value = NULL, *pred = NULL; + struct dm_config_node *node = dm_config_find_node(parent->child, field); + struct dm_config_value *new; + + if (node) + value = node->v; + + while (value && value->type != DM_CFG_EMPTY_ARRAY && strcmp(value->v.str, flag)) { + pred = value; + value = value->next; + } + + if (value && want) + return 1; + + if (!value && !want) + return 1; + + if (value && !want) { + if (pred) { + pred->next = value->next; + } else if (value == node->v && value->next) { + node->v = value->next; + } else { + node->v->type = DM_CFG_EMPTY_ARRAY; + } + } + + if (!value && want) { + if (!node) { + if (!(node = dm_config_create_node(cft, field))) + return 0; + node->sib = parent->child; + if (!(node->v = dm_config_create_value(cft))) + return 0; + node->v->type = DM_CFG_EMPTY_ARRAY; + node->parent = parent; + parent->child = node; + } + if (!(new = dm_config_create_value(cft))) { + /* FIXME error reporting */ + return 0; + } + new->type = DM_CFG_STRING; + new->v.str = flag; + new->next = node->v; + node->v = new; + } + + return 1; +} + +static void chain_node(struct dm_config_node *cn, + struct dm_config_node *parent, + struct dm_config_node *pre_sib) +{ + cn->parent = parent; + cn->sib = NULL; + + if (parent && parent->child && !pre_sib) { /* find the last one */ + pre_sib = parent->child; + while (pre_sib && pre_sib->sib) + pre_sib = pre_sib->sib; + } + + if (parent && !parent->child) + parent->child = cn; + if (pre_sib) { + cn->sib = pre_sib->sib; + pre_sib->sib = cn; + } + +} + +struct dm_config_node *make_config_node(struct dm_config_tree *cft, + const char *key, + struct dm_config_node *parent, + struct dm_config_node *pre_sib) +{ + struct dm_config_node *cn; + + if (!(cn = dm_config_create_node(cft, key))) + return NULL; + + cn->v = NULL; + cn->child = NULL; + + chain_node(cn, parent, pre_sib); + + return cn; +} + +struct dm_config_node *make_text_node(struct dm_config_tree *cft, + const char *key, + const char *value, + struct dm_config_node *parent, + struct dm_config_node *pre_sib) +{ + struct dm_config_node *cn; + + if (!(cn = make_config_node(cft, key, parent, pre_sib)) || + !(cn->v = dm_config_create_value(cft))) + return NULL; + + cn->v->type = DM_CFG_STRING; + cn->v->v.str = value; + return cn; +} + +struct dm_config_node *make_int_node(struct dm_config_tree *cft, + const char *key, + int64_t value, + struct dm_config_node *parent, + struct dm_config_node *pre_sib) +{ + struct dm_config_node *cn; + + if (!(cn = make_config_node(cft, key, parent, pre_sib)) || + !(cn->v = dm_config_create_value(cft))) + return NULL; + + cn->v->type = DM_CFG_INT; + cn->v->v.i = value; + return cn; +} + +struct dm_config_node *config_make_nodes_v(struct dm_config_tree *cft, + struct dm_config_node *parent, + struct dm_config_node *pre_sib, + va_list ap) +{ + const char *next; + struct dm_config_node *first = NULL; + struct dm_config_node *cn; + const char *fmt, *key; + + while ((next = va_arg(ap, char *))) { + cn = NULL; + fmt = strchr(next, '='); + + if (!fmt) { + log_error(INTERNAL_ERROR "Bad format string '%s'", fmt); + return_NULL; + } + fmt += 2; + + key = dm_pool_strdup(cft->mem, next); + *strchr(key, '=') = 0; + + if (!strcmp(fmt, "%d") || !strcmp(fmt, "%" PRId64)) { + int64_t value = va_arg(ap, int64_t); + if (!(cn = make_int_node(cft, key, value, parent, pre_sib))) + return 0; + } else if (!strcmp(fmt, "%s")) { + char *value = va_arg(ap, char *); + if (!(cn = make_text_node(cft, key, value, parent, pre_sib))) + return 0; + } else if (!strcmp(fmt, "%t")) { + struct dm_config_tree *tree = va_arg(ap, struct dm_config_tree *); + cn = dm_config_clone_node(cft, tree->root, 1); + if (!cn) + return 0; + cn->key = key; + chain_node(cn, parent, pre_sib); + } else { + log_error(INTERNAL_ERROR "Bad format string '%s'", fmt); + return_NULL; + } + if (!first) + first = cn; + if (cn) + pre_sib = cn; + } + + return first; +} + +struct dm_config_node *config_make_nodes(struct dm_config_tree *cft, + struct dm_config_node *parent, + struct dm_config_node *pre_sib, + ...) +{ + struct dm_config_node *res; + va_list ap; + + va_start(ap, pre_sib); + res = config_make_nodes_v(cft, parent, pre_sib, ap); + va_end(ap); + + return res; +} + +int buffer_realloc(struct buffer *buf, int needed) +{ + char *new; + int alloc = buf->allocated; + if (alloc < needed) + alloc = needed; + + buf->allocated += alloc; + new = realloc(buf->mem, buf->allocated); + if (new) + buf->mem = new; + else { /* utter failure */ + dm_free(buf->mem); + buf->mem = 0; + buf->allocated = buf->used = 0; + return 0; + } + return 1; +} + +int buffer_append(struct buffer *buf, const char *string) +{ + int len = strlen(string); + + if ((buf->allocated - buf->used <= len) && + !buffer_realloc(buf, len + 1)) + return 0; + + strcpy(buf->mem + buf->used, string); + buf->used += len; + return 1; +} + +int buffer_line(const char *line, void *baton) +{ + struct buffer *buf = baton; + if (!buffer_append(buf, line)) + return 0; + if (!buffer_append(buf, "\n")) + return 0; + return 1; +} + +void buffer_destroy(struct buffer *buf) +{ + dm_free(buf->mem); + buffer_init(buf); +} + +void buffer_init(struct buffer *buf) +{ + buf->allocated = buf->used = 0; + buf->mem = 0; +} diff --git a/libdaemon/client/config-util.h b/libdaemon/client/config-util.h new file mode 100644 index 0000000..f03bcab --- /dev/null +++ b/libdaemon/client/config-util.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LVM_DAEMON_CONFIG_UTIL_H +#define _LVM_DAEMON_CONFIG_UTIL_H + +#include "configure.h" +#include "libdevmapper.h" + +#include <stdarg.h> + +struct buffer { + int allocated; + int used; + char *mem; +}; + +int buffer_append_vf(struct buffer *buf, va_list ap); +int buffer_append_f(struct buffer *buf, ...); +int buffer_append(struct buffer *buf, const char *string); +void buffer_init(struct buffer *buf); +void buffer_destroy(struct buffer *buf); +int buffer_realloc(struct buffer *buf, int required); + +int buffer_line(const char *line, void *baton); + +int set_flag(struct dm_config_tree *cft, struct dm_config_node *parent, + const char *field, const char *flag, int want); + +struct dm_config_node *make_config_node(struct dm_config_tree *cft, + const char *key, + struct dm_config_node *parent, + struct dm_config_node *pre_sib); + +struct dm_config_node *make_text_node(struct dm_config_tree *cft, + const char *key, + const char *value, + struct dm_config_node *parent, + struct dm_config_node *pre_sib); + +struct dm_config_node *make_int_node(struct dm_config_tree *cft, + const char *key, + int64_t value, + struct dm_config_node *parent, + struct dm_config_node *pre_sib); + +struct dm_config_node *config_make_nodes_v(struct dm_config_tree *cft, + struct dm_config_node *parent, + struct dm_config_node *pre_sib, + va_list ap); +struct dm_config_node *config_make_nodes(struct dm_config_tree *cft, + struct dm_config_node *parent, + struct dm_config_node *pre_sib, + ...); + +#endif /* _LVM_DAEMON_SHARED_H */ diff --git a/libdaemon/client/daemon-client.c b/libdaemon/client/daemon-client.c new file mode 100644 index 0000000..a010cc0 --- /dev/null +++ b/libdaemon/client/daemon-client.c @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "daemon-io.h" +#include "config-util.h" +#include "daemon-client.h" +#include "dm-logging.h" + +#include <sys/un.h> +#include <sys/socket.h> +#include <string.h> +#include <stdio.h> +#include <unistd.h> +#include <assert.h> +#include <errno.h> // ENOMEM + +daemon_handle daemon_open(daemon_info i) { + daemon_handle h = { .protocol_version = 0, .error = 0 }; + daemon_reply r = { .cft = NULL }; + struct sockaddr_un sockaddr = { .sun_family = AF_UNIX }; + + if ((h.socket_fd = socket(PF_UNIX, SOCK_STREAM /* | SOCK_NONBLOCK */, 0)) < 0) + goto error; + + if (!dm_strncpy(sockaddr.sun_path, i.socket, sizeof(sockaddr.sun_path))) { + fprintf(stderr, "%s: daemon socket path too long.\n", i.socket); + goto error; + } + + if (connect(h.socket_fd,(struct sockaddr *) &sockaddr, sizeof(sockaddr))) + goto error; + + r = daemon_send_simple(h, "hello", NULL); + if (r.error || strcmp(daemon_reply_str(r, "response", "unknown"), "OK")) + goto error; + + h.protocol = daemon_reply_str(r, "protocol", NULL); + if (h.protocol) + h.protocol = dm_strdup(h.protocol); /* keep around */ + h.protocol_version = daemon_reply_int(r, "version", 0); + + if (i.protocol && (!h.protocol || strcmp(h.protocol, i.protocol))) + goto error; + if (i.protocol_version && h.protocol_version != i.protocol_version) + goto error; + + daemon_reply_destroy(r); + return h; + +error: + h.error = errno; + if (h.socket_fd >= 0) + if (close(h.socket_fd)) + log_sys_error("close", "daemon_open"); + if (r.cft) + daemon_reply_destroy(r); + h.socket_fd = -1; + return h; +} + +daemon_reply daemon_send(daemon_handle h, daemon_request rq) +{ + struct buffer buffer; + daemon_reply reply = { .cft = NULL, .error = 0 }; + assert(h.socket_fd >= 0); + buffer = rq.buffer; + + if (!buffer.mem) + dm_config_write_node(rq.cft->root, buffer_line, &buffer); + + assert(buffer.mem); + if (!buffer_write(h.socket_fd, &buffer)) + reply.error = errno; + + if (buffer_read(h.socket_fd, &reply.buffer)) { + reply.cft = dm_config_from_string(reply.buffer.mem); + if (!reply.cft) + reply.error = EPROTO; + } else + reply.error = errno; + + if (buffer.mem != rq.buffer.mem) + buffer_destroy(&buffer); + + return reply; +} + +void daemon_reply_destroy(daemon_reply r) { + if (r.cft) + dm_config_destroy(r.cft); + buffer_destroy(&r.buffer); +} + +daemon_reply daemon_send_simple_v(daemon_handle h, const char *id, va_list ap) +{ + static const daemon_reply err = { .error = ENOMEM, .buffer = { 0, 0, NULL }, .cft = NULL }; + daemon_request rq = { .cft = NULL }; + daemon_reply repl; + + if (!buffer_append_f(&rq.buffer, "request = %s", id, NULL) || + !buffer_append_vf(&rq.buffer, ap)) { + buffer_destroy(&rq.buffer); + return err; + } + + repl = daemon_send(h, rq); + buffer_destroy(&rq.buffer); + + return repl; +} + +daemon_reply daemon_send_simple(daemon_handle h, const char *id, ...) +{ + daemon_reply r; + va_list ap; + + va_start(ap, id); + r = daemon_send_simple_v(h, id, ap); + va_end(ap); + + return r; +} + +void daemon_close(daemon_handle h) +{ + dm_free((char *)h.protocol); +} + +daemon_request daemon_request_make(const char *id) +{ + daemon_request r; + r.cft = NULL; + buffer_init(&r.buffer); + + if (!(r.cft = dm_config_create())) + goto bad; + + if (!(r.cft->root = make_text_node(r.cft, "request", id, NULL, NULL))) + goto bad; + + return r; +bad: + if (r.cft) + dm_config_destroy(r.cft); + r.cft = NULL; + return r; +} + +int daemon_request_extend_v(daemon_request r, va_list ap) +{ + if (!r.cft) + return 0; + + if (!config_make_nodes_v(r.cft, NULL, r.cft->root, ap)) + return 0; + + return 1; +} + +int daemon_request_extend(daemon_request r, ...) +{ + int res; + va_list ap; + + va_start(ap, r); + res = daemon_request_extend_v(r, ap); + va_end(ap); + + return res; +} + +void daemon_request_destroy(daemon_request r) { + if (r.cft) + dm_config_destroy(r.cft); + buffer_destroy(&r.buffer); +} + diff --git a/libdaemon/client/daemon-client.h b/libdaemon/client/daemon-client.h new file mode 100644 index 0000000..6ba65e6 --- /dev/null +++ b/libdaemon/client/daemon-client.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LVM_DAEMON_COMMON_CLIENT_H +#define _LVM_DAEMON_COMMON_CLIENT_H + +#include "libdevmapper.h" +#include "config-util.h" + +typedef struct { + int socket_fd; /* the fd we use to talk to the daemon */ + const char *protocol; + int protocol_version; /* version of the protocol the daemon uses */ + int error; +} daemon_handle; + +typedef struct { + const char *path; /* the binary of the daemon */ + const char *socket; /* path to the comms socket */ + unsigned autostart:1; /* start the daemon if not running? */ + + /* + * If the following are not NULL/0, an attempt to talk to a daemon which + * uses a different protocol or version will fail. + */ + const char *protocol; + int protocol_version; +} daemon_info; + +typedef struct { + struct buffer buffer; + /* + * The request looks like this: + * request = "id" + * arg_foo = "something" + * arg_bar = 3 + * arg_wibble { + * something_special = "here" + * amount = 75 + * knobs = [ "twiddle", "tweak" ] + * } + */ + struct dm_config_tree *cft; +} daemon_request; + +typedef struct { + int error; /* 0 for success */ + struct buffer buffer; + struct dm_config_tree *cft; /* parsed reply, if available */ +} daemon_reply; + +/* + * Open the communication channel to the daemon. If the daemon is not running, + * it may be autostarted based on the binary path provided in the info (this + * will only happen if autostart is set to true). If the call fails for any + * reason, daemon_handle_valid(h) for the response will return false. Otherwise, + * the connection is good to start serving requests. + */ +daemon_handle daemon_open(daemon_info i); + +/* + * Send a request to the daemon, waiting for the reply. All communication with + * the daemon is synchronous. The function handles the IO details and parses the + * response, handling common error conditions. See "daemon_reply" for details. + * + * In case the request contains a non-NULL buffer pointer, this buffer is sent + * *verbatim* to the server. In this case, the cft pointer may be NULL (but will + * be ignored even if non-NULL). If the buffer is NULL, the cft is required to + * be a valid pointer, and is used to build up the request. + */ +daemon_reply daemon_send(daemon_handle h, daemon_request r); + +/* + * A simple interface to daemon_send. This function just takes the command id + * and possibly a list of parameters (of the form "name = %?", "value"). The + * type (string, integer) of the value is indicated by a character substituted + * for ? in %?: d for integer, s for string. + */ +daemon_reply daemon_send_simple(daemon_handle h, const char *id, ...); +daemon_reply daemon_send_simple_v(daemon_handle h, const char *id, va_list ap); + +daemon_request daemon_request_make(const char *id); +int daemon_request_extend(daemon_request r, ...); +int daemon_request_extend_v(daemon_request r, va_list ap); +void daemon_request_destroy(daemon_request r); + +void daemon_reply_destroy(daemon_reply r); + +static inline int64_t daemon_reply_int(daemon_reply r, const char *path, int64_t def) { + return dm_config_find_int64(r.cft->root, path, def); +} + +static inline const char *daemon_reply_str(daemon_reply r, const char *path, const char *def) { + return dm_config_find_str(r.cft->root, path, def); +} + + +/* Shut down the communication to the daemon. Compulsory. */ +void daemon_close(daemon_handle h); + +#endif diff --git a/libdaemon/client/daemon-io.c b/libdaemon/client/daemon-io.c new file mode 100644 index 0000000..37f2744 --- /dev/null +++ b/libdaemon/client/daemon-io.c @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "daemon-io.h" +#include "libdevmapper.h" + +/* + * Read a single message from a (socket) filedescriptor. Messages are delimited + * by blank lines. This call will block until all of a message is received. The + * memory will be allocated from heap. Upon error, all memory is freed and the + * buffer pointer is set to NULL. + * + * See also write_buffer about blocking (read_buffer has identical behaviour). + */ +int buffer_read(int fd, struct buffer *buffer) { + if (!buffer_realloc(buffer, 32)) /* ensure we have some space */ + goto fail; + + while (1) { + int result = read(fd, buffer->mem + buffer->used, buffer->allocated - buffer->used); + if (result > 0) { + buffer->used += result; + if (!strncmp((buffer->mem) + buffer->used - 4, "\n##\n", 4)) { + *(buffer->mem + buffer->used - 4) = 0; + buffer->used -= 4; + break; /* success, we have the full message now */ + } + if (buffer->used - buffer->allocated < 32) + if (!buffer_realloc(buffer, 1024)) + goto fail; + continue; + } + if (result == 0) { + errno = ECONNRESET; + goto fail; /* we should never encounter EOF here */ + } + if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) + goto fail; + /* TODO call select here if we encountered EAGAIN/EWOULDBLOCK/EINTR */ + } + return 1; +fail: + return 0; +} + +/* + * Write a buffer to a filedescriptor. Keep trying. Blocks (even on + * SOCK_NONBLOCK) until all of the write went through. + * + * TODO use select on EWOULDBLOCK/EAGAIN/EINTR to avoid useless spinning + */ +int buffer_write(int fd, struct buffer *buffer) { + struct buffer terminate = { .mem = (char *) "\n##\n", .used = 4 }; + int done = 0; + int written = 0; + struct buffer *use = buffer; +write: + while (1) { + int result = write(fd, use->mem + written, use->used - written); + if (result > 0) + written += result; + if (result < 0 && errno != EWOULDBLOCK && errno != EAGAIN && errno != EINTR) + return 0; /* too bad */ + if (written == use->used) { + if (done) + return 1; + else + break; /* done */ + } + } + + use = &terminate; + written = 0; + done = 1; + goto write; +} diff --git a/libdaemon/client/daemon-io.h b/libdaemon/client/daemon-io.h new file mode 100644 index 0000000..1f55af7 --- /dev/null +++ b/libdaemon/client/daemon-io.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LVM_DAEMON_IO_H +#define _LVM_DAEMON_IO_H + +#include "configure.h" +#include "config-util.h" +#include "libdevmapper.h" + +#define _REENTRANT +#define _GNU_SOURCE +#define _FILE_OFFSET_BITS 64 + +/* TODO function names */ + +int buffer_read(int fd, struct buffer *buffer); +int buffer_write(int fd, struct buffer *buffer); + +#endif /* _LVM_DAEMON_SHARED_H */ diff --git a/libdaemon/server/Makefile.in b/libdaemon/server/Makefile.in new file mode 100644 index 0000000..1c4a38e --- /dev/null +++ b/libdaemon/server/Makefile.in @@ -0,0 +1,22 @@ +# Copyright (C) 2011 Red Hat, Inc. All rights reserved. +# +# This file is part of the device-mapper userspace tools. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions +# of the GNU Lesser General Public License v.2.1. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +top_builddir = @top_builddir@ + +LIB_STATIC = libdaemonserver.a +SOURCES = daemon-server.c daemon-log.c + +include $(top_builddir)/make.tmpl + +LIBS += $(DAEMON_LIBS) diff --git a/libdaemon/server/daemon-log.c b/libdaemon/server/daemon-log.c new file mode 100644 index 0000000..6f5f4dc --- /dev/null +++ b/libdaemon/server/daemon-log.c @@ -0,0 +1,180 @@ +#include "daemon-server.h" +#include "daemon-log.h" +#include <syslog.h> +#include <assert.h> + +struct backend { + int id; + void (*log)(log_state *s, void **state, int type, const char *message); +}; + +static void log_syslog(log_state *s, void **state, int type, const char *message) +{ + int prio; + + if (!*state) { /* initialize */ + *state = (void *)1; + openlog(s->name, LOG_PID, LOG_DAEMON); + } + + switch (type) { + case DAEMON_LOG_INFO: prio = LOG_INFO; break; + case DAEMON_LOG_WARN: prio = LOG_WARNING; break; + case DAEMON_LOG_FATAL: prio = LOG_CRIT; break; + default: prio = LOG_DEBUG; break; + } + + syslog(prio, "%s", message); +} + +static void log_stderr(log_state *s, void **state, int type, const char *message) +{ + const char *prefix; + + switch (type) { + case DAEMON_LOG_INFO: prefix = "I: "; break; + case DAEMON_LOG_WARN: prefix = "W: " ; break; + case DAEMON_LOG_ERROR: /* fall through */ + case DAEMON_LOG_FATAL: prefix = "E: " ; break; + default: prefix = ""; break; + } + + fprintf(stderr, "%s%s\n", prefix, message); +} + +struct backend backend[] = { + { DAEMON_LOG_OUTLET_SYSLOG, log_syslog }, + { DAEMON_LOG_OUTLET_STDERR, log_stderr }, + { 0, 0 } +}; + +void daemon_log(log_state *s, int type, const char *message) { + int i = 0; + while ( backend[i].id ) { + if ( (s->log_config[type] & backend[i].id) == backend[i].id ) + backend[i].log( s, &s->backend_state[i], type, message ); + ++ i; + } +} + +static int _type_interesting(log_state *s, int type) { + int i = 0; + while ( backend[i].id ) { + if ( (s->log_config[type] & backend[i].id) == backend[i].id ) + return 1; + ++ i; + } + return 0; +} + +void daemon_logf(log_state *s, int type, const char *fmt, ...) { + char *buf; + va_list ap; + + va_start(ap, fmt); + if (dm_vasprintf(&buf, fmt, ap) >= 0) { + daemon_log(s, type, buf); + dm_free(buf); + } /* else return_0 */ + va_end(ap); +} + +struct log_line_baton { + log_state *s; + int type; + const char *prefix; +}; + +static int _log_line(const char *line, void *baton) { + struct log_line_baton *b = baton; + daemon_logf(b->s, b->type, "%s%s", b->prefix, line); + return 0; +} + +void daemon_log_cft(log_state *s, int type, const char *prefix, const struct dm_config_node *n) +{ + struct log_line_baton b = { .s = s, .type = type, .prefix = prefix }; + + if (!_type_interesting(s, type)) + return; + + dm_config_write_node(n, &_log_line, &b); +} + +void daemon_log_multi(log_state *s, int type, const char *prefix, const char *msg) +{ + struct log_line_baton b = { .s = s, .type = type, .prefix = prefix }; + char *buf; + char *pos; + + if (!_type_interesting(s, type)) + return; + + buf = dm_strdup(msg); + pos = buf; + + if (!buf) + return; /* _0 */ + + while (pos) { + char *next = strchr(pos, '\n'); + if (next) + *next = 0; + _log_line(pos, &b); + pos = next ? next + 1 : 0; + } + dm_free(buf); +} + +void daemon_log_enable(log_state *s, int outlet, int type, int enable) +{ + assert(type < 32); + if (enable) + s->log_config[type] |= outlet; + else + s->log_config[type] &= ~outlet; +} + +static int _parse_one(log_state *s, int outlet, const char *type, int enable) +{ + int i; + if (!strcmp(type, "all")) + for (i = 0; i < 32; ++i) + daemon_log_enable(s, outlet, i, enable); + else if (!strcmp(type, "wire")) + daemon_log_enable(s, outlet, DAEMON_LOG_WIRE, enable); + else if (!strcmp(type, "debug")) + daemon_log_enable(s, outlet, DAEMON_LOG_DEBUG, enable); + else + return 0; + + return 1; +} + +int daemon_log_parse(log_state *s, int outlet, const char *types, int enable) +{ + char *buf; + char *pos; + + if (!types || !types[0]) + return 1; + + if (!(buf = dm_strdup(types))) + return 0; + + pos = buf; + while (pos) { + char *next = strchr(pos, ','); + if (next) + *next = 0; + if (!_parse_one(s, outlet, pos, enable)) { + dm_free(buf); + return 0; + } + pos = next ? next + 1 : 0; + } + + dm_free(buf); + + return 1; +} diff --git a/libdaemon/server/daemon-log.h b/libdaemon/server/daemon-log.h new file mode 100644 index 0000000..e2e11c5 --- /dev/null +++ b/libdaemon/server/daemon-log.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LVM_DAEMON_LOG_H +#define _LVM_DAEMON_LOG_H + +enum { DAEMON_LOG_FATAL = 0 /* usually preceding daemon death */ + , DAEMON_LOG_ERROR = 1 /* something serious has happened */ + , DAEMON_LOG_WARN = 2 /* something unusual has happened */ + , DAEMON_LOG_INFO = 3 /* thought you might be interested */ + , DAEMON_LOG_WIRE = 4 /* dump traffic on client sockets */ + , DAEMON_LOG_DEBUG = 5 /* unsorted debug stuff */ +}; + +#define DEBUGLOG(s, x...) daemon_logf((s)->log, DAEMON_LOG_DEBUG, x) +#define DEBUGLOG_cft(s, i, n) daemon_log_cft((s)->log, DAEMON_LOG_DEBUG, i, n) +#define WARN(s, x...) daemon_logf((s)->log, DAEMON_LOG_WARN, x) +#define INFO(s, x...) daemon_logf((s)->log, DAEMON_LOG_INFO, x) +#define ERROR(s, x...) daemon_logf((s)->log, DAEMON_LOG_ERROR, x) +#define FATAL(s, x...) daemon_logf((s)->log, DAEMON_LOG_FATAL, x) + +#endif diff --git a/libdaemon/server/daemon-server.c b/libdaemon/server/daemon-server.c new file mode 100644 index 0000000..3711419 --- /dev/null +++ b/libdaemon/server/daemon-server.c @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "daemon-io.h" +#include "config-util.h" +#include "daemon-server.h" +#include "daemon-log.h" + +#include <dlfcn.h> +#include <errno.h> +#include <pthread.h> +#include <sys/file.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <unistd.h> +#include <signal.h> + +#include <syslog.h> /* FIXME. For the global closelog(). */ + +#if 0 +/* Create a device monitoring thread. */ +static int _pthread_create(pthread_t *t, void *(*fun)(void *), void *arg, int stacksize) +{ + pthread_attr_t attr; + pthread_attr_init(&attr); + /* + * We use a smaller stack since it gets preallocated in its entirety + */ + pthread_attr_setstacksize(&attr, stacksize); + return pthread_create(t, &attr, fun, arg); +} +#endif + +static volatile sig_atomic_t _shutdown_requested = 0; +static int _systemd_activation = 0; + +static void _exit_handler(int sig __attribute__((unused))) +{ + _shutdown_requested = 1; +} + +#ifdef linux + +#include <stddef.h> + +/* + * Kernel version 2.6.36 and higher has + * new OOM killer adjustment interface. + */ +# define OOM_ADJ_FILE_OLD "/proc/self/oom_adj" +# define OOM_ADJ_FILE "/proc/self/oom_score_adj" + +/* From linux/oom.h */ +/* Old interface */ +# define OOM_DISABLE (-17) +# define OOM_ADJUST_MIN (-16) +/* New interface */ +# define OOM_SCORE_ADJ_MIN (-1000) + +/* Systemd on-demand activation support */ +# define SD_ACTIVATION_ENV_VAR_NAME "SD_ACTIVATION" +# define SD_LISTEN_PID_ENV_VAR_NAME "LISTEN_PID" +# define SD_LISTEN_FDS_ENV_VAR_NAME "LISTEN_FDS" +# define SD_LISTEN_FDS_START 3 +# define SD_FD_SOCKET_SERVER SD_LISTEN_FDS_START + +# include <stdio.h> + +static int _set_oom_adj(const char *oom_adj_path, int val) +{ + FILE *fp; + + if (!(fp = fopen(oom_adj_path, "w"))) { + perror("oom_adj: fopen failed"); + return 0; + } + + fprintf(fp, "%i", val); + + if (dm_fclose(fp)) + perror("oom_adj: fclose failed"); + + return 1; +} + +/* + * Protection against OOM killer if kernel supports it + */ +static int _protect_against_oom_killer(void) +{ + struct stat st; + + if (stat(OOM_ADJ_FILE, &st) == -1) { + if (errno != ENOENT) + perror(OOM_ADJ_FILE ": stat failed"); + + /* Try old oom_adj interface as a fallback */ + if (stat(OOM_ADJ_FILE_OLD, &st) == -1) { + if (errno == ENOENT) + perror(OOM_ADJ_FILE_OLD " not found"); + else + perror(OOM_ADJ_FILE_OLD ": stat failed"); + return 1; + } + + return _set_oom_adj(OOM_ADJ_FILE_OLD, OOM_DISABLE) || + _set_oom_adj(OOM_ADJ_FILE_OLD, OOM_ADJUST_MIN); + } + + return _set_oom_adj(OOM_ADJ_FILE, OOM_SCORE_ADJ_MIN); +} + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_un un; +}; + +static int _handle_preloaded_socket(int fd, const char *path) +{ + struct stat st_fd; + union sockaddr_union sockaddr = { .sa.sa_family = 0 }; + int type = 0; + socklen_t len = sizeof(type); + size_t path_len = strlen(path); + + if (fd < 0) + return 0; + + if (fstat(fd, &st_fd) < 0 || !S_ISSOCK(st_fd.st_mode)) + return 0; + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &len) < 0 || + len != sizeof(type) || type != SOCK_STREAM) + return 0; + + len = sizeof(sockaddr); + if (getsockname(fd, &sockaddr.sa, &len) < 0 || + len < sizeof(sa_family_t) || + sockaddr.sa.sa_family != PF_UNIX) + return 0; + + if (!(len >= offsetof(struct sockaddr_un, sun_path) + path_len + 1 && + memcmp(path, sockaddr.un.sun_path, path_len) == 0)) + return 0; + + return 1; +} + +static int _systemd_handover(struct daemon_state *ds) +{ + const char *e; + char *p; + unsigned long env_pid, env_listen_fds; + int r = 0; + + /* SD_ACTIVATION must be set! */ + if (!(e = getenv(SD_ACTIVATION_ENV_VAR_NAME)) || strcmp(e, "1")) + goto out; + + /* LISTEN_PID must be equal to our PID! */ + if (!(e = getenv(SD_LISTEN_PID_ENV_VAR_NAME))) + goto out; + + errno = 0; + env_pid = strtoul(e, &p, 10); + if (errno || !p || *p || env_pid <= 0 || + getpid() != (pid_t) env_pid) + goto out; + + /* LISTEN_FDS must be 1 and the fd must be a socket! */ + if (!(e = getenv(SD_LISTEN_FDS_ENV_VAR_NAME))) + goto out; + + errno = 0; + env_listen_fds = strtoul(e, &p, 10); + if (errno || !p || *p || env_listen_fds != 1) + goto out; + + /* Check and handle the socket passed in */ + if ((r = _handle_preloaded_socket(SD_FD_SOCKET_SERVER, ds->socket_path))) + ds->socket_fd = SD_FD_SOCKET_SERVER; + +out: + unsetenv(SD_ACTIVATION_ENV_VAR_NAME); + unsetenv(SD_LISTEN_PID_ENV_VAR_NAME); + unsetenv(SD_LISTEN_FDS_ENV_VAR_NAME); + return r; +} + +#endif + +static int _open_socket(daemon_state s) +{ + int fd = -1; + struct sockaddr_un sockaddr = { .sun_family = AF_UNIX }; + mode_t old_mask; + + (void) dm_prepare_selinux_context(s.socket_path, S_IFSOCK); + old_mask = umask(0077); + + /* Open local socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + perror("Can't create local socket."); + goto error; + } + + /* Set Close-on-exec & non-blocking */ + if (fcntl(fd, F_SETFD, 1)) + fprintf(stderr, "setting CLOEXEC on socket fd %d failed: %s\n", fd, strerror(errno)); + if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK)) + fprintf(stderr, "setting O_NONBLOCK on socket fd %d failed: %s\n", fd, strerror(errno)); + + fprintf(stderr, "[D] creating %s\n", s.socket_path); + if (!dm_strncpy(sockaddr.sun_path, s.socket_path, sizeof(sockaddr.sun_path))) { + fprintf(stderr, "%s: daemon socket path too long.\n", s.socket_path); + goto error; + } + + if (bind(fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr))) { + perror("can't bind local socket."); + goto error; + } + if (listen(fd, 1) != 0) { + perror("listen local"); + goto error; + } + +out: + umask(old_mask); + (void) dm_prepare_selinux_context(NULL, 0); + return fd; + +error: + if (fd >= 0) { + if (close(fd)) + perror("close failed"); + if (unlink(s.socket_path)) + perror("unlink failed"); + fd = -1; + } + goto out; +} + +static void remove_lockfile(const char *file) +{ + if (unlink(file)) + perror("unlink failed"); +} + +static void _daemonise(void) +{ + int child_status; + int fd; + pid_t pid; + struct rlimit rlim; + struct timeval tval; + sigset_t my_sigset; + + sigemptyset(&my_sigset); + if (sigprocmask(SIG_SETMASK, &my_sigset, NULL) < 0) { + fprintf(stderr, "Unable to restore signals.\n"); + exit(EXIT_FAILURE); + } + signal(SIGTERM, &_exit_handler); + + switch (pid = fork()) { + case -1: + perror("fork failed:"); + exit(EXIT_FAILURE); + + case 0: /* Child */ + break; + + default: + /* Wait for response from child */ + while (!waitpid(pid, &child_status, WNOHANG) && !_shutdown_requested) { + tval.tv_sec = 0; + tval.tv_usec = 250000; /* .25 sec */ + select(0, NULL, NULL, NULL, &tval); + } + + if (_shutdown_requested) /* Child has signaled it is ok - we can exit now */ + exit(0); + + /* Problem with child. Determine what it is by exit code */ + fprintf(stderr, "Child exited with code %d\n", WEXITSTATUS(child_status)); + exit(WEXITSTATUS(child_status)); + } + + if (chdir("/")) + exit(1); + + if (getrlimit(RLIMIT_NOFILE, &rlim) < 0) + fd = 256; /* just have to guess */ + else + fd = rlim.rlim_cur; + + for (--fd; fd >= 0; fd--) { +#ifdef linux + /* Do not close fds preloaded by systemd! */ + if (_systemd_activation && fd == SD_FD_SOCKET_SERVER) + continue; +#endif + (void) close(fd); + } + + if ((open("/dev/null", O_RDONLY) < 0) || + (open("/dev/null", O_WRONLY) < 0) || + (open("/dev/null", O_WRONLY) < 0)) + exit(1); + + setsid(); +} + +response daemon_reply_simple(const char *id, ...) +{ + va_list ap; + response res = { .cft = NULL }; + + va_start(ap, id); + + buffer_init(&res.buffer); + if (!buffer_append_f(&res.buffer, "response = %s", id, NULL)) { + res.error = ENOMEM; + goto end; + } + if (!buffer_append_vf(&res.buffer, ap)) { + res.error = ENOMEM; + goto end; + } + +end: + va_end(ap); + return res; +} + +struct thread_baton { + daemon_state s; + client_handle client; +}; + +static response builtin_handler(daemon_state s, client_handle h, request r) +{ + const char *rq = daemon_request_str(r, "request", "NONE"); + response res = { .error = EPROTO }; + + if (!strcmp(rq, "hello")) { + return daemon_reply_simple("OK", "protocol = %s", s.protocol ?: "default", + "version = %" PRId64, (int64_t) s.protocol_version, NULL); + } + + buffer_init(&res.buffer); + return res; +} + +static void *client_thread(void *baton) +{ + struct thread_baton *b = baton; + request req; + response res; + + buffer_init(&req.buffer); + + while (1) { + if (!buffer_read(b->client.socket_fd, &req.buffer)) + goto fail; + + req.cft = dm_config_from_string(req.buffer.mem); + + if (!req.cft) + fprintf(stderr, "error parsing request:\n %s\n", req.buffer.mem); + else + daemon_log_cft(b->s.log, DAEMON_LOG_WIRE, "<- ", req.cft->root); + + res = builtin_handler(b->s, b->client, req); + + if (res.error == EPROTO) /* Not a builtin, delegate to the custom handler. */ + res = b->s.handler(b->s, b->client, req); + + if (!res.buffer.mem) { + dm_config_write_node(res.cft->root, buffer_line, &res.buffer); + if (!buffer_append(&res.buffer, "\n\n")) + goto fail; + dm_config_destroy(res.cft); + } + + if (req.cft) + dm_config_destroy(req.cft); + buffer_destroy(&req.buffer); + + daemon_log_multi(b->s.log, DAEMON_LOG_WIRE, "-> ", res.buffer.mem); + buffer_write(b->client.socket_fd, &res.buffer); + + buffer_destroy(&res.buffer); + } +fail: + /* TODO what should we really do here? */ + if (close(b->client.socket_fd)) + perror("close"); + buffer_destroy(&req.buffer); + dm_free(baton); + return NULL; +} + +static int handle_connect(daemon_state s) +{ + struct thread_baton *baton; + struct sockaddr_un sockaddr; + client_handle client = { .thread_id = 0 }; + socklen_t sl = sizeof(sockaddr); + + client.socket_fd = accept(s.socket_fd, (struct sockaddr *) &sockaddr, &sl); + if (client.socket_fd < 0) + return 0; + + if (!(baton = malloc(sizeof(struct thread_baton)))) + return 0; + + baton->s = s; + baton->client = client; + + if (pthread_create(&baton->client.thread_id, NULL, client_thread, baton)) + return 0; + + pthread_detach(baton->client.thread_id); + + return 1; +} + +void daemon_start(daemon_state s) +{ + int failed = 0; + log_state _log = { { 0 } }; + + /* + * Switch to C locale to avoid reading large locale-archive file used by + * some glibc (on some distributions it takes over 100MB). Some daemons + * need to use mlockall(). + */ + if (setenv("LANG", "C", 1)) + perror("Cannot set LANG to C"); + +#ifdef linux + _systemd_activation = _systemd_handover(&s); +#endif + + if (!s.foreground) + _daemonise(); + + s.log = &_log; + s.log->name = s.name; + + /* Log important things to syslog by default. */ + daemon_log_enable(s.log, DAEMON_LOG_OUTLET_SYSLOG, DAEMON_LOG_FATAL, 1); + daemon_log_enable(s.log, DAEMON_LOG_OUTLET_SYSLOG, DAEMON_LOG_ERROR, 1); + + if (s.pidfile) { + (void) dm_prepare_selinux_context(s.pidfile, S_IFREG); + + /* + * NB. Take care to not keep stale locks around. Best not exit(...) + * after this point. + */ + if (dm_create_lockfile(s.pidfile) == 0) + exit(1); + + (void) dm_prepare_selinux_context(NULL, 0); + } + + /* Set normal exit signals to request shutdown instead of dying. */ + signal(SIGINT, &_exit_handler); + signal(SIGHUP, &_exit_handler); + signal(SIGQUIT, &_exit_handler); + signal(SIGTERM, &_exit_handler); + signal(SIGALRM, &_exit_handler); + signal(SIGPIPE, SIG_IGN); + +#ifdef linux + /* Systemd has adjusted oom killer for us already */ + if (s.avoid_oom && !_systemd_activation && !_protect_against_oom_killer()) + ERROR(&s, "Failed to protect against OOM killer"); +#endif + + if (!_systemd_activation && s.socket_path) { + s.socket_fd = _open_socket(s); + if (s.socket_fd < 0) + failed = 1; + } + + /* Signal parent, letting them know we are ready to go. */ + if (!s.foreground) + kill(getppid(), SIGTERM); + + if (s.daemon_init) + if (!s.daemon_init(&s)) + failed = 1; + + while (!_shutdown_requested && !failed) { + fd_set in; + FD_ZERO(&in); + FD_SET(s.socket_fd, &in); + if (select(FD_SETSIZE, &in, NULL, NULL, NULL) < 0 && errno != EINTR) + perror("select error"); + if (FD_ISSET(s.socket_fd, &in)) + if (!_shutdown_requested && !handle_connect(s)) + ERROR(&s, "Failed to handle a client connection."); + } + + /* If activated by systemd, do not unlink the socket - systemd takes care of that! */ + if (!_systemd_activation && s.socket_fd >= 0) + if (unlink(s.socket_path)) + perror("unlink error"); + + if (s.daemon_fini) + if (!s.daemon_fini(&s)) + failed = 1; + + INFO(&s, "%s shutting down", s.name); + + closelog(); /* FIXME */ + if (s.pidfile) + remove_lockfile(s.pidfile); + if (failed) + exit(1); +} diff --git a/libdaemon/server/daemon-server.h b/libdaemon/server/daemon-server.h new file mode 100644 index 0000000..f184853 --- /dev/null +++ b/libdaemon/server/daemon-server.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This file is part of LVM2. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU Lesser General Public License v.2.1. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _LVM_DAEMON_COMMON_SERVER_H +#define _LVM_DAEMON_COMMON_SERVER_H + +#include "daemon-client.h" + +typedef struct { + int socket_fd; /* the fd we use to talk to the client */ + pthread_t thread_id; + char *read_buf; + void *private; /* this holds per-client state */ +} client_handle; + +typedef struct { + struct dm_config_tree *cft; + struct buffer buffer; +} request; + +typedef struct { + int error; + struct dm_config_tree *cft; + struct buffer buffer; +} response; + +struct daemon_state; + +/* + * Craft a simple reply, without the need to construct a config_tree. See + * daemon_send_simple in daemon-client.h for the description of the parameters. + */ +response daemon_reply_simple(const char *id, ...); + +static inline int daemon_request_int(request r, const char *path, int def) { + if (!r.cft) + return def; + return dm_config_find_int(r.cft->root, path, def); +} + +static inline const char *daemon_request_str(request r, const char *path, const char *def) { + if (!r.cft) + return def; + return dm_config_find_str(r.cft->root, path, def); +} + +/* + * The callback. Called once per request issued, in the respective client's + * thread. It is presented by a parsed request (in the form of a config tree). + * The output is a new config tree that is serialised and sent back to the + * client. The client blocks until the request processing is done and reply is + * sent. + */ +typedef response (*handle_request)(struct daemon_state s, client_handle h, request r); + +typedef struct { + uint32_t log_config[32]; + void *backend_state[32]; + const char *name; +} log_state; + +typedef struct daemon_state { + /* + * The maximal stack size for individual daemon threads. This is + * essential for daemons that need to be locked into memory, since + * pthread's default is 10M per thread. + */ + int thread_stack_size; + + /* Flags & attributes affecting the behaviour of the daemon. */ + unsigned avoid_oom:1; + unsigned foreground:1; + const char *name; + const char *pidfile; + const char *socket_path; + const char *protocol; + int protocol_version; + + handle_request handler; + int (*daemon_init)(struct daemon_state *st); + int (*daemon_fini)(struct daemon_state *st); + + /* Global runtime info maintained by the framework. */ + int socket_fd; + + log_state *log; + void *private; /* the global daemon state */ +} daemon_state; + +/* + * Start serving the requests. This does all the daemonisation, socket setup + * work and so on. This function takes over the process, and upon failure, it + * will terminate execution. It may be called at most once. + */ +void daemon_start(daemon_state s); + +/* + * Take over from an already running daemon. This function handles connecting + * to the running daemon and telling it we are going to take over. The takeover + * request may be customised by passing in a non-NULL request. + * + * The takeover sequence: the old daemon stops accepting new clients, then it + * waits until all current client connections are closed. When that happens, it + * serializes its current state and sends that as a reply, which is then + * returned by this function (therefore, this function won't return until the + * previous instance has shut down). + * + * The daemon, after calling daemon_takeover is expected to set up its + * daemon_state using the reply from this function and call daemon_start as + * usual. + */ +daemon_reply daemon_takeover(daemon_info i, daemon_request r); + +/* Call this to request a clean shutdown of the daemon. Async safe. */ +void daemon_stop(void); + +enum { DAEMON_LOG_OUTLET_SYSLOG = 1, + DAEMON_LOG_OUTLET_STDERR = 2, + DAEMON_LOG_OUTLET_SOCKET = 4 }; + +/* Log a message of a given type. */ +void daemon_log(log_state *s, int type, const char *message); + +/* Log a config (sub)tree, using a given message type, each line prefixed with "prefix". */ +void daemon_log_cft(log_state *s, int type, const char *prefix, + const struct dm_config_node *n); + +/* Log a multi-line block, prefixing each line with "prefix". */ +void daemon_log_multi(log_state *s, int type, const char *prefix, const char *message); + +/* Log a formatted message as "type". See also daemon-log.h. */ +void daemon_logf(log_state *s, int type, const char *format, ...); + +/* + * Configure log_state to send messages of type "type" to the log outlet + * "outlet", iff "enable" is true. + */ +void daemon_log_enable(log_state *s, int outlet, int type, int enable); + +/* + * Set up logging on a given outlet using a list of message types (comma + * separated) to log using that outlet. The list is expected to look like this, + * "all,wire,debug". Returns 0 upon encountering an unknown message type. + */ +int daemon_log_parse(log_state *s, int outlet, const char *types, int enable); + +#endif |