/* * 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 "libdevmapper-event.h" #include "dmeventd.h" #include "libdm/misc/dm-logging.h" #include "base/memory/zalloc.h" #include "lib/misc/intl.h" #include #include #include #include #include #include /* for htonl, ntohl */ #include #include #include 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) { free(dmevh->dev_name); 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 = 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); free(dmevh->dso); free(dmevh->dmeventd_path); free(dmevh); } int dm_event_handler_set_dmeventd_path(struct dm_event_handler *dmevh, const char *dmeventd_path) { if (!dmeventd_path) /* noop */ return 0; free(dmevh->dmeventd_path); if (!(dmevh->dmeventd_path = 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; free(dmevh->dso); if (!(dmevh->dso = 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 = 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 = 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."); goto bad; } if ((ret == 0) && (i > 4) && !bytes) { log_error("No input from event server."); goto bad; } } if (ret < 1) { log_error("Unable to read from event server."); goto bad; } ret = read(fifos->server, buf + bytes, size); if (ret < 0) { if ((errno == EINTR) || (errno == EAGAIN)) continue; log_error("Unable to read from event server."); goto bad; } bytes += ret; if (!msg->data && (bytes == 2 * sizeof(uint32_t))) { msg->cmd = ntohl(header[0]); bytes = 0; if (!(size = msg->size = ntohl(header[1]))) break; if (!(buf = msg->data = malloc(msg->size))) { log_error("Unable to allocate message data."); return 0; } } } if (bytes == size) return 1; bad: free(msg->data); msg->data = NULL; return 0; } /* 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; 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; free(msg->data); msg->data = NULL; return -EIO; } do { 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; } 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 = { .client = -1, .server = -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); 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-lvm2vdo.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; } 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; } 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; size_t len = (p = strchr(*src, delimiter)) ? (size_t)(p - *src) : strlen(*src); if ((ret = strndup(*src, len))) *src += len + 1; 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); free(id); return 0; } 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 ((ret = _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)); 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; 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 = 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); free(reply_dso); reply_dso = NULL; free(reply_uuid); reply_uuid = NULL; if (!(dmevh->dev_name = 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: free(msg.data); free(reply_dso); 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 int _abort_on_internal_errors = -1; static pthread_mutex_t _log_mutex = PTHREAD_MUTEX_INITIALIZER; static time_t start = 0; const char *indent = ""; FILE *stream = log_stderr(level) ? stderr : stdout; int prio; time_t now; int log_with_debug = 0; if (subsys[0] == '#') { /* Subsystems starting with '#' are logged * only when debugging is enabled. */ log_with_debug++; subsys++; } switch (log_level(level)) { case _LOG_DEBUG: /* Never shown without -ddd */ if (_debug_level < 3) return; prio = LOG_DEBUG; indent = " "; break; case _LOG_INFO: if (log_with_debug && _debug_level < 2) return; prio = LOG_INFO; indent = " "; break; case _LOG_NOTICE: if (log_with_debug && _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; if (_debug_level) 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 (_abort_on_internal_errors < 0) /* Set when env DM_ABORT_ON_INTERNAL_ERRORS is not "0" */ _abort_on_internal_errors = strcmp(getenv("DM_ABORT_ON_INTERNAL_ERRORS") ? : "0", "0"); if (_abort_on_internal_errors && !strncmp(format, INTERNAL_ERROR, sizeof(INTERNAL_ERROR) - 1)) abort(); } #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); free(msg.data); return -EIO; } *timeout = atoi(p); } free(msg.data); return ret; } #endif