/* * Copyright (c) 2004, 2005 Christophe Varoqui * Copyright (c) 2005 Kiyoshi Ueda, NEC * Copyright (c) 2005 Edward Goggin, EMC * Copyright (c) 2005, 2018 Benjamin Marzinski, Redhat */ #include #include #include #include #include #include #include #include #include #include #include #include #include "vector.h" #include "structs.h" #include "structs_vec.h" #include "devmapper.h" #include "debug.h" #include "main.h" #include "dmevents.h" #include "util.h" #ifndef DM_DEV_ARM_POLL #define DM_DEV_ARM_POLL _IOWR(DM_IOCTL, DM_DEV_SET_GEOMETRY_CMD + 1, struct dm_ioctl) #endif enum event_actions { EVENT_NOTHING, EVENT_REMOVE, EVENT_UPDATE, }; struct dev_event { char name[WWID_SIZE]; uint32_t evt_nr; enum event_actions action; }; struct dmevent_waiter { int fd; struct vectors *vecs; vector events; pthread_mutex_t events_lock; }; static struct dmevent_waiter *waiter; /* * DM_VERSION_MINOR hasn't been updated when DM_DEV_ARM_POLL * was added in kernel 4.13. 4.37.0 (4.14) has it, safely. */ static const unsigned int DM_VERSION_FOR_ARM_POLL[] = {4, 37, 0}; int dmevent_poll_supported(void) { unsigned int v[3]; if (dm_drv_version(v)) return 0; if (VERSION_GE(v, DM_VERSION_FOR_ARM_POLL)) return 1; return 0; } int init_dmevent_waiter(struct vectors *vecs) { if (!vecs) { condlog(0, "can't create waiter structure. invalid vectors"); goto fail; } waiter = (struct dmevent_waiter *)malloc(sizeof(struct dmevent_waiter)); if (!waiter) { condlog(0, "failed to allocate waiter structure"); goto fail; } memset(waiter, 0, sizeof(struct dmevent_waiter)); waiter->events = vector_alloc(); if (!waiter->events) { condlog(0, "failed to allocate waiter events vector"); goto fail_waiter; } waiter->fd = open("/dev/mapper/control", O_RDWR); if (waiter->fd < 0) { condlog(0, "failed to open /dev/mapper/control for waiter"); goto fail_events; } pthread_mutex_init(&waiter->events_lock, NULL); waiter->vecs = vecs; return 0; fail_events: vector_free(waiter->events); fail_waiter: free(waiter); fail: waiter = NULL; return -1; } void cleanup_dmevent_waiter(void) { struct dev_event *dev_evt; int i; if (!waiter) return; pthread_mutex_destroy(&waiter->events_lock); close(waiter->fd); vector_foreach_slot(waiter->events, dev_evt, i) free(dev_evt); vector_free(waiter->events); free(waiter); waiter = NULL; } static int arm_dm_event_poll(int fd) { struct dm_ioctl dmi; memset(&dmi, 0, sizeof(dmi)); dmi.version[0] = DM_VERSION_FOR_ARM_POLL[0]; dmi.version[1] = DM_VERSION_FOR_ARM_POLL[1]; dmi.version[2] = DM_VERSION_FOR_ARM_POLL[2]; /* This flag currently does nothing. It simply exists to * duplicate the behavior of libdevmapper */ dmi.flags = 0x4; dmi.data_start = offsetof(struct dm_ioctl, data); dmi.data_size = sizeof(dmi); return ioctl(fd, DM_DEV_ARM_POLL, &dmi); } /* * As of version 4.37.0 device-mapper stores the event number in the * dm_names structure after the name, when DM_DEVICE_LIST is called */ static uint32_t dm_event_nr(struct dm_names *n) { return *(uint32_t *)(((uintptr_t)(strchr(n->name, 0) + 1) + 7) & ~7); } static int dm_get_events(void) { struct dm_task *dmt; struct dm_names *names; struct dev_event *dev_evt; int i; if (!(dmt = libmp_dm_task_create(DM_DEVICE_LIST))) return -1; dm_task_no_open_count(dmt); if (!dm_task_run(dmt)) goto fail; if (!(names = dm_task_get_names(dmt))) goto fail; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) dev_evt->action = EVENT_REMOVE; while (names->dev) { uint32_t event_nr; /* Don't delete device if dm_is_mpath() fails without * checking the device type */ if (dm_is_mpath(names->name) == 0) goto next; event_nr = dm_event_nr(names); vector_foreach_slot(waiter->events, dev_evt, i) { if (!strcmp(dev_evt->name, names->name)) { if (event_nr != dev_evt->evt_nr) { dev_evt->evt_nr = event_nr; dev_evt->action = EVENT_UPDATE; } else dev_evt->action = EVENT_NOTHING; break; } } next: if (!names->next) break; names = (void *)names + names->next; } pthread_mutex_unlock(&waiter->events_lock); dm_task_destroy(dmt); return 0; fail: dm_task_destroy(dmt); return -1; } /* You must call __setup_multipath() after calling this function, to * deal with any events that came in before the device was added */ int watch_dmevents(char *name) { int event_nr; struct dev_event *dev_evt, *old_dev_evt; int i; /* We know that this is a multipath device, so only fail if * device-mapper tells us that we're wrong */ if (dm_is_mpath(name) == 0) { condlog(0, "%s: not a multipath device. can't watch events", name); return -1; } if ((event_nr = dm_geteventnr(name)) < 0) return -1; dev_evt = (struct dev_event *)malloc(sizeof(struct dev_event)); if (!dev_evt) { condlog(0, "%s: can't allocate event waiter structure", name); return -1; } strlcpy(dev_evt->name, name, WWID_SIZE); dev_evt->evt_nr = event_nr; dev_evt->action = EVENT_NOTHING; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, old_dev_evt, i){ if (!strcmp(dev_evt->name, old_dev_evt->name)) { /* caller will be updating this device */ old_dev_evt->evt_nr = event_nr; old_dev_evt->action = EVENT_NOTHING; pthread_mutex_unlock(&waiter->events_lock); condlog(2, "%s: already waiting for events on device", name); free(dev_evt); return 0; } } if (!vector_alloc_slot(waiter->events)) { pthread_mutex_unlock(&waiter->events_lock); free(dev_evt); return -1; } vector_set_slot(waiter->events, dev_evt); pthread_mutex_unlock(&waiter->events_lock); return 0; } void unwatch_all_dmevents(void) { struct dev_event *dev_evt; int i; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) free(dev_evt); vector_reset(waiter->events); pthread_mutex_unlock(&waiter->events_lock); } static void unwatch_dmevents(char *name) { struct dev_event *dev_evt; int i; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) { if (!strcmp(dev_evt->name, name)) { vector_del_slot(waiter->events, i); free(dev_evt); break; } } pthread_mutex_unlock(&waiter->events_lock); } /* * returns the reschedule delay * negative means *stop* */ /* poll, arm, update, return */ static int dmevent_loop (void) { int r, i = 0; struct pollfd pfd; struct dev_event *dev_evt; pfd.fd = waiter->fd; pfd.events = POLLIN; r = poll(&pfd, 1, -1); if (r <= 0) { condlog(0, "failed polling for dm events: %s", strerror(errno)); /* sleep 1s and hope things get better */ return 1; } if (arm_dm_event_poll(waiter->fd) != 0) { condlog(0, "Cannot re-arm event polling: %s", strerror(errno)); /* sleep 1s and hope things get better */ return 1; } if (dm_get_events() != 0) { condlog(0, "failed getting dm events: %s", strerror(errno)); /* sleep 1s and hope things get better */ return 1; } /* * upon event ... */ while (1) { int done = 1; struct dev_event curr_dev; pthread_mutex_lock(&waiter->events_lock); vector_foreach_slot(waiter->events, dev_evt, i) { if (dev_evt->action != EVENT_NOTHING) { curr_dev = *dev_evt; if (dev_evt->action == EVENT_REMOVE) { vector_del_slot(waiter->events, i); free(dev_evt); } else dev_evt->action = EVENT_NOTHING; done = 0; break; } } pthread_mutex_unlock(&waiter->events_lock); if (done) return 1; condlog(3, "%s: devmap event #%i", curr_dev.name, curr_dev.evt_nr); /* * event might be : * * 1) a table reload, which means our mpp structure is * obsolete : refresh it through update_multipath() * 2) a path failed by DM : mark as such through * update_multipath() * 3) map has gone away : stop the thread. * 4) a path reinstate : nothing to do * 5) a switch group : nothing to do */ pthread_cleanup_push(cleanup_lock, &waiter->vecs->lock); lock(&waiter->vecs->lock); pthread_testcancel(); r = 0; if (curr_dev.action == EVENT_REMOVE) remove_map_by_alias(curr_dev.name, waiter->vecs, 1); else r = update_multipath(waiter->vecs, curr_dev.name, 1); pthread_cleanup_pop(1); if (r) { condlog(2, "%s: stopped watching dmevents", curr_dev.name); unwatch_dmevents(curr_dev.name); } } condlog(0, "dmevent waiter thread unexpectedly quit"); return -1; /* never reach there */ } static void rcu_unregister(__attribute__((unused)) void *param) { rcu_unregister_thread(); } void *wait_dmevents (__attribute__((unused)) void *unused) { int r; if (!waiter) { condlog(0, "dmevents waiter not intialized"); return NULL; } pthread_cleanup_push(rcu_unregister, NULL); rcu_register_thread(); mlockall(MCL_CURRENT | MCL_FUTURE); while (1) { r = dmevent_loop(); if (r < 0) break; sleep(r); } pthread_cleanup_pop(1); return NULL; }