| /* |
| * Copyright (C) 2005-2015 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include "dm-logging.h" |
| #include "dmlib.h" |
| #include "libdevmapper-event.h" |
| #include "dmeventd.h" |
| |
| #include <fcntl.h> |
| #include <sys/file.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/wait.h> |
| #include <arpa/inet.h> /* for htonl, ntohl */ |
| #include <pthread.h> |
| #include <syslog.h> |
| |
| static int _debug_level = 0; |
| static int _use_syslog = 0; |
| static int _sequence_nr = 0; |
| |
| struct dm_event_handler { |
| char *dso; |
| |
| char *dmeventd_path; |
| |
| char *dev_name; |
| |
| char *uuid; |
| int major; |
| int minor; |
| uint32_t timeout; |
| |
| enum dm_event_mask mask; |
| }; |
| |
| static void _dm_event_handler_clear_dev_info(struct dm_event_handler *dmevh) |
| { |
| dm_free(dmevh->dev_name); |
| dm_free(dmevh->uuid); |
| dmevh->dev_name = dmevh->uuid = NULL; |
| dmevh->major = dmevh->minor = 0; |
| } |
| |
| struct dm_event_handler *dm_event_handler_create(void) |
| { |
| struct dm_event_handler *dmevh; |
| |
| if (!(dmevh = dm_zalloc(sizeof(*dmevh)))) { |
| log_error("Failed to allocate event handler."); |
| return NULL; |
| } |
| |
| return dmevh; |
| } |
| |
| void dm_event_handler_destroy(struct dm_event_handler *dmevh) |
| { |
| _dm_event_handler_clear_dev_info(dmevh); |
| dm_free(dmevh->dso); |
| dm_free(dmevh->dmeventd_path); |
| dm_free(dmevh); |
| } |
| |
| int dm_event_handler_set_dmeventd_path(struct dm_event_handler *dmevh, const char *dmeventd_path) |
| { |
| if (!dmeventd_path) /* noop */ |
| return 0; |
| |
| dm_free(dmevh->dmeventd_path); |
| |
| if (!(dmevh->dmeventd_path = dm_strdup(dmeventd_path))) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| int dm_event_handler_set_dso(struct dm_event_handler *dmevh, const char *path) |
| { |
| if (!path) /* noop */ |
| return 0; |
| |
| dm_free(dmevh->dso); |
| |
| if (!(dmevh->dso = dm_strdup(path))) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| int dm_event_handler_set_dev_name(struct dm_event_handler *dmevh, const char *dev_name) |
| { |
| if (!dev_name) |
| return 0; |
| |
| _dm_event_handler_clear_dev_info(dmevh); |
| |
| if (!(dmevh->dev_name = dm_strdup(dev_name))) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| int dm_event_handler_set_uuid(struct dm_event_handler *dmevh, const char *uuid) |
| { |
| if (!uuid) |
| return 0; |
| |
| _dm_event_handler_clear_dev_info(dmevh); |
| |
| if (!(dmevh->uuid = dm_strdup(uuid))) |
| return -ENOMEM; |
| |
| return 0; |
| } |
| |
| void dm_event_handler_set_major(struct dm_event_handler *dmevh, int major) |
| { |
| int minor = dmevh->minor; |
| |
| _dm_event_handler_clear_dev_info(dmevh); |
| |
| dmevh->major = major; |
| dmevh->minor = minor; |
| } |
| |
| void dm_event_handler_set_minor(struct dm_event_handler *dmevh, int minor) |
| { |
| int major = dmevh->major; |
| |
| _dm_event_handler_clear_dev_info(dmevh); |
| |
| dmevh->major = major; |
| dmevh->minor = minor; |
| } |
| |
| void dm_event_handler_set_event_mask(struct dm_event_handler *dmevh, |
| enum dm_event_mask evmask) |
| { |
| dmevh->mask = evmask; |
| } |
| |
| void dm_event_handler_set_timeout(struct dm_event_handler *dmevh, int timeout) |
| { |
| dmevh->timeout = timeout; |
| } |
| |
| const char *dm_event_handler_get_dso(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->dso; |
| } |
| |
| const char *dm_event_handler_get_dev_name(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->dev_name; |
| } |
| |
| const char *dm_event_handler_get_uuid(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->uuid; |
| } |
| |
| int dm_event_handler_get_major(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->major; |
| } |
| |
| int dm_event_handler_get_minor(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->minor; |
| } |
| |
| int dm_event_handler_get_timeout(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->timeout; |
| } |
| |
| enum dm_event_mask dm_event_handler_get_event_mask(const struct dm_event_handler *dmevh) |
| { |
| return dmevh->mask; |
| } |
| |
| static int _check_message_id(struct dm_event_daemon_message *msg) |
| { |
| int pid, seq_nr; |
| |
| if ((sscanf(msg->data, "%d:%d", &pid, &seq_nr) != 2) || |
| (pid != getpid()) || (seq_nr != _sequence_nr)) { |
| log_error("Ignoring out-of-sequence reply from dmeventd. " |
| "Expected %d:%d but received %s.", getpid(), |
| _sequence_nr, msg->data); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* |
| * daemon_read |
| * @fifos |
| * @msg |
| * |
| * Read message from daemon. |
| * |
| * Returns: 0 on failure, 1 on success |
| */ |
| static int _daemon_read(struct dm_event_fifos *fifos, |
| struct dm_event_daemon_message *msg) |
| { |
| unsigned bytes = 0; |
| int ret, i; |
| fd_set fds; |
| size_t size = 2 * sizeof(uint32_t); /* status + size */ |
| uint32_t *header = alloca(size); |
| char *buf = (char *)header; |
| |
| while (bytes < size) { |
| for (i = 0, ret = 0; (i < 20) && (ret < 1); i++) { |
| /* Watch daemon read FIFO for input. */ |
| struct timeval tval = { .tv_sec = 1 }; |
| FD_ZERO(&fds); |
| FD_SET(fifos->server, &fds); |
| ret = select(fifos->server + 1, &fds, NULL, NULL, &tval); |
| if (ret < 0 && errno != EINTR) { |
| log_error("Unable to read from event server."); |
| return 0; |
| } |
| if ((ret == 0) && (i > 4) && !bytes) { |
| log_error("No input from event server."); |
| return 0; |
| } |
| } |
| if (ret < 1) { |
| log_error("Unable to read from event server."); |
| return 0; |
| } |
| |
| ret = read(fifos->server, buf + bytes, size); |
| if (ret < 0) { |
| if ((errno == EINTR) || (errno == EAGAIN)) |
| continue; |
| else { |
| log_error("Unable to read from event server."); |
| return 0; |
| } |
| } |
| |
| bytes += ret; |
| 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 bytes == size; |
| } |
| |
| /* Write message to daemon. */ |
| static int _daemon_write(struct dm_event_fifos *fifos, |
| struct dm_event_daemon_message *msg) |
| { |
| int ret; |
| fd_set fds; |
| size_t bytes = 0; |
| size_t size = 2 * sizeof(uint32_t) + msg->size; |
| uint32_t *header = alloca(size); |
| char *buf = (char *)header; |
| char drainbuf[128]; |
| |
| header[0] = htonl(msg->cmd); |
| header[1] = htonl(msg->size); |
| memcpy(buf + 2 * sizeof(uint32_t), msg->data, msg->size); |
| |
| /* drain the answer fifo */ |
| while (1) { |
| struct timeval tval = { .tv_usec = 100 }; |
| FD_ZERO(&fds); |
| FD_SET(fifos->server, &fds); |
| ret = select(fifos->server + 1, &fds, NULL, NULL, &tval); |
| if (ret < 0) { |
| if (errno == EINTR) |
| continue; |
| log_error("Unable to talk to event daemon."); |
| return 0; |
| } |
| if (ret == 0) |
| break; |
| ret = read(fifos->server, drainbuf, sizeof(drainbuf)); |
| if (ret < 0) { |
| if ((errno == EINTR) || (errno == EAGAIN)) |
| continue; |
| log_error("Unable to talk to event daemon."); |
| return 0; |
| } |
| } |
| |
| while (bytes < size) { |
| do { |
| /* Watch daemon write FIFO to be ready for output. */ |
| FD_ZERO(&fds); |
| FD_SET(fifos->client, &fds); |
| ret = select(fifos->client + 1, NULL, &fds, NULL, NULL); |
| if ((ret < 0) && (errno != EINTR)) { |
| log_error("Unable to talk to event daemon."); |
| return 0; |
| } |
| } while (ret < 1); |
| |
| ret = write(fifos->client, buf + bytes, size - bytes); |
| if (ret < 0) { |
| if ((errno == EINTR) || (errno == EAGAIN)) |
| continue; |
| else { |
| log_error("Unable to talk to event daemon."); |
| return 0; |
| } |
| } |
| |
| bytes += ret; |
| } |
| |
| return bytes == size; |
| } |
| |
| int daemon_talk(struct dm_event_fifos *fifos, |
| struct dm_event_daemon_message *msg, int cmd, |
| const char *dso_name, const char *dev_name, |
| enum dm_event_mask evmask, uint32_t timeout) |
| { |
| int msg_size; |
| memset(msg, 0, sizeof(*msg)); |
| |
| /* |
| * Set command and pack the arguments |
| * into ASCII message string. |
| */ |
| if ((msg_size = |
| ((cmd == DM_EVENT_CMD_HELLO) ? |
| dm_asprintf(&(msg->data), "%d:%d HELLO", getpid(), _sequence_nr) : |
| dm_asprintf(&(msg->data), "%d:%d %s %s %u %" PRIu32, |
| getpid(), _sequence_nr, |
| dso_name ? : "-", dev_name ? : "-", evmask, timeout))) |
| < 0) { |
| log_error("_daemon_talk: message allocation failed."); |
| return -ENOMEM; |
| } |
| msg->cmd = cmd; |
| msg->size = msg_size; |
| |
| /* |
| * Write command and message to and |
| * read status return code from daemon. |
| */ |
| if (!_daemon_write(fifos, msg)) { |
| stack; |
| dm_free(msg->data); |
| msg->data = NULL; |
| return -EIO; |
| } |
| |
| do { |
| dm_free(msg->data); |
| msg->data = NULL; |
| |
| if (!_daemon_read(fifos, msg)) { |
| stack; |
| return -EIO; |
| } |
| } while (!_check_message_id(msg)); |
| |
| _sequence_nr++; |
| |
| return (int32_t) msg->cmd; |
| } |
| |
| /* |
| * start_daemon |
| * |
| * This function forks off a process (dmeventd) that will handle |
| * the events. I am currently test opening one of the fifos to |
| * ensure that the daemon is running and listening... I thought |
| * this would be less expensive than fork/exec'ing every time. |
| * Perhaps there is an even quicker/better way (no, checking the |
| * lock file is _not_ a better way). |
| * |
| * Returns: 1 on success, 0 otherwise |
| */ |
| static int _start_daemon(char *dmeventd_path, struct dm_event_fifos *fifos) |
| { |
| int pid, ret = 0; |
| int status; |
| struct stat statbuf; |
| char default_dmeventd_path[] = DMEVENTD_PATH; |
| char *args[] = { dmeventd_path ? : default_dmeventd_path, NULL }; |
| |
| /* |
| * FIXME Explicitly verify the code's requirement that client_path is secure: |
| * - All parent directories owned by root without group/other write access unless sticky. |
| */ |
| |
| /* If client fifo path exists, only use it if it is root-owned fifo mode 0600 */ |
| if ((lstat(fifos->client_path, &statbuf) < 0)) { |
| if (errno == ENOENT) |
| /* Jump ahead if fifo does not already exist. */ |
| goto start_server; |
| else { |
| log_sys_error("stat", fifos->client_path); |
| return 0; |
| } |
| } else if (!S_ISFIFO(statbuf.st_mode)) { |
| log_error("%s must be a fifo.", fifos->client_path); |
| return 0; |
| } else if (statbuf.st_uid) { |
| log_error("%s must be owned by uid 0.", fifos->client_path); |
| return 0; |
| } else if (statbuf.st_mode & (S_IEXEC | S_IRWXG | S_IRWXO)) { |
| log_error("%s must have mode 0600.", fifos->client_path); |
| return 0; |
| } |
| |
| /* Anyone listening? If not, errno will be ENXIO */ |
| fifos->client = open(fifos->client_path, O_WRONLY | O_NONBLOCK); |
| if (fifos->client >= 0) { |
| /* Should never happen if all the above checks passed. */ |
| if ((fstat(fifos->client, &statbuf) < 0) || |
| !S_ISFIFO(statbuf.st_mode) || statbuf.st_uid || |
| (statbuf.st_mode & (S_IEXEC | S_IRWXG | S_IRWXO))) { |
| log_error("%s is no longer a secure root-owned fifo with mode 0600.", fifos->client_path); |
| if (close(fifos->client)) |
| log_sys_debug("close", fifos->client_path); |
| return 0; |
| } |
| |
| /* server is running and listening */ |
| if (close(fifos->client)) |
| log_sys_debug("close", fifos->client_path); |
| return 1; |
| } else if (errno != ENXIO && errno != ENOENT) { |
| /* problem */ |
| log_sys_error("open", fifos->client_path); |
| return 0; |
| } |
| |
| start_server: |
| /* server is not running */ |
| |
| if ((args[0][0] == '/') && stat(args[0], &statbuf)) { |
| log_sys_error("stat", args[0]); |
| return 0; |
| } |
| |
| pid = fork(); |
| |
| if (pid < 0) |
| log_sys_error("fork", ""); |
| |
| else if (!pid) { |
| execvp(args[0], args); |
| log_error("Unable to exec dmeventd: %s.", strerror(errno)); |
| _exit(EXIT_FAILURE); |
| } else { |
| if (waitpid(pid, &status, 0) < 0) |
| log_error("Unable to start dmeventd: %s.", |
| strerror(errno)); |
| else if (WEXITSTATUS(status)) |
| log_error("Unable to start dmeventd."); |
| else |
| ret = 1; |
| } |
| |
| return ret; |
| } |
| |
| int init_fifos(struct dm_event_fifos *fifos) |
| { |
| /* FIXME? Is fifo the most suitable method? Why not share |
| comms/daemon code with something else e.g. multipath? */ |
| |
| /* Open the fifo used to read from the daemon. */ |
| if ((fifos->server = open(fifos->server_path, O_RDWR)) < 0) { |
| log_sys_error("open", fifos->server_path); |
| return 0; |
| } |
| |
| /* Lock out anyone else trying to do communication with the daemon. */ |
| if (flock(fifos->server, LOCK_EX) < 0) { |
| log_sys_error("flock", fifos->server_path); |
| goto bad; |
| } |
| |
| /* if ((fifos->client = open(fifos->client_path, O_WRONLY | O_NONBLOCK)) < 0) {*/ |
| if ((fifos->client = open(fifos->client_path, O_RDWR | O_NONBLOCK)) < 0) { |
| log_sys_error("open", fifos->client_path); |
| goto bad; |
| } |
| |
| return 1; |
| bad: |
| if (close(fifos->server)) |
| log_sys_debug("close", fifos->server_path); |
| fifos->server = -1; |
| |
| return 0; |
| } |
| |
| /* Initialize client. */ |
| static int _init_client(char *dmeventd_path, struct dm_event_fifos *fifos) |
| { |
| if (!_start_daemon(dmeventd_path, fifos)) |
| return_0; |
| |
| return init_fifos(fifos); |
| } |
| |
| void fini_fifos(struct dm_event_fifos *fifos) |
| { |
| if (fifos->client >= 0 && close(fifos->client)) |
| log_sys_debug("close", fifos->client_path); |
| |
| if (fifos->server >= 0) { |
| if (flock(fifos->server, LOCK_UN)) |
| log_sys_debug("flock unlock", fifos->server_path); |
| |
| if (close(fifos->server)) |
| log_sys_debug("close", fifos->server_path); |
| } |
| } |
| |
| /* Get uuid of a device */ |
| static struct dm_task *_get_device_info(const struct dm_event_handler *dmevh) |
| { |
| struct dm_task *dmt; |
| struct dm_info info; |
| |
| if (!(dmt = dm_task_create(DM_DEVICE_INFO))) { |
| log_error("_get_device_info: dm_task creation for info failed."); |
| return NULL; |
| } |
| |
| if (dmevh->uuid) { |
| if (!dm_task_set_uuid(dmt, dmevh->uuid)) |
| goto_bad; |
| } else if (dmevh->dev_name) { |
| if (!dm_task_set_name(dmt, dmevh->dev_name)) |
| goto_bad; |
| } else if (dmevh->major && dmevh->minor) { |
| if (!dm_task_set_major(dmt, dmevh->major) || |
| !dm_task_set_minor(dmt, dmevh->minor)) |
| goto_bad; |
| } |
| |
| /* FIXME Add name or uuid or devno to messages */ |
| if (!dm_task_run(dmt)) { |
| log_error("_get_device_info: dm_task_run() failed."); |
| goto bad; |
| } |
| |
| if (!dm_task_get_info(dmt, &info)) { |
| log_error("_get_device_info: failed to get info for device."); |
| goto bad; |
| } |
| |
| if (!info.exists) { |
| log_error("_get_device_info: %s%s%s%.0d%s%.0d%s%s: device not found.", |
| dmevh->uuid ? : "", |
| (!dmevh->uuid && dmevh->dev_name) ? dmevh->dev_name : "", |
| (!dmevh->uuid && !dmevh->dev_name && dmevh->major > 0) ? "(" : "", |
| (!dmevh->uuid && !dmevh->dev_name && dmevh->major > 0) ? dmevh->major : 0, |
| (!dmevh->uuid && !dmevh->dev_name && dmevh->major > 0) ? ":" : "", |
| (!dmevh->uuid && !dmevh->dev_name && dmevh->minor > 0) ? dmevh->minor : 0, |
| (!dmevh->uuid && !dmevh->dev_name && dmevh->major > 0) && dmevh->minor == 0 ? "0" : "", |
| (!dmevh->uuid && !dmevh->dev_name && dmevh->major > 0) ? ") " : ""); |
| goto bad; |
| } |
| |
| return dmt; |
| |
| bad: |
| dm_task_destroy(dmt); |
| return NULL; |
| } |
| |
| /* Handle the event (de)registration call and return negative error codes. */ |
| static int _do_event(int cmd, char *dmeventd_path, struct dm_event_daemon_message *msg, |
| const char *dso_name, const char *dev_name, |
| enum dm_event_mask evmask, uint32_t timeout) |
| { |
| int ret; |
| 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 |
| }; |
| |
| if (!_init_client(dmeventd_path, &fifos)) { |
| ret = -ESRCH; |
| goto_out; |
| } |
| |
| ret = daemon_talk(&fifos, msg, DM_EVENT_CMD_HELLO, NULL, NULL, 0, 0); |
| |
| dm_free(msg->data); |
| msg->data = 0; |
| |
| if (!ret) |
| ret = daemon_talk(&fifos, msg, cmd, dso_name, dev_name, evmask, timeout); |
| out: |
| /* what is the opposite of init? */ |
| fini_fifos(&fifos); |
| |
| return ret; |
| } |
| |
| /* External library interface. */ |
| int dm_event_register_handler(const struct dm_event_handler *dmevh) |
| { |
| int ret = 1, err; |
| const char *uuid; |
| struct dm_task *dmt; |
| struct dm_event_daemon_message msg = { 0 }; |
| |
| if (!(dmt = _get_device_info(dmevh))) |
| return_0; |
| |
| uuid = dm_task_get_uuid(dmt); |
| |
| if (!strstr(dmevh->dso, "libdevmapper-event-lvm2thin.so") && |
| !strstr(dmevh->dso, "libdevmapper-event-lvm2snapshot.so") && |
| !strstr(dmevh->dso, "libdevmapper-event-lvm2mirror.so") && |
| !strstr(dmevh->dso, "libdevmapper-event-lvm2raid.so")) |
| log_warn("WARNING: %s: dmeventd plugins are deprecated.", dmevh->dso); |
| |
| |
| if ((err = _do_event(DM_EVENT_CMD_REGISTER_FOR_EVENT, dmevh->dmeventd_path, &msg, |
| dmevh->dso, uuid, dmevh->mask, dmevh->timeout)) < 0) { |
| log_error("%s: event registration failed: %s.", |
| dm_task_get_name(dmt), |
| msg.data ? msg.data : strerror(-err)); |
| ret = 0; |
| } |
| |
| dm_free(msg.data); |
| |
| dm_task_destroy(dmt); |
| |
| return ret; |
| } |
| |
| int dm_event_unregister_handler(const struct dm_event_handler *dmevh) |
| { |
| int ret = 1, err; |
| const char *uuid; |
| struct dm_task *dmt; |
| struct dm_event_daemon_message msg = { 0 }; |
| |
| if (!(dmt = _get_device_info(dmevh))) |
| return_0; |
| |
| uuid = dm_task_get_uuid(dmt); |
| |
| if ((err = _do_event(DM_EVENT_CMD_UNREGISTER_FOR_EVENT, dmevh->dmeventd_path, &msg, |
| dmevh->dso, uuid, dmevh->mask, dmevh->timeout)) < 0) { |
| log_error("%s: event deregistration failed: %s.", |
| dm_task_get_name(dmt), |
| msg.data ? msg.data : strerror(-err)); |
| ret = 0; |
| } |
| |
| dm_free(msg.data); |
| |
| dm_task_destroy(dmt); |
| |
| return ret; |
| } |
| |
| /* Fetch a string off src and duplicate it into *dest. */ |
| /* FIXME: move to separate module to share with the daemon. */ |
| static char *_fetch_string(char **src, const int delimiter) |
| { |
| char *p, *ret; |
| |
| if ((p = strchr(*src, delimiter))) |
| *p = 0; |
| |
| if ((ret = dm_strdup(*src))) |
| *src += strlen(ret) + 1; |
| |
| if (p) |
| *p = delimiter; |
| |
| return ret; |
| } |
| |
| /* Parse a device message from the daemon. */ |
| static int _parse_message(struct dm_event_daemon_message *msg, char **dso_name, |
| char **uuid, enum dm_event_mask *evmask) |
| { |
| char *id; |
| char *p = msg->data; |
| |
| if ((id = _fetch_string(&p, ' ')) && |
| (*dso_name = _fetch_string(&p, ' ')) && |
| (*uuid = _fetch_string(&p, ' '))) { |
| *evmask = atoi(p); |
| dm_free(id); |
| return 0; |
| } |
| |
| dm_free(id); |
| return -ENOMEM; |
| } |
| |
| /* |
| * Returns 0 if handler found; error (-ENOMEM, -ENOENT) otherwise. |
| */ |
| int dm_event_get_registered_device(struct dm_event_handler *dmevh, int next) |
| { |
| int ret = 0; |
| const char *uuid = NULL; |
| char *reply_dso = NULL, *reply_uuid = NULL; |
| enum dm_event_mask reply_mask = 0; |
| struct dm_task *dmt = NULL; |
| struct dm_event_daemon_message msg = { 0 }; |
| struct dm_info info; |
| |
| if (!(dmt = _get_device_info(dmevh))) { |
| log_debug("Device does not exists (uuid=%s, name=%s, %d:%d).", |
| dmevh->uuid, dmevh->dev_name, |
| dmevh->major, dmevh->minor); |
| ret = -ENODEV; |
| goto fail; |
| } |
| |
| uuid = dm_task_get_uuid(dmt); |
| |
| /* FIXME Distinguish errors connecting to daemon */ |
| if (_do_event(next ? DM_EVENT_CMD_GET_NEXT_REGISTERED_DEVICE : |
| DM_EVENT_CMD_GET_REGISTERED_DEVICE, dmevh->dmeventd_path, |
| &msg, dmevh->dso, uuid, dmevh->mask, 0)) { |
| log_debug("%s: device not registered.", dm_task_get_name(dmt)); |
| ret = -ENOENT; |
| goto fail; |
| } |
| |
| /* FIXME this will probably horribly break if we get |
| ill-formatted reply */ |
| ret = _parse_message(&msg, &reply_dso, &reply_uuid, &reply_mask); |
| |
| dm_task_destroy(dmt); |
| dmt = NULL; |
| |
| dm_free(msg.data); |
| msg.data = NULL; |
| |
| _dm_event_handler_clear_dev_info(dmevh); |
| if (!reply_uuid) { |
| ret = -ENXIO; /* dmeventd probably gave us bogus uuid back */ |
| goto fail; |
| } |
| |
| if (!(dmevh->uuid = dm_strdup(reply_uuid))) { |
| ret = -ENOMEM; |
| goto fail; |
| } |
| |
| if (!(dmt = _get_device_info(dmevh))) { |
| ret = -ENXIO; /* dmeventd probably gave us bogus uuid back */ |
| goto fail; |
| } |
| |
| dm_event_handler_set_dso(dmevh, reply_dso); |
| dm_event_handler_set_event_mask(dmevh, reply_mask); |
| |
| dm_free(reply_dso); |
| reply_dso = NULL; |
| |
| dm_free(reply_uuid); |
| reply_uuid = NULL; |
| |
| if (!(dmevh->dev_name = dm_strdup(dm_task_get_name(dmt)))) { |
| ret = -ENOMEM; |
| goto fail; |
| } |
| |
| if (!dm_task_get_info(dmt, &info)) { |
| ret = -1; |
| goto fail; |
| } |
| |
| dmevh->major = info.major; |
| dmevh->minor = info.minor; |
| |
| dm_task_destroy(dmt); |
| |
| return ret; |
| |
| fail: |
| dm_free(msg.data); |
| dm_free(reply_dso); |
| dm_free(reply_uuid); |
| _dm_event_handler_clear_dev_info(dmevh); |
| if (dmt) |
| dm_task_destroy(dmt); |
| return ret; |
| } |
| |
| /* |
| * You can (and have to) call this at the stage of the protocol where |
| * daemon_talk(fifos, &msg, DM_EVENT_CMD_HELLO, NULL, NULL, 0, 0) |
| * |
| * would be normally sent. This call will parse the version reply from |
| * dmeventd, in addition to above call. It is not safe to call this at any |
| * other place in the protocol. |
| * |
| * This is an internal function, not exposed in the public API. |
| */ |
| |
| int dm_event_get_version(struct dm_event_fifos *fifos, int *version) { |
| char *p; |
| struct dm_event_daemon_message msg = { 0 }; |
| |
| if (daemon_talk(fifos, &msg, DM_EVENT_CMD_HELLO, NULL, NULL, 0, 0)) |
| return 0; |
| p = msg.data; |
| *version = 0; |
| |
| if (!p || !(p = strchr(p, ' '))) /* Message ID */ |
| return 0; |
| if (!(p = strchr(p + 1, ' '))) /* HELLO */ |
| return 0; |
| if ((p = strchr(p + 1, ' '))) /* HELLO, once more */ |
| *version = atoi(p); |
| |
| return 1; |
| } |
| |
| void dm_event_log_set(int debug_log_level, int use_syslog) |
| { |
| _debug_level = debug_log_level; |
| _use_syslog = use_syslog; |
| } |
| |
| void dm_event_log(const char *subsys, int level, const char *file, |
| int line, int dm_errno_or_class, |
| const char *format, va_list ap) |
| { |
| static pthread_mutex_t _log_mutex = PTHREAD_MUTEX_INITIALIZER; |
| static time_t start = 0; |
| const char *indent = ""; |
| FILE *stream = stdout; |
| int prio; |
| time_t now; |
| |
| switch (level & ~(_LOG_STDERR | _LOG_ONCE)) { |
| case _LOG_DEBUG: |
| if (_debug_level < 3) |
| return; |
| prio = LOG_DEBUG; |
| indent = " "; |
| break; |
| case _LOG_INFO: |
| if (_debug_level < 2) |
| return; |
| prio = LOG_INFO; |
| indent = " "; |
| break; |
| case _LOG_NOTICE: |
| if (_debug_level < 1) |
| return; |
| prio = LOG_NOTICE; |
| indent = " "; |
| break; |
| case _LOG_WARN: |
| prio = LOG_WARNING; |
| break; |
| case _LOG_ERR: |
| prio = LOG_ERR; |
| stream = stderr; |
| break; |
| default: |
| prio = LOG_CRIT; |
| } |
| |
| /* Serialize to keep lines readable */ |
| pthread_mutex_lock(&_log_mutex); |
| |
| if (_use_syslog) { |
| vsyslog(prio, format, ap); |
| } else { |
| now = time(NULL); |
| if (!start) |
| start = now; |
| now -= start; |
| fprintf(stream, "[%2d:%02d] %8x:%-6s%s", |
| (int)now / 60, (int)now % 60, |
| // TODO: Maybe use shorter ID |
| // ((int)(pthread_self()) >> 6) & 0xffff, |
| (int)pthread_self(), subsys, |
| (_debug_level > 3) ? "" : indent); |
| if (_debug_level > 3) |
| fprintf(stream, "%28s:%4d %s", file, line, indent); |
| vfprintf(stream, _(format), ap); |
| fputc('\n', stream); |
| fflush(stream); |
| } |
| |
| pthread_mutex_unlock(&_log_mutex); |
| } |
| |
| #if 0 /* left out for now */ |
| |
| static char *_skip_string(char *src, const int delimiter) |
| { |
| src = srtchr(src, delimiter); |
| if (src && *(src + 1)) |
| return src + 1; |
| return NULL; |
| } |
| |
| int dm_event_set_timeout(const char *device_path, uint32_t timeout) |
| { |
| struct dm_event_daemon_message msg = { 0 }; |
| |
| if (!device_exists(device_path)) |
| return -ENODEV; |
| |
| return _do_event(DM_EVENT_CMD_SET_TIMEOUT, &msg, |
| NULL, device_path, 0, timeout); |
| } |
| |
| int dm_event_get_timeout(const char *device_path, uint32_t *timeout) |
| { |
| int ret; |
| struct dm_event_daemon_message msg = { 0 }; |
| |
| if (!device_exists(device_path)) |
| return -ENODEV; |
| |
| if (!(ret = _do_event(DM_EVENT_CMD_GET_TIMEOUT, &msg, NULL, device_path, |
| 0, 0))) { |
| char *p = _skip_string(msg.data, ' '); |
| if (!p) { |
| log_error("Malformed reply from dmeventd '%s'.", |
| msg.data); |
| dm_free(msg.data); |
| return -EIO; |
| } |
| *timeout = atoi(p); |
| } |
| dm_free(msg.data); |
| |
| return ret; |
| } |
| #endif |