1
0
mirror of git://sourceware.org/git/lvm2.git synced 2024-12-21 13:34:40 +03:00
lvm2/daemons/dmeventd/dmeventd.c
Alasdair G Kergon 160777bb3e dmeventd: Add basic thread debugging messages.
Only with -DDEBUG.
2014-11-04 18:56:20 +00:00

2257 lines
54 KiB
C

/*
* Copyright (C) 2005-2007 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
*/
/*
* dmeventd - dm event daemon to monitor active mapped devices
*/
#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64
#include "configure.h"
#include "libdevmapper.h"
#include "libdevmapper-event.h"
#include "dmeventd.h"
//#include "libmultilog.h"
#include "dm-logging.h"
#include <stdarg.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 <unistd.h>
#include <signal.h>
#include <arpa/inet.h> /* for htonl, ntohl */
#include <fcntl.h> /* for musl libc */
#ifdef __linux__
/*
* 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_RUNTIME_UNIT_FILE_DIR DEFAULT_DM_RUN_DIR "/systemd/system/"
# 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_FIFO_SERVER SD_LISTEN_FDS_START
# define SD_FD_FIFO_CLIENT (SD_LISTEN_FDS_START + 1)
#endif
/* FIXME We use syslog for now, because multilog is not yet implemented */
#include <syslog.h>
static volatile sig_atomic_t _exit_now = 0; /* set to '1' when signal is given to exit */
/* List (un)link macros. */
#define LINK(x, head) dm_list_add(head, &(x)->list)
#define LINK_DSO(dso) LINK(dso, &_dso_registry)
#define LINK_THREAD(thread) LINK(thread, &_thread_registry)
#define UNLINK(x) dm_list_del(&(x)->list)
#define UNLINK_DSO(x) UNLINK(x)
#define UNLINK_THREAD(x) UNLINK(x)
#define DAEMON_NAME "dmeventd"
/*
Global mutex for thread list access. Has to be held when:
- iterating thread list
- adding or removing elements from thread list
- changing or reading thread_status's fields:
processing, status, events
Use _lock_mutex() and _unlock_mutex() to hold/release it
*/
static pthread_mutex_t _global_mutex;
/*
There are three states a thread can attain (see struct
thread_status, field int status):
- DM_THREAD_RUNNING: thread has started up and is either working or
waiting for events... transitions to either SHUTDOWN or DONE
- DM_THREAD_SHUTDOWN: thread is still doing something, but it is
supposed to terminate (and transition to DONE) as soon as it
finishes whatever it was doing at the point of flipping state to
SHUTDOWN... the thread is still on the thread list
- DM_THREAD_DONE: thread has terminated and has been moved over to
unused thread list, cleanup pending
*/
#define DM_THREAD_RUNNING 0
#define DM_THREAD_SHUTDOWN 1
#define DM_THREAD_DONE 2
#define THREAD_STACK_SIZE (300*1024)
int dmeventd_debug = 0;
static int _systemd_activation = 0;
static int _foreground = 0;
static int _restart = 0;
static char **_initial_registrations = 0;
/* FIXME Make configurable at runtime */
#ifdef DEBUG
# define DEBUGLOG(fmt, args...) debuglog("[Thr %x]: " fmt, (int)pthread_self(), ## args)
void debuglog(const char *fmt, ... ) __attribute__ ((format(printf, 1, 2)));
void debuglog(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog(LOG_DEBUG, fmt, ap);
va_end(ap);
}
static const char *decode_cmd(uint32_t cmd)
{
static char buf[128];
const char *command;
switch (cmd) {
case DM_EVENT_CMD_ACTIVE:
command = "ACTIVE";
break;
case DM_EVENT_CMD_REGISTER_FOR_EVENT:
command = "REGISTER_FOR_EVENT";
break;
case DM_EVENT_CMD_UNREGISTER_FOR_EVENT:
command = "UNREGISTER_FOR_EVENT";
break;
case DM_EVENT_CMD_GET_REGISTERED_DEVICE:
command = "GET_REGISTERED_DEVICE";
break;
case DM_EVENT_CMD_GET_NEXT_REGISTERED_DEVICE:
command = "GET_NEXT_REGISTERED_DEVICE";
break;
case DM_EVENT_CMD_SET_TIMEOUT:
command = "SET_TIMEOUT";
break;
case DM_EVENT_CMD_GET_TIMEOUT:
command = "GET_TIMEOUT";
break;
case DM_EVENT_CMD_HELLO:
command = "HELLO";
break;
case DM_EVENT_CMD_DIE:
command = "DIE";
break;
case DM_EVENT_CMD_GET_STATUS:
command = "GET_STATUS";
break;
case DM_EVENT_CMD_GET_PARAMETERS:
command = "GET_PARAMETERS";
break;
default:
command = "unknown";
break;
}
snprintf(buf, sizeof(buf), "%s (0x%x)", command, cmd);
return buf;
}
#else
# define DEBUGLOG(fmt, args...) do { } while (0)
#endif
/* Data kept about a DSO. */
struct dso_data {
struct dm_list list;
char *dso_name; /* DSO name (eg, "evms", "dmraid", "lvm2"). */
void *dso_handle; /* Opaque handle as returned from dlopen(). */
unsigned int ref_count; /* Library reference count. */
/*
* Event processing.
*
* The DSO can do whatever appropriate steps if an event
* happens such as changing the mapping in case a mirror
* fails, update the application metadata etc.
*
* This function gets a dm_task that is a result of
* DM_DEVICE_WAITEVENT ioctl (results equivalent to
* DM_DEVICE_STATUS). It should not destroy it.
* The caller must dispose of the task.
*/
void (*process_event)(struct dm_task *dmt, enum dm_event_mask event, void **user);
/*
* Device registration.
*
* When an application registers a device for an event, the DSO
* can carry out appropriate steps so that a later call to
* the process_event() function is sane (eg, read metadata
* and activate a mapping).
*/
int (*register_device)(const char *device, const char *uuid, int major,
int minor, void **user);
/*
* Device unregistration.
*
* In case all devices of a mapping (eg, RAID10) are unregistered
* for events, the DSO can recognize this and carry out appropriate
* steps (eg, deactivate mapping, metadata update).
*/
int (*unregister_device)(const char *device, const char *uuid,
int major, int minor, void **user);
};
static DM_LIST_INIT(_dso_registry);
/* Structure to keep parsed register variables from client message. */
struct message_data {
char *id;
char *dso_name; /* Name of DSO. */
char *device_uuid; /* Mapped device path. */
char *events_str; /* Events string as fetched from message. */
enum dm_event_mask events_field; /* Events bitfield. */
char *timeout_str;
uint32_t timeout_secs;
struct dm_event_daemon_message *msg; /* Pointer to message buffer. */
};
/*
* Housekeeping of thread+device states.
*
* One thread per mapped device which can block on it until an event
* occurs and the event processing function of the DSO gets called.
*/
struct thread_status {
struct dm_list list;
pthread_t thread;
struct dso_data *dso_data; /* DSO this thread accesses. */
struct {
char *uuid;
char *name;
int major, minor;
} device;
uint32_t event_nr; /* event number */
int processing; /* Set when event is being processed */
int status; /* see DM_THREAD_{RUNNING,SHUTDOWN,DONE}
constants above */
enum dm_event_mask events; /* bitfield for event filter. */
enum dm_event_mask current_events; /* bitfield for occured events. */
struct dm_task *current_task;
time_t next_time;
uint32_t timeout;
struct dm_list timeout_list;
void *dso_private; /* dso per-thread status variable */
};
static DM_LIST_INIT(_thread_registry);
static DM_LIST_INIT(_thread_registry_unused);
static int _timeout_running;
static DM_LIST_INIT(_timeout_registry);
static pthread_mutex_t _timeout_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t _timeout_cond = PTHREAD_COND_INITIALIZER;
/* Allocate/free the status structure for a monitoring thread. */
static struct thread_status *_alloc_thread_status(const struct message_data *data,
struct dso_data *dso_data)
{
struct thread_status *ret;
if (!(ret = dm_zalloc(sizeof(*ret))))
return NULL;
if (!(ret->device.uuid = dm_strdup(data->device_uuid))) {
dm_free(ret);
return NULL;
}
ret->dso_data = dso_data;
ret->events = data->events_field;
ret->timeout = data->timeout_secs;
dm_list_init(&ret->timeout_list);
return ret;
}
static void _lib_put(struct dso_data *data);
static void _free_thread_status(struct thread_status *thread)
{
_lib_put(thread->dso_data);
if (thread->current_task)
dm_task_destroy(thread->current_task);
dm_free(thread->device.uuid);
dm_free(thread->device.name);
dm_free(thread);
}
/* Allocate/free DSO data. */
static struct dso_data *_alloc_dso_data(struct message_data *data)
{
struct dso_data *ret = (typeof(ret)) dm_zalloc(sizeof(*ret));
if (!ret)
return NULL;
if (!(ret->dso_name = dm_strdup(data->dso_name))) {
dm_free(ret);
return NULL;
}
return ret;
}
/*
* Create a device monitoring thread.
* N.B. Error codes returned are positive.
*/
static int _pthread_create_smallstack(pthread_t *t, void *(*fun)(void *), void *arg)
{
int r;
pthread_t tmp;
pthread_attr_t attr;
/*
* From pthread_attr_init man page:
* POSIX.1-2001 documents an ENOMEM error for pthread_attr_init(); on
* Linux these functions always succeed (but portable and future-proof
* applications should nevertheless handle a possible error return).
*/
if ((r = pthread_attr_init(&attr)) != 0)
return r;
/*
* We use a smaller stack since it gets preallocated in its entirety
*/
pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE);
/*
* If no-one will be waiting, we need to detach.
*/
if (!t) {
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
t = &tmp;
}
r = pthread_create(t, &attr, fun, arg);
pthread_attr_destroy(&attr);
return r;
}
static void _free_dso_data(struct dso_data *data)
{
dm_free(data->dso_name);
dm_free(data);
}
/*
* Fetch a string off src and duplicate it into *ptr.
* Pay attention to zero-length strings.
*/
/* FIXME? move to libdevmapper to share with the client lib (need to
make delimiter a parameter then) */
static int _fetch_string(char **ptr, char **src, const int delimiter)
{
int ret = 0;
char *p;
size_t len;
if ((p = strchr(*src, delimiter)))
*p = 0;
if ((*ptr = dm_strdup(*src))) {
if ((len = strlen(*ptr)))
*src += len;
else {
dm_free(*ptr);
*ptr = NULL;
}
(*src)++;
ret = 1;
}
if (p)
*p = delimiter;
return ret;
}
/* Free message memory. */
static void _free_message(struct message_data *message_data)
{
dm_free(message_data->id);
dm_free(message_data->dso_name);
dm_free(message_data->device_uuid);
dm_free(message_data->events_str);
dm_free(message_data->timeout_str);
}
/* Parse a register message from the client. */
static int _parse_message(struct message_data *message_data)
{
int ret = 0;
struct dm_event_daemon_message *msg = message_data->msg;
char *p = msg->data;
if (!msg->data)
return 0;
/*
* Retrieve application identifier, mapped device
* path and events # string from message.
*/
if (_fetch_string(&message_data->id, &p, ' ') &&
_fetch_string(&message_data->dso_name, &p, ' ') &&
_fetch_string(&message_data->device_uuid, &p, ' ') &&
_fetch_string(&message_data->events_str, &p, ' ') &&
_fetch_string(&message_data->timeout_str, &p, ' ')) {
if (message_data->events_str)
message_data->events_field =
atoi(message_data->events_str);
if (message_data->timeout_str)
message_data->timeout_secs =
atoi(message_data->timeout_str)
? : DM_EVENT_DEFAULT_TIMEOUT;
ret = 1;
}
dm_free(msg->data);
msg->data = NULL;
return ret;
}
/* Global mutex to lock access to lists et al. See _global_mutex
above. */
static int _lock_mutex(void)
{
return pthread_mutex_lock(&_global_mutex);
}
static int _unlock_mutex(void)
{
return pthread_mutex_unlock(&_global_mutex);
}
/* Check, if a device exists. */
static int _fill_device_data(struct thread_status *ts)
{
struct dm_task *dmt;
struct dm_info dmi;
int ret = 0;
if (!ts->device.uuid)
return 0;
if (!(dmt = dm_task_create(DM_DEVICE_INFO)))
return 0;
if (!dm_task_set_uuid(dmt, ts->device.uuid))
goto fail;
if (!dm_task_run(dmt))
goto fail;
dm_free(ts->device.name);
if (!(ts->device.name = dm_strdup(dm_task_get_name(dmt))))
goto fail;
if (!dm_task_get_info(dmt, &dmi))
goto fail;
ts->device.major = dmi.major;
ts->device.minor = dmi.minor;
ret = 1;
fail:
dm_task_destroy(dmt);
return ret;
}
/*
* Find an existing thread for a device.
*
* Mutex must be held when calling this.
*/
static struct thread_status *_lookup_thread_status(struct message_data *data)
{
struct thread_status *thread;
dm_list_iterate_items(thread, &_thread_registry)
if (!strcmp(data->device_uuid, thread->device.uuid))
return thread;
return NULL;
}
static int _get_status(struct message_data *message_data)
{
struct dm_event_daemon_message *msg = message_data->msg;
struct thread_status *thread;
int i = 0, j;
int ret = -ENOMEM;
int count;
int size = 0, current;
size_t len;
char **buffers;
char *message;
_lock_mutex();
count = dm_list_size(&_thread_registry);
buffers = alloca(sizeof(char*) * count);
dm_list_iterate_items(thread, &_thread_registry) {
if ((current = dm_asprintf(buffers + i, "0:%d %s %s %u %" PRIu32 ";",
i, thread->dso_data->dso_name,
thread->device.uuid, thread->events,
thread->timeout)) < 0) {
_unlock_mutex();
goto out;
}
++i;
size += current; /* count with trailing '\0' */
}
_unlock_mutex();
len = strlen(message_data->id);
msg->size = size + len + 1;
dm_free(msg->data);
if (!(msg->data = dm_malloc(msg->size)))
goto out;
memcpy(msg->data, message_data->id, len);
message = msg->data + len;
*message++ = ' ';
for (j = 0; j < i; ++j) {
len = strlen(buffers[j]);
memcpy(message, buffers[j], len);
message += len;
}
ret = 0;
out:
for (j = 0; j < i; ++j)
dm_free(buffers[j]);
return ret;
}
static int _get_parameters(struct message_data *message_data) {
struct dm_event_daemon_message *msg = message_data->msg;
int size;
dm_free(msg->data);
if ((size = dm_asprintf(&msg->data, "%s pid=%d daemon=%s exec_method=%s",
message_data->id, getpid(),
_foreground ? "no" : "yes",
_systemd_activation ? "systemd" : "direct")) < 0) {
stack;
return -ENOMEM;
}
msg->size = (uint32_t) size;
return 0;
}
/* Cleanup at exit. */
static void _exit_dm_lib(void)
{
dm_lib_release();
dm_lib_exit();
}
static void _exit_timeout(void *unused __attribute__((unused)))
{
_timeout_running = 0;
pthread_mutex_unlock(&_timeout_mutex);
}
/* Wake up monitor threads every so often. */
static void *_timeout_thread(void *unused __attribute__((unused)))
{
struct thread_status *thread;
struct timespec timeout;
time_t curr_time;
DEBUGLOG("Timeout thread starting.");
timeout.tv_nsec = 0;
pthread_cleanup_push(_exit_timeout, NULL);
pthread_mutex_lock(&_timeout_mutex);
while (!dm_list_empty(&_timeout_registry)) {
timeout.tv_sec = 0;
curr_time = time(NULL);
dm_list_iterate_items_gen(thread, &_timeout_registry, timeout_list) {
if (thread->next_time <= curr_time) {
thread->next_time = curr_time + thread->timeout;
DEBUGLOG("Sending SIGALRM to Thr %x for timeout.", (int) thread->thread);
pthread_kill(thread->thread, SIGALRM);
}
if (thread->next_time < timeout.tv_sec || !timeout.tv_sec)
timeout.tv_sec = thread->next_time;
}
pthread_cond_timedwait(&_timeout_cond, &_timeout_mutex,
&timeout);
}
DEBUGLOG("Timeout thread finished.");
pthread_cleanup_pop(1);
return NULL;
}
static int _register_for_timeout(struct thread_status *thread)
{
int ret = 0;
pthread_mutex_lock(&_timeout_mutex);
if (dm_list_empty(&thread->timeout_list)) {
thread->next_time = time(NULL) + thread->timeout;
dm_list_add(&_timeout_registry, &thread->timeout_list);
if (_timeout_running)
pthread_cond_signal(&_timeout_cond);
}
if (!_timeout_running &&
!(ret = _pthread_create_smallstack(NULL, _timeout_thread, NULL)))
_timeout_running = 1;
pthread_mutex_unlock(&_timeout_mutex);
return ret;
}
static void _unregister_for_timeout(struct thread_status *thread)
{
pthread_mutex_lock(&_timeout_mutex);
if (!dm_list_empty(&thread->timeout_list)) {
dm_list_del(&thread->timeout_list);
dm_list_init(&thread->timeout_list);
if (dm_list_empty(&_timeout_registry))
/* No more work -> wakeup to finish quickly */
pthread_cond_signal(&_timeout_cond);
}
pthread_mutex_unlock(&_timeout_mutex);
}
__attribute__((format(printf, 4, 5)))
static void _no_intr_log(int level, const char *file, int line,
const char *f, ...)
{
va_list ap;
if (errno == EINTR)
return;
if (level > _LOG_WARN)
return;
va_start(ap, f);
vfprintf((level < _LOG_WARN) ? stderr : stdout, f, ap);
va_end(ap);
fputc('\n', (level < _LOG_WARN) ? stderr : stdout);
}
static sigset_t _unblock_sigalrm(void)
{
sigset_t set, old;
sigemptyset(&set);
sigaddset(&set, SIGALRM);
pthread_sigmask(SIG_UNBLOCK, &set, &old);
return old;
}
#define DM_WAIT_RETRY 0
#define DM_WAIT_INTR 1
#define DM_WAIT_FATAL 2
/* Wait on a device until an event occurs. */
static int _event_wait(struct thread_status *thread, struct dm_task **task)
{
static unsigned _in_event_counter = 0;
sigset_t set;
int ret = DM_WAIT_RETRY;
struct dm_task *dmt;
struct dm_info info;
*task = 0;
DEBUGLOG("Preparing waitevent task for %s", thread->device.uuid);
if (!(dmt = dm_task_create(DM_DEVICE_WAITEVENT)))
return DM_WAIT_RETRY;
thread->current_task = dmt;
if (!dm_task_set_uuid(dmt, thread->device.uuid) ||
!dm_task_set_event_nr(dmt, thread->event_nr))
goto out;
_lock_mutex();
/*
* Check if there are already some waiting events,
* in this case the logging is unmodified.
* TODO: audit libdm thread usage
*/
if (!_in_event_counter++)
dm_log_init(_no_intr_log);
_unlock_mutex();
DEBUGLOG("Starting waitevent task for %s", thread->device.uuid);
/*
* This is so that you can break out of waiting on an event,
* either for a timeout event, or to cancel the thread.
*/
set = _unblock_sigalrm();
errno = 0;
if (dm_task_run(dmt)) {
thread->current_events |= DM_EVENT_DEVICE_ERROR;
ret = DM_WAIT_INTR;
if ((ret = dm_task_get_info(dmt, &info)))
thread->event_nr = info.event_nr;
} else if (thread->events & DM_EVENT_TIMEOUT && errno == EINTR) {
thread->current_events |= DM_EVENT_TIMEOUT;
ret = DM_WAIT_INTR;
} else if (thread->status == DM_THREAD_SHUTDOWN && errno == EINTR) {
ret = DM_WAIT_FATAL;
} else {
syslog(LOG_NOTICE, "dm_task_run failed, errno = %d, %s",
errno, strerror(errno));
if (errno == ENXIO) {
syslog(LOG_ERR, "%s disappeared, detaching",
thread->device.name);
ret = DM_WAIT_FATAL;
}
}
DEBUGLOG("Completed waitevent task for %s", thread->device.uuid);
pthread_sigmask(SIG_SETMASK, &set, NULL);
_lock_mutex();
if (--_in_event_counter == 0)
dm_log_init(NULL);
_unlock_mutex();
out:
if (ret == DM_WAIT_FATAL || ret == DM_WAIT_RETRY) {
dm_task_destroy(dmt);
thread->current_task = NULL;
} else
*task = dmt;
return ret;
}
/* Register a device with the DSO. */
static int _do_register_device(struct thread_status *thread)
{
return thread->dso_data->register_device(thread->device.name,
thread->device.uuid,
thread->device.major,
thread->device.minor,
&(thread->dso_private));
}
/* Unregister a device with the DSO. */
static int _do_unregister_device(struct thread_status *thread)
{
return thread->dso_data->unregister_device(thread->device.name,
thread->device.uuid,
thread->device.major,
thread->device.minor,
&(thread->dso_private));
}
/* Process an event in the DSO. */
static void _do_process_event(struct thread_status *thread, struct dm_task *task)
{
thread->dso_data->process_event(task, thread->current_events, &(thread->dso_private));
}
/* Thread cleanup handler to unregister device. */
static void _monitor_unregister(void *arg)
{
struct thread_status *thread = arg, *thread_iter;
DEBUGLOG("_monitor_unregister thread cleanup handler running");
if (!_do_unregister_device(thread))
syslog(LOG_ERR, "%s: %s unregister failed\n", __func__,
thread->device.name);
if (thread->current_task) {
dm_task_destroy(thread->current_task);
thread->current_task = NULL;
}
_lock_mutex();
if (thread->events & DM_EVENT_TIMEOUT) {
/* _unregister_for_timeout locks another mutex, we
don't want to deadlock so we release our mutex for
a bit */
_unlock_mutex();
_unregister_for_timeout(thread);
_lock_mutex();
}
/* we may have been relinked to unused registry since we were
called, so check that */
dm_list_iterate_items(thread_iter, &_thread_registry_unused)
if (thread_iter == thread) {
thread->status = DM_THREAD_DONE;
_unlock_mutex();
return;
}
DEBUGLOG("Marking Thr %x as DONE and unused.", (int)thread->thread);
thread->status = DM_THREAD_DONE;
UNLINK_THREAD(thread);
LINK(thread, &_thread_registry_unused);
_unlock_mutex();
}
static struct dm_task *_get_device_status(struct thread_status *ts)
{
struct dm_task *dmt = dm_task_create(DM_DEVICE_STATUS);
if (!dmt)
return NULL;
if (!dm_task_set_uuid(dmt, ts->device.uuid)) {
dm_task_destroy(dmt);
return NULL;
}
if (!dm_task_run(dmt)) {
dm_task_destroy(dmt);
return NULL;
}
return dmt;
}
/* Device monitoring thread. */
static void *_monitor_thread(void *arg)
{
struct thread_status *thread = arg;
int wait_error;
struct dm_task *task;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
pthread_cleanup_push(_monitor_unregister, thread);
/* Wait for do_process_request() to finish its task. */
_lock_mutex();
thread->status = DM_THREAD_RUNNING;
_unlock_mutex();
/* Loop forever awaiting/analyzing device events. */
while (1) {
thread->current_events = 0;
wait_error = _event_wait(thread, &task);
if (wait_error == DM_WAIT_RETRY) {
usleep(100); /* avoid busy loop */
continue;
}
if (wait_error == DM_WAIT_FATAL)
break;
/* Timeout occurred, task is not filled properly.
* We get device status here for processing it in DSO.
*/
if (wait_error == DM_WAIT_INTR &&
thread->current_events & DM_EVENT_TIMEOUT) {
dm_task_destroy(task);
task = _get_device_status(thread);
/* FIXME: syslog fail here ? */
if (!(thread->current_task = task))
continue;
}
/*
* We know that wait succeeded and stored a
* pointer to dm_task with device status into task.
*/
/*
* Check against filter.
*
* If there's current events delivered from _event_wait() AND
* the device got registered for those events AND
* those events haven't been processed yet, call
* the DSO's process_event() handler.
*/
_lock_mutex();
if (thread->status == DM_THREAD_SHUTDOWN) {
_unlock_mutex();
break;
}
if (thread->events & thread->current_events) {
thread->processing = 1;
_unlock_mutex();
_do_process_event(thread, task);
dm_task_destroy(task);
thread->current_task = NULL;
_lock_mutex();
thread->processing = 0;
_unlock_mutex();
} else {
_unlock_mutex();
dm_task_destroy(task);
thread->current_task = NULL;
}
}
DEBUGLOG("Finished _monitor_thread");
pthread_cleanup_pop(1);
return NULL;
}
/* Create a device monitoring thread. */
static int _create_thread(struct thread_status *thread)
{
return _pthread_create_smallstack(&thread->thread, _monitor_thread, thread);
}
static int _terminate_thread(struct thread_status *thread)
{
DEBUGLOG("Sending SIGALRM to terminate Thr %x.", (int)thread->thread);
return pthread_kill(thread->thread, SIGALRM);
}
/* DSO reference counting. Call with _global_mutex locked! */
static void _lib_get(struct dso_data *data)
{
data->ref_count++;
}
static void _lib_put(struct dso_data *data)
{
if (!--data->ref_count) {
dlclose(data->dso_handle);
UNLINK_DSO(data);
_free_dso_data(data);
}
}
/* Find DSO data. */
static struct dso_data *_lookup_dso(struct message_data *data)
{
struct dso_data *dso_data, *ret = NULL;
dm_list_iterate_items(dso_data, &_dso_registry)
if (!strcmp(data->dso_name, dso_data->dso_name)) {
_lib_get(dso_data);
ret = dso_data;
break;
}
return ret;
}
/* Lookup DSO symbols we need. */
static int _lookup_symbol(void *dl, void **symbol, const char *name)
{
if ((*symbol = dlsym(dl, name)))
return 1;
return 0;
}
static int lookup_symbols(void *dl, struct dso_data *data)
{
return _lookup_symbol(dl, (void *) &data->process_event,
"process_event") &&
_lookup_symbol(dl, (void *) &data->register_device,
"register_device") &&
_lookup_symbol(dl, (void *) &data->unregister_device,
"unregister_device");
}
/* Load an application specific DSO. */
static struct dso_data *_load_dso(struct message_data *data)
{
void *dl;
struct dso_data *ret;
if (!(dl = dlopen(data->dso_name, RTLD_NOW))) {
const char *dlerr = dlerror();
syslog(LOG_ERR, "dmeventd %s dlopen failed: %s", data->dso_name,
dlerr);
data->msg->size =
dm_asprintf(&(data->msg->data), "%s %s dlopen failed: %s",
data->id, data->dso_name, dlerr);
return NULL;
}
if (!(ret = _alloc_dso_data(data))) {
dlclose(dl);
return NULL;
}
if (!(lookup_symbols(dl, ret))) {
_free_dso_data(ret);
dlclose(dl);
return NULL;
}
/*
* Keep handle to close the library once
* we've got no references to it any more.
*/
ret->dso_handle = dl;
_lib_get(ret);
_lock_mutex();
LINK_DSO(ret);
_unlock_mutex();
return ret;
}
/* Return success on daemon active check. */
static int _active(struct message_data *message_data)
{
return 0;
}
/*
* Register for an event.
*
* Only one caller at a time here, because we use
* a FIFO and lock it against multiple accesses.
*/
static int _register_for_event(struct message_data *message_data)
{
int ret = 0;
struct thread_status *thread, *thread_new = NULL;
struct dso_data *dso_data;
if (!(dso_data = _lookup_dso(message_data)) &&
!(dso_data = _load_dso(message_data))) {
stack;
#ifdef ELIBACC
ret = -ELIBACC;
#else
ret = -ENODEV;
#endif
goto out;
}
/* Preallocate thread status struct to avoid deadlock. */
if (!(thread_new = _alloc_thread_status(message_data, dso_data))) {
stack;
ret = -ENOMEM;
goto out;
}
if (!_fill_device_data(thread_new)) {
stack;
ret = -ENODEV;
goto out;
}
/* If creation of timeout thread fails (as it may), we fail
here completely. The client is responsible for either
retrying later or trying to register without timeout
events. However, if timeout thread cannot be started, it
usually means we are so starved on resources that we are
almost as good as dead already... */
if ((thread_new->events & DM_EVENT_TIMEOUT) &&
(ret = -_register_for_timeout(thread_new)))
goto out;
_lock_mutex();
if (!(thread = _lookup_thread_status(message_data))) {
_unlock_mutex();
if (!(ret = _do_register_device(thread_new)))
goto out;
thread = thread_new;
thread_new = NULL;
/* Try to create the monitoring thread for this device. */
_lock_mutex();
if ((ret = -_create_thread(thread))) {
_unlock_mutex();
_do_unregister_device(thread);
_free_thread_status(thread);
goto out;
}
LINK_THREAD(thread);
}
/* Or event # into events bitfield. */
thread->events |= message_data->events_field;
_unlock_mutex();
out:
/*
* Deallocate thread status after releasing
* the lock in case we haven't used it.
*/
if (thread_new)
_free_thread_status(thread_new);
return ret;
}
/*
* Unregister for an event.
*
* Only one caller at a time here as with register_for_event().
*/
static int _unregister_for_event(struct message_data *message_data)
{
int ret = 0;
struct thread_status *thread;
/*
* Clear event in bitfield and deactivate
* monitoring thread in case bitfield is 0.
*/
_lock_mutex();
if (!(thread = _lookup_thread_status(message_data))) {
_unlock_mutex();
ret = -ENODEV;
goto out;
}
if (thread->status == DM_THREAD_DONE) {
/* the thread has terminated while we were not
watching */
_unlock_mutex();
return 0;
}
thread->events &= ~message_data->events_field;
if (!(thread->events & DM_EVENT_TIMEOUT)) {
_unlock_mutex();
_unregister_for_timeout(thread);
_lock_mutex();
}
/*
* In case there's no events to monitor on this device ->
* unlink and terminate its monitoring thread.
*/
if (!thread->events) {
DEBUGLOG("Marking Thr %x unused (no events).", (int)thread->thread);
UNLINK_THREAD(thread);
LINK(thread, &_thread_registry_unused);
}
_unlock_mutex();
out:
return ret;
}
/*
* Get registered device.
*
* Only one caller at a time here as with register_for_event().
*/
static int _registered_device(struct message_data *message_data,
struct thread_status *thread)
{
int r;
struct dm_event_daemon_message *msg = message_data->msg;
unsigned events = ((thread->status == DM_THREAD_RUNNING) &&
thread->events) ? thread->events :
thread->events | DM_EVENT_REGISTRATION_PENDING;
dm_free(msg->data);
if ((r = dm_asprintf(&(msg->data), "%s %s %s %u",
message_data->id,
thread->dso_data->dso_name,
thread->device.uuid, events)) < 0)
return -ENOMEM;
msg->size = (uint32_t) r;
return 0;
}
static int _want_registered_device(char *dso_name, char *device_uuid,
struct thread_status *thread)
{
/* If DSO names and device paths are equal. */
if (dso_name && device_uuid)
return !strcmp(dso_name, thread->dso_data->dso_name) &&
!strcmp(device_uuid, thread->device.uuid);
/* If DSO names are equal. */
if (dso_name)
return !strcmp(dso_name, thread->dso_data->dso_name);
/* If device paths are equal. */
if (device_uuid)
return !strcmp(device_uuid, thread->device.uuid);
return 1;
}
static int _get_registered_dev(struct message_data *message_data, int next)
{
struct thread_status *thread, *hit = NULL;
int ret = -ENOENT;
_lock_mutex();
/* Iterate list of threads checking if we want a particular one. */
dm_list_iterate_items(thread, &_thread_registry)
if (_want_registered_device(message_data->dso_name,
message_data->device_uuid,
thread)) {
hit = thread;
break;
}
/*
* If we got a registered device and want the next one ->
* fetch next conforming element off the list.
*/
if (hit && !next)
goto reg;
/*
* If we didn't get a match, try the threads waiting to be deleted.
* FIXME Do something similar if 'next' is set.
*/
if (!hit && !next)
dm_list_iterate_items(thread, &_thread_registry_unused)
if (_want_registered_device(message_data->dso_name,
message_data->device_uuid, thread)) {
hit = thread;
goto reg;
}
if (!hit)
goto out;
while (1) {
if (dm_list_end(&_thread_registry, &thread->list))
goto out;
thread = dm_list_item(thread->list.n, struct thread_status);
if (_want_registered_device(message_data->dso_name, NULL, thread)) {
hit = thread;
break;
}
}
reg:
ret = _registered_device(message_data, hit);
out:
_unlock_mutex();
return ret;
}
static int _get_registered_device(struct message_data *message_data)
{
return _get_registered_dev(message_data, 0);
}
static int _get_next_registered_device(struct message_data *message_data)
{
return _get_registered_dev(message_data, 1);
}
static int _set_timeout(struct message_data *message_data)
{
struct thread_status *thread;
_lock_mutex();
if ((thread = _lookup_thread_status(message_data)))
thread->timeout = message_data->timeout_secs;
_unlock_mutex();
return thread ? 0 : -ENODEV;
}
static int _get_timeout(struct message_data *message_data)
{
struct thread_status *thread;
struct dm_event_daemon_message *msg = message_data->msg;
dm_free(msg->data);
_lock_mutex();
if ((thread = _lookup_thread_status(message_data))) {
msg->size = dm_asprintf(&(msg->data), "%s %" PRIu32,
message_data->id, thread->timeout);
} else
msg->data = NULL;
_unlock_mutex();
return thread ? 0 : -ENODEV;
}
/* Open fifos used for client communication. */
static int _open_fifos(struct dm_event_fifos *fifos)
{
struct stat st;
/* Create client fifo. */
(void) dm_prepare_selinux_context(fifos->client_path, S_IFIFO);
if ((mkfifo(fifos->client_path, 0600) == -1) && errno != EEXIST) {
syslog(LOG_ERR, "%s: Failed to create client fifo %s: %m.\n",
__func__, fifos->client_path);
(void) dm_prepare_selinux_context(NULL, 0);
goto fail;
}
/* Create server fifo. */
(void) dm_prepare_selinux_context(fifos->server_path, S_IFIFO);
if ((mkfifo(fifos->server_path, 0600) == -1) && errno != EEXIST) {
syslog(LOG_ERR, "%s: Failed to create server fifo %s: %m.\n",
__func__, fifos->server_path);
(void) dm_prepare_selinux_context(NULL, 0);
goto fail;
}
(void) dm_prepare_selinux_context(NULL, 0);
/* Warn about wrong permissions if applicable */
if ((!stat(fifos->client_path, &st)) && (st.st_mode & 0777) != 0600)
syslog(LOG_WARNING, "Fixing wrong permissions on %s: %m.\n",
fifos->client_path);
if ((!stat(fifos->server_path, &st)) && (st.st_mode & 0777) != 0600)
syslog(LOG_WARNING, "Fixing wrong permissions on %s: %m.\n",
fifos->server_path);
/* If they were already there, make sure permissions are ok. */
if (chmod(fifos->client_path, 0600)) {
syslog(LOG_ERR, "Unable to set correct file permissions on %s: %m.\n",
fifos->client_path);
goto fail;
}
if (chmod(fifos->server_path, 0600)) {
syslog(LOG_ERR, "Unable to set correct file permissions on %s: %m.\n",
fifos->server_path);
goto fail;
}
/* Need to open read+write or we will block or fail */
if ((fifos->server = open(fifos->server_path, O_RDWR)) < 0) {
syslog(LOG_ERR, "Failed to open fifo server %s: %m.\n",
fifos->server_path);
goto fail;
}
if (fcntl(fifos->server, F_SETFD, FD_CLOEXEC) < 0) {
syslog(LOG_ERR, "Failed to set FD_CLOEXEC for fifo server %s: %m.\n",
fifos->server_path);
goto fail;
}
/* Need to open read+write for select() to work. */
if ((fifos->client = open(fifos->client_path, O_RDWR)) < 0) {
syslog(LOG_ERR, "Failed to open fifo client %s: %m", fifos->client_path);
goto fail;
}
if (fcntl(fifos->client, F_SETFD, FD_CLOEXEC) < 0) {
syslog(LOG_ERR, "Failed to set FD_CLOEXEC for fifo client %s: %m.\n",
fifos->client_path);
goto fail;
}
return 1;
fail:
if (fifos->server >= 0 && close(fifos->server))
syslog(LOG_ERR, "Failed to close fifo server %s: %m", fifos->server_path);
if (fifos->client >= 0 && close(fifos->client))
syslog(LOG_ERR, "Failed to close fifo client %s: %m", fifos->client_path);
return 0;
}
/*
* Read message from client making sure that data is available
* and a complete message is read. Must not block indefinitely.
*/
static int _client_read(struct dm_event_fifos *fifos,
struct dm_event_daemon_message *msg)
{
struct timeval t;
unsigned bytes = 0;
int ret = 0;
fd_set fds;
size_t size = 2 * sizeof(uint32_t); /* status + size */
uint32_t *header = alloca(size);
char *buf = (char *)header;
msg->data = NULL;
errno = 0;
while (bytes < size && errno != EOF) {
/* Watch client read FIFO for input. */
FD_ZERO(&fds);
FD_SET(fifos->client, &fds);
t.tv_sec = 1;
t.tv_usec = 0;
ret = select(fifos->client + 1, &fds, NULL, NULL, &t);
if (!ret && !bytes) /* nothing to read */
return 0;
if (!ret) /* trying to finish read */
continue;
if (ret < 0) /* error */
return 0;
ret = read(fifos->client, buf + bytes, size - bytes);
bytes += ret > 0 ? ret : 0;
if (header && (bytes == 2 * sizeof(uint32_t))) {
msg->cmd = ntohl(header[0]);
msg->size = ntohl(header[1]);
buf = msg->data = dm_malloc(msg->size);
size = msg->size;
bytes = 0;
header = 0;
}
}
if (bytes != size) {
dm_free(msg->data);
msg->data = NULL;
return 0;
}
return 1;
}
/*
* Write a message to the client making sure that it is ready to write.
*/
static int _client_write(struct dm_event_fifos *fifos,
struct dm_event_daemon_message *msg)
{
uint32_t temp[2];
unsigned bytes = 0;
int ret = 0;
fd_set fds;
size_t size = 2 * sizeof(uint32_t) + ((msg->data) ? msg->size : 0);
uint32_t *header = dm_malloc(size);
char *buf = (char *)header;
if (!header) {
/* Reply with ENOMEM message */
header = temp;
size = sizeof(temp);
header[0] = htonl(-ENOMEM);
header[1] = 0;
} else {
header[0] = htonl(msg->cmd);
header[1] = htonl((msg->data) ? msg->size : 0);
if (msg->data)
memcpy(buf + 2 * sizeof(uint32_t), msg->data, msg->size);
}
while (bytes < size) {
do {
/* Watch client write FIFO to be ready for output. */
FD_ZERO(&fds);
FD_SET(fifos->server, &fds);
} while (select(fifos->server + 1, NULL, &fds, NULL, NULL) != 1);
if ((ret = write(fifos->server, buf + bytes, size - bytes)) > 0)
bytes += ret;
else if (errno == EIO)
break;
}
if (header != temp)
dm_free(header);
return (bytes == size);
}
/*
* Handle a client request.
*
* We put the request handling functions into
* a list because of the growing number.
*/
static int _handle_request(struct dm_event_daemon_message *msg,
struct message_data *message_data)
{
switch (msg->cmd) {
case DM_EVENT_CMD_REGISTER_FOR_EVENT:
return _register_for_event(message_data);
case DM_EVENT_CMD_UNREGISTER_FOR_EVENT:
return _unregister_for_event(message_data);
case DM_EVENT_CMD_GET_REGISTERED_DEVICE:
return _get_registered_device(message_data);
case DM_EVENT_CMD_GET_NEXT_REGISTERED_DEVICE:
return _get_next_registered_device(message_data);
case DM_EVENT_CMD_SET_TIMEOUT:
return _set_timeout(message_data);
case DM_EVENT_CMD_GET_TIMEOUT:
return _get_timeout(message_data);
case DM_EVENT_CMD_ACTIVE:
return _active(message_data);
case DM_EVENT_CMD_GET_STATUS:
return _get_status(message_data);
/* dmeventd parameters of running dmeventd,
* returns 'pid=<pid> daemon=<no/yes> exec_method=<direct/systemd>'
* pid - pidfile of running dmeventd
* daemon - running as a daemon or not (foreground)?
* exec_method - "direct" if executed directly or
* "systemd" if executed via systemd
*/
case DM_EVENT_CMD_GET_PARAMETERS:
return _get_parameters(message_data);
default:
return -EINVAL;
}
}
/* Process a request passed from the communication thread. */
static int _do_process_request(struct dm_event_daemon_message *msg)
{
int ret;
char *answer;
struct message_data message_data = { .msg = msg };
/* Parse the message. */
if (msg->cmd == DM_EVENT_CMD_HELLO || msg->cmd == DM_EVENT_CMD_DIE) {
ret = 0;
answer = msg->data;
if (answer) {
msg->size = dm_asprintf(&(msg->data), "%s %s %d", answer,
(msg->cmd == DM_EVENT_CMD_DIE) ? "DYING" : "HELLO",
DM_EVENT_PROTOCOL_VERSION);
dm_free(answer);
}
} else if (msg->cmd != DM_EVENT_CMD_ACTIVE && !_parse_message(&message_data)) {
stack;
ret = -EINVAL;
} else
ret = _handle_request(msg, &message_data);
msg->cmd = ret;
if (!msg->data)
msg->size = dm_asprintf(&(msg->data), "%s %s", message_data.id, strerror(-ret));
_free_message(&message_data);
return ret;
}
/* Only one caller at a time. */
static void _process_request(struct dm_event_fifos *fifos)
{
int die;
struct dm_event_daemon_message msg = { 0 };
#ifdef DEBUG
const char *cmd;
#endif
/*
* Read the request from the client (client_read, client_write
* give true on success and false on failure).
*/
if (!_client_read(fifos, &msg))
return;
DEBUGLOG("%s processing...", cmd = decode_cmd(msg.cmd));
die = (msg.cmd == DM_EVENT_CMD_DIE) ? 1 : 0;
/* _do_process_request fills in msg (if memory allows for
data, otherwise just cmd and size = 0) */
_do_process_request(&msg);
if (!_client_write(fifos, &msg))
stack;
dm_free(msg.data);
DEBUGLOG("%s completed.", cmd);
if (die) {
if (unlink(DMEVENTD_PIDFILE))
perror(DMEVENTD_PIDFILE ": unlink failed");
_exit(0);
}
}
static void _process_initial_registrations(void)
{
int i;
char *reg;
struct dm_event_daemon_message msg = { 0 };
for (i = 0; (reg = _initial_registrations[i]); ++i) {
msg.cmd = DM_EVENT_CMD_REGISTER_FOR_EVENT;
if ((msg.size = strlen(reg))) {
msg.data = reg;
_do_process_request(&msg);
}
}
}
static void _cleanup_unused_threads(void)
{
int ret;
struct dm_list *l;
struct thread_status *thread;
int join_ret = 0;
_lock_mutex();
while ((l = dm_list_first(&_thread_registry_unused))) {
thread = dm_list_item(l, struct thread_status);
if (thread->processing)
break; /* cleanup on the next round */
if (thread->status == DM_THREAD_RUNNING) {
thread->status = DM_THREAD_SHUTDOWN;
break;
}
if (thread->status == DM_THREAD_SHUTDOWN) {
if (!thread->events) {
/* turn codes negative -- should we be returning this? */
ret = _terminate_thread(thread);
if (ret == ESRCH) {
thread->status = DM_THREAD_DONE;
} else if (ret) {
syslog(LOG_ERR,
"Unable to terminate thread: %s\n",
strerror(-ret));
stack;
}
break;
}
dm_list_del(l);
syslog(LOG_ERR,
"thread can't be on unused list unless !thread->events");
thread->status = DM_THREAD_RUNNING;
LINK_THREAD(thread);
continue;
}
if (thread->status == DM_THREAD_DONE) {
DEBUGLOG("Destroying Thr %x.", (int)thread->thread);
dm_list_del(l);
_unlock_mutex();
join_ret = pthread_join(thread->thread, NULL);
_free_thread_status(thread);
_lock_mutex();
}
}
_unlock_mutex();
if (join_ret)
syslog(LOG_ERR, "Failed pthread_join: %s\n", strerror(join_ret));
}
static void _sig_alarm(int signum __attribute__((unused)))
{
DEBUGLOG("Received SIGALRM.");
pthread_testcancel();
}
/* Init thread signal handling. */
static void _init_thread_signals(void)
{
sigset_t my_sigset;
struct sigaction act = { .sa_handler = _sig_alarm };
sigaction(SIGALRM, &act, NULL);
sigfillset(&my_sigset);
/* These are used for exiting */
sigdelset(&my_sigset, SIGTERM);
sigdelset(&my_sigset, SIGINT);
sigdelset(&my_sigset, SIGHUP);
sigdelset(&my_sigset, SIGQUIT);
pthread_sigmask(SIG_BLOCK, &my_sigset, NULL);
}
/*
* exit_handler
* @sig
*
* Set the global variable which the process should
* be watching to determine when to exit.
*/
static void _exit_handler(int sig __attribute__((unused)))
{
_exit_now = 1;
}
#ifdef __linux__
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);
}
static int _handle_preloaded_fifo(int fd, const char *path)
{
struct stat st_fd, st_path;
int flags;
if ((flags = fcntl(fd, F_GETFD)) < 0)
return 0;
if (flags & FD_CLOEXEC)
return 0;
if (fstat(fd, &st_fd) < 0 || !S_ISFIFO(st_fd.st_mode))
return 0;
if (stat(path, &st_path) < 0 ||
st_path.st_dev != st_fd.st_dev ||
st_path.st_ino != st_fd.st_ino)
return 0;
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
return 0;
return 1;
}
static int _systemd_handover(struct dm_event_fifos *fifos)
{
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 2 and the fds must be FIFOSs! */
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 != 2)
goto out;
/* Check and handle the FIFOs passed in */
r = (_handle_preloaded_fifo(SD_FD_FIFO_SERVER, DM_EVENT_FIFO_SERVER) &&
_handle_preloaded_fifo(SD_FD_FIFO_CLIENT, DM_EVENT_FIFO_CLIENT));
if (r) {
fifos->server = SD_FD_FIFO_SERVER;
fifos->server_path = DM_EVENT_FIFO_SERVER;
fifos->client = SD_FD_FIFO_CLIENT;
fifos->client_path = DM_EVENT_FIFO_CLIENT;
}
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 void _remove_files_on_exit(void)
{
if (unlink(DMEVENTD_PIDFILE))
perror(DMEVENTD_PIDFILE ": unlink failed");
if (!_systemd_activation) {
if (unlink(DM_EVENT_FIFO_CLIENT))
perror(DM_EVENT_FIFO_CLIENT " : unlink failed");
if (unlink(DM_EVENT_FIFO_SERVER))
perror(DM_EVENT_FIFO_SERVER " : unlink failed");
}
}
static void _daemonize(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) && !_exit_now) {
tval.tv_sec = 0;
tval.tv_usec = 250000; /* .25 sec */
select(0, NULL, NULL, NULL, &tval);
}
if (_exit_now) /* Child has signaled it is ok - we can exit now */
exit(EXIT_SUCCESS);
/* Problem with child. Determine what it is by exit code */
switch (WEXITSTATUS(child_status)) {
case EXIT_DESC_CLOSE_FAILURE:
case EXIT_DESC_OPEN_FAILURE:
case EXIT_FIFO_FAILURE:
case EXIT_CHDIR_FAILURE:
default:
fprintf(stderr, "Child exited with code %d\n", WEXITSTATUS(child_status));
break;
}
exit(WEXITSTATUS(child_status));
}
if (chdir("/"))
exit(EXIT_CHDIR_FAILURE);
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_FIFO_SERVER || fd == SD_FD_FIFO_CLIENT))
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(EXIT_DESC_OPEN_FAILURE);
setsid();
}
static int _reinstate_registrations(struct dm_event_fifos *fifos)
{
static const char _failed_parsing_msg[] = "Failed to parse existing event registration.\n";
static const char *_delim = " ";
struct dm_event_daemon_message msg = { 0 };
char *endp, *dso_name, *dev_name, *mask, *timeout;
unsigned long mask_value, timeout_value;
int i, ret;
ret = daemon_talk(fifos, &msg, DM_EVENT_CMD_HELLO, NULL, NULL, 0, 0);
dm_free(msg.data);
msg.data = NULL;
if (ret) {
fprintf(stderr, "Failed to communicate with new instance of dmeventd.\n");
return 0;
}
for (i = 0; _initial_registrations[i]; ++i) {
if (!(strtok(_initial_registrations[i], _delim)) ||
!(dso_name = strtok(NULL, _delim)) ||
!(dev_name = strtok(NULL, _delim)) ||
!(mask = strtok(NULL, _delim)) ||
!(timeout = strtok(NULL, _delim))) {
fprintf(stderr, _failed_parsing_msg);
continue;
}
errno = 0;
mask_value = strtoul(mask, &endp, 10);
if (errno || !endp || *endp) {
fprintf(stderr, _failed_parsing_msg);
continue;
}
errno = 0;
timeout_value = strtoul(timeout, &endp, 10);
if (errno || !endp || *endp) {
fprintf(stderr, _failed_parsing_msg);
continue;
}
if (daemon_talk(fifos, &msg, DM_EVENT_CMD_REGISTER_FOR_EVENT,
dso_name,
dev_name,
(enum dm_event_mask) mask_value,
timeout_value))
fprintf(stderr, "Failed to reinstate monitoring for device %s.\n", dev_name);
}
return 1;
}
static void restart(void)
{
struct dm_event_fifos fifos = {
.server = -1,
.client = -1,
/* FIXME Make these either configurable or depend directly on dmeventd_path */
.client_path = DM_EVENT_FIFO_CLIENT,
.server_path = DM_EVENT_FIFO_SERVER
};
struct dm_event_daemon_message msg = { 0 };
int i, count = 0;
char *message;
int version;
const char *e;
/* Get the list of registrations from the running daemon. */
if (!init_fifos(&fifos)) {
fprintf(stderr, "WARNING: Could not initiate communication with existing dmeventd.\n");
exit(EXIT_FAILURE);
}
if (!dm_event_get_version(&fifos, &version)) {
fprintf(stderr, "WARNING: Could not communicate with existing dmeventd.\n");
goto bad;
}
if (version < 1) {
fprintf(stderr, "WARNING: The running dmeventd instance is too old.\n"
"Protocol version %d (required: 1). Action cancelled.\n",
version);
goto bad;
}
if (daemon_talk(&fifos, &msg, DM_EVENT_CMD_GET_STATUS, "-", "-", 0, 0))
goto bad;
message = strchr(msg.data, ' ') + 1;
for (i = 0; msg.data[i]; ++i)
if (msg.data[i] == ';') {
msg.data[i] = 0;
++count;
}
if (!(_initial_registrations = dm_malloc(sizeof(char*) * (count + 1)))) {
fprintf(stderr, "Memory allocation registration failed.\n");
goto bad;
}
for (i = 0; i < count; ++i) {
if (!(_initial_registrations[i] = dm_strdup(message))) {
fprintf(stderr, "Memory allocation for message failed.\n");
goto bad;
}
message += strlen(message) + 1;
}
_initial_registrations[count] = NULL;
if (version >= 2) {
if (daemon_talk(&fifos, &msg, DM_EVENT_CMD_GET_PARAMETERS, "-", "-", 0, 0)) {
fprintf(stderr, "Failed to acquire parameters from old dmeventd.\n");
goto bad;
}
if (strstr(msg.data, "exec_method=systemd"))
_systemd_activation = 1;
}
#ifdef __linux__
/*
* If the protocol version is old, just assume that if systemd is running,
* the dmeventd is also run as a systemd service via fifo activation.
*/
if (version < 2) {
/* This check is copied from sd-daemon.c. */
struct stat st;
if (!lstat(SD_RUNTIME_UNIT_FILE_DIR, &st) && !!S_ISDIR(st.st_mode))
_systemd_activation = 1;
}
#endif
if (daemon_talk(&fifos, &msg, DM_EVENT_CMD_DIE, "-", "-", 0, 0)) {
fprintf(stderr, "Old dmeventd refused to die.\n");
goto bad;
}
if (!_systemd_activation &&
((e = getenv(SD_ACTIVATION_ENV_VAR_NAME)) && strcmp(e, "1")))
_systemd_activation = 1;
for (i = 0; i < 10; ++i) {
if ((access(DMEVENTD_PIDFILE, F_OK) == -1) && (errno == ENOENT))
break;
usleep(10);
}
if (!_systemd_activation) {
fini_fifos(&fifos);
return;
}
/* Reopen fifos. */
fini_fifos(&fifos);
if (!init_fifos(&fifos)) {
fprintf(stderr, "Could not initiate communication with new instance of dmeventd.\n");
exit(EXIT_FAILURE);
}
if (!_reinstate_registrations(&fifos)) {
fprintf(stderr, "Failed to reinstate monitoring with new instance of dmeventd.\n");
goto bad;
}
fini_fifos(&fifos);
exit(EXIT_SUCCESS);
bad:
fini_fifos(&fifos);
exit(EXIT_FAILURE);
}
static void usage(char *prog, FILE *file)
{
fprintf(file, "Usage:\n"
"%s [-d [-d [-d]]] [-f] [-h] [-R] [-V] [-?]\n\n"
" -d Log debug messages to syslog (-d, -dd, -ddd)\n"
" -f Don't fork, run in the foreground\n"
" -h -? Show this help information\n"
" -R Restart dmeventd\n"
" -V Show version of dmeventd\n\n", prog);
}
int main(int argc, char *argv[])
{
signed char opt;
struct dm_event_fifos fifos = {
.client = -1,
.server = -1,
.client_path = DM_EVENT_FIFO_CLIENT,
.server_path = DM_EVENT_FIFO_SERVER
};
int nothreads;
//struct sys_log logdata = {DAEMON_NAME, LOG_DAEMON};
opterr = 0;
optind = 0;
while ((opt = getopt(argc, argv, "?fhVdR")) != EOF) {
switch (opt) {
case 'h':
usage(argv[0], stdout);
exit(EXIT_SUCCESS);
case '?':
usage(argv[0], stderr);
exit(EXIT_SUCCESS);
case 'R':
_restart++;
break;
case 'f':
_foreground++;
break;
case 'd':
dmeventd_debug++;
break;
case 'V':
printf("dmeventd version: %s\n", DM_LIB_VERSION);
exit(EXIT_SUCCESS);
}
}
/*
* Switch to C locale to avoid reading large locale-archive file
* used by some glibc (on some distributions it takes over 100MB).
* Daemon currently needs to use mlockall().
*/
if (setenv("LC_ALL", "C", 1))
perror("Cannot set LC_ALL to C");
if (_restart)
restart();
#ifdef __linux__
_systemd_activation = _systemd_handover(&fifos);
#endif
if (!_foreground)
_daemonize();
openlog("dmeventd", LOG_PID, LOG_DAEMON);
(void) dm_prepare_selinux_context(DMEVENTD_PIDFILE, S_IFREG);
if (dm_create_lockfile(DMEVENTD_PIDFILE) == 0)
exit(EXIT_FAILURE);
atexit(_remove_files_on_exit);
(void) dm_prepare_selinux_context(NULL, 0);
/* Set the rest of the signals to cause '_exit_now' to be set */
signal(SIGTERM, &_exit_handler);
signal(SIGINT, &_exit_handler);
signal(SIGHUP, &_exit_handler);
signal(SIGQUIT, &_exit_handler);
#ifdef __linux__
/* Systemd has adjusted oom killer for us already */
if (!_systemd_activation && !_protect_against_oom_killer())
syslog(LOG_ERR, "Failed to protect against OOM killer");
#endif
_init_thread_signals();
//multilog_clear_logging();
//multilog_add_type(std_syslog, &logdata);
//multilog_init_verbose(std_syslog, _LOG_DEBUG);
//multilog_async(1);
pthread_mutex_init(&_global_mutex, NULL);
if (!_systemd_activation && !_open_fifos(&fifos))
exit(EXIT_FIFO_FAILURE);
/* Signal parent, letting them know we are ready to go. */
if (!_foreground)
kill(getppid(), SIGTERM);
syslog(LOG_NOTICE, "dmeventd ready for processing.");
if (_initial_registrations)
_process_initial_registrations();
for (;;) {
if (_exit_now) {
_exit_now = 0;
/*
* When '_exit_now' is set, signal has been received,
* but can not simply exit unless all
* threads are done processing.
*/
_lock_mutex();
nothreads = (dm_list_empty(&_thread_registry) &&
dm_list_empty(&_thread_registry_unused));
_unlock_mutex();
if (nothreads)
break;
syslog(LOG_ERR, "There are still devices being monitored.");
syslog(LOG_ERR, "Refusing to exit.");
}
_process_request(&fifos);
_cleanup_unused_threads();
}
_exit_dm_lib();
pthread_mutex_destroy(&_global_mutex);
syslog(LOG_NOTICE, "dmeventd shutting down.");
closelog();
exit(EXIT_SUCCESS);
}