/* * Original author : tridge@samba.org, January 2002 * * Copyright (c) 2005 Christophe Varoqui * Copyright (c) 2005 Benjamin Marzinski, Redhat */ /* * A simple domain socket listener */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "checkers.h" #include "memory.h" #include "debug.h" #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "uxsock.h" #include "defaults.h" #include "config.h" #include "mpath_cmd.h" #include "time-util.h" #include "main.h" #include "cli.h" #include "uxlsnr.h" static struct timespec sleep_time = {5, 0}; struct client { struct list_head node; int fd; }; #define MIN_POLLS 1023 static LIST_HEAD(clients); static pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER; static struct pollfd *polls; static int notify_fd = -1; static char *watch_config_dir; static bool _socket_client_is_root(int fd); static bool _socket_client_is_root(int fd) { socklen_t len = 0; struct ucred uc; len = sizeof(struct ucred); if ((fd >= 0) && (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &uc, &len) == 0) && (uc.uid == 0)) return true; /* Treat error as not root client */ return false; } /* * handle a new client joining */ static void new_client(int ux_sock) { struct client *c; struct sockaddr addr; socklen_t len = sizeof(addr); int fd; fd = accept(ux_sock, &addr, &len); if (fd == -1) return; c = (struct client *)MALLOC(sizeof(*c)); if (!c) { close(fd); return; } memset(c, 0, sizeof(*c)); INIT_LIST_HEAD(&c->node); c->fd = fd; /* put it in our linked list */ pthread_mutex_lock(&client_lock); list_add_tail(&c->node, &clients); pthread_mutex_unlock(&client_lock); } /* * kill off a dead client */ static void _dead_client(struct client *c) { int fd = c->fd; list_del_init(&c->node); c->fd = -1; FREE(c); close(fd); } static void dead_client(struct client *c) { pthread_cleanup_push(cleanup_lock, &client_lock); pthread_mutex_lock(&client_lock); _dead_client(c); pthread_cleanup_pop(1); } static void free_polls (void) { if (polls) FREE(polls); } static void check_timeout(struct timespec start_time, char *inbuf, unsigned int timeout) { struct timespec diff_time, end_time; if (start_time.tv_sec) { unsigned long msecs; get_monotonic_time(&end_time); timespecsub(&end_time, &start_time, &diff_time); msecs = diff_time.tv_sec * 1000 + diff_time.tv_nsec / (1000 * 1000); if (msecs > timeout) condlog(2, "cli cmd '%s' timeout reached " "after %lu.%06lu secs", inbuf, diff_time.tv_sec, diff_time.tv_nsec / 1000); } } void uxsock_cleanup(void *arg) { struct client *client_loop; struct client *client_tmp; long ux_sock = (long)arg; close(ux_sock); close(notify_fd); free(watch_config_dir); pthread_mutex_lock(&client_lock); list_for_each_entry_safe(client_loop, client_tmp, &clients, node) { _dead_client(client_loop); } pthread_mutex_unlock(&client_lock); cli_exit(); free_polls(); } struct watch_descriptors { int conf_wd; int dir_wd; }; /* failing to set the watch descriptor is o.k. we just miss a warning * message */ static void reset_watch(int notify_fd, struct watch_descriptors *wds, unsigned int *sequence_nr) { struct config *conf; int dir_reset = 0; int conf_reset = 0; if (notify_fd == -1) return; conf = get_multipath_config(); /* instead of repeatedly try to reset the inotify watch if * the config directory or multipath.conf isn't there, just * do it once per reconfigure */ if (*sequence_nr != conf->sequence_nr) { *sequence_nr = conf->sequence_nr; if (wds->conf_wd == -1) conf_reset = 1; if (!watch_config_dir || !conf->config_dir || strcmp(watch_config_dir, conf->config_dir)) { dir_reset = 1; if (watch_config_dir) free(watch_config_dir); if (conf->config_dir) watch_config_dir = strdup(conf->config_dir); else watch_config_dir = NULL; } else if (wds->dir_wd == -1) dir_reset = 1; } put_multipath_config(conf); if (dir_reset) { if (wds->dir_wd != -1) { inotify_rm_watch(notify_fd, wds->dir_wd); wds->dir_wd = -1; } if (watch_config_dir) { wds->dir_wd = inotify_add_watch(notify_fd, watch_config_dir, IN_CLOSE_WRITE | IN_DELETE | IN_ONLYDIR); if (wds->dir_wd == -1) condlog(3, "didn't set up notifications on %s: %m", watch_config_dir); } } if (conf_reset) { wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE); if (wds->conf_wd == -1) condlog(3, "didn't set up notifications on /etc/multipath.conf: %m"); } return; } static void handle_inotify(int fd, struct watch_descriptors *wds) { char buff[1024] __attribute__ ((aligned(__alignof__(struct inotify_event)))); const struct inotify_event *event; ssize_t len; char *ptr; int got_notify = 0; for (;;) { len = read(fd, buff, sizeof(buff)); if (len <= 0) { if (len < 0 && errno != EAGAIN) { condlog(3, "error reading from inotify_fd"); if (wds->conf_wd != -1) inotify_rm_watch(fd, wds->conf_wd); if (wds->dir_wd != -1) inotify_rm_watch(fd, wds->dir_wd); wds->conf_wd = wds->dir_wd = -1; } break; } got_notify = 1; for (ptr = buff; ptr < buff + len; ptr += sizeof(struct inotify_event) + event->len) { event = (const struct inotify_event *) ptr; if (event->mask & IN_IGNORED) { /* multipathd.conf may have been overwritten. * Try once to reset the notification */ if (wds->conf_wd == event->wd) wds->conf_wd = inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE); else if (wds->dir_wd == event->wd) wds->dir_wd = -1; } } } if (got_notify) condlog(1, "Multipath configuration updated.\nReload multipathd for changes to take effect"); } /* * entry point */ void * uxsock_listen(uxsock_trigger_fn uxsock_trigger, long ux_sock, void * trigger_data) { int rlen; char *inbuf; char *reply; sigset_t mask; int old_clients = MIN_POLLS; /* conf->sequence_nr will be 1 when uxsock_listen is first called */ unsigned int sequence_nr = 0; struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1 }; condlog(3, "uxsock: startup listener"); polls = (struct pollfd *)MALLOC((MIN_POLLS + 2) * sizeof(struct pollfd)); if (!polls) { condlog(0, "uxsock: failed to allocate poll fds"); exit_daemon(); } notify_fd = inotify_init1(IN_NONBLOCK); if (notify_fd == -1) /* it's fine if notifications fail */ condlog(3, "failed to start up configuration notifications"); sigfillset(&mask); sigdelset(&mask, SIGINT); sigdelset(&mask, SIGTERM); sigdelset(&mask, SIGHUP); sigdelset(&mask, SIGUSR1); while (1) { struct client *c, *tmp; int i, poll_count, num_clients; /* setup for a poll */ pthread_mutex_lock(&client_lock); num_clients = 0; list_for_each_entry(c, &clients, node) { num_clients++; } if (num_clients != old_clients) { struct pollfd *new; if (num_clients <= MIN_POLLS && old_clients > MIN_POLLS) { new = REALLOC(polls, (2 + MIN_POLLS) * sizeof(struct pollfd)); } else if (num_clients <= MIN_POLLS && old_clients <= MIN_POLLS) { new = polls; } else { new = REALLOC(polls, (2 + num_clients) * sizeof(struct pollfd)); } if (!new) { pthread_mutex_unlock(&client_lock); condlog(0, "%s: failed to realloc %d poll fds", "uxsock", 2 + num_clients); sched_yield(); continue; } old_clients = num_clients; polls = new; } polls[0].fd = ux_sock; polls[0].events = POLLIN; reset_watch(notify_fd, &wds, &sequence_nr); if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1)) polls[1].fd = -1; else polls[1].fd = notify_fd; polls[1].events = POLLIN; /* setup the clients */ i = 2; list_for_each_entry(c, &clients, node) { polls[i].fd = c->fd; polls[i].events = POLLIN; i++; } pthread_mutex_unlock(&client_lock); /* most of our life is spent in this call */ poll_count = ppoll(polls, i, &sleep_time, &mask); handle_signals(false); if (poll_count == -1) { if (errno == EINTR) { handle_signals(true); continue; } /* something went badly wrong! */ condlog(0, "uxsock: poll failed with %d", errno); exit_daemon(); break; } if (poll_count == 0) { handle_signals(true); continue; } /* * Client connection. We shouldn't answer while we're * configuring - nothing may be configured yet. * But we can't wait forever either, because this thread * must handle signals. So wait a short while only. */ if (wait_for_state_change_if(DAEMON_CONFIGURE, 10) == DAEMON_CONFIGURE) { handle_signals(false); continue; } /* see if a client wants to speak to us */ for (i = 2; i < num_clients + 2; i++) { if (polls[i].revents & POLLIN) { struct timespec start_time; c = NULL; pthread_mutex_lock(&client_lock); list_for_each_entry(tmp, &clients, node) { if (tmp->fd == polls[i].fd) { c = tmp; break; } } pthread_mutex_unlock(&client_lock); if (!c) { condlog(4, "cli%d: new fd %d", i, polls[i].fd); continue; } get_monotonic_time(&start_time); if (recv_packet_from_client(c->fd, &inbuf, uxsock_timeout) != 0) { dead_client(c); continue; } if (!inbuf) { condlog(4, "recv_packet_from_client " "get null request"); continue; } condlog(4, "cli[%d]: Got request [%s]", i, inbuf); uxsock_trigger(inbuf, &reply, &rlen, _socket_client_is_root(c->fd), trigger_data); if (reply) { if (send_packet(c->fd, reply) != 0) { dead_client(c); } else { condlog(4, "cli[%d]: " "Reply [%d bytes]", i, rlen); } FREE(reply); reply = NULL; } check_timeout(start_time, inbuf, uxsock_timeout); FREE(inbuf); } } /* see if we got a non-fatal signal */ handle_signals(true); /* see if we got a new client */ if (polls[0].revents & POLLIN) { new_client(ux_sock); } /* handle inotify events on config files */ if (polls[1].revents & POLLIN) handle_inotify(notify_fd, &wds); } return NULL; }