/* Copyright (c) 2018 Martin Wilck, SUSE Linux GmbH This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include "vector.h" #include "debug.h" #include "util.h" #include "foreign.h" #include "structs.h" #include "structs_vec.h" #include "print.h" static vector foreigns; /* This protects vector foreigns */ static pthread_rwlock_t foreign_lock = PTHREAD_RWLOCK_INITIALIZER; static void rdlock_foreigns(void) { pthread_rwlock_rdlock(&foreign_lock); } static void wrlock_foreigns(void) { pthread_rwlock_wrlock(&foreign_lock); } static void unlock_foreigns(__attribute__((unused)) void *unused) { pthread_rwlock_unlock(&foreign_lock); } #define get_dlsym(foreign, sym, lbl) \ do { \ foreign->sym = dlsym(foreign->handle, #sym); \ if (foreign->sym == NULL) { \ condlog(0, "%s: symbol \"%s\" not found in \"%s\"", \ __func__, #sym, foreign->name); \ goto lbl; \ } \ } while(0) static void free_foreign(struct foreign *fgn) { struct context *ctx; if (fgn == NULL) return; ctx = fgn->context; fgn->context = NULL; if (ctx != NULL) fgn->cleanup(ctx); if (fgn->handle != NULL) dlclose(fgn->handle); free(fgn); } void _cleanup_foreign(void) { struct foreign *fgn; int i; if (foreigns == NULL) return; vector_foreach_slot_backwards(foreigns, fgn, i) { vector_del_slot(foreigns, i); free_foreign(fgn); } vector_free(foreigns); foreigns = NULL; } void cleanup_foreign(void) { wrlock_foreigns(); _cleanup_foreign(); unlock_foreigns(NULL); } static const char foreign_pattern[] = "libforeign-*.so"; static int select_foreign_libs(const struct dirent *di) { return fnmatch(foreign_pattern, di->d_name, FNM_FILE_NAME) == 0; } static void free_pre(void *arg) { regex_t **pre = arg; if (pre != NULL && *pre != NULL) { regfree(*pre); free(*pre); *pre = NULL; } } static int _init_foreign(const char *multipath_dir, const char *enable) { char pathbuf[PATH_MAX]; struct dirent **di; struct scandir_result sr; int r, i; regex_t *enable_re = NULL; foreigns = vector_alloc(); if (foreigns == NULL) return -ENOMEM; pthread_cleanup_push(free_pre, &enable_re); enable_re = calloc(1, sizeof(*enable_re)); if (enable_re) { const char *str = enable ? enable : DEFAULT_ENABLE_FOREIGN; r = regcomp(enable_re, str, REG_EXTENDED|REG_NOSUB); if (r != 0) { char errbuf[64]; (void)regerror(r, enable_re, errbuf, sizeof(errbuf)); condlog (2, "%s: error compiling enable_foreign = \"%s\": \"%s\"", __func__, str, errbuf); goto out_free_pre; } } r = scandir(multipath_dir, &di, select_foreign_libs, alphasort); if (r == 0) { condlog(3, "%s: no foreign multipath libraries found", __func__); goto out_free_pre; } else if (r < 0) { r = -errno; condlog(1, "%s: error scanning foreign multipath libraries: %m", __func__); _cleanup_foreign(); goto out_free_pre; } sr.di = di; sr.n = r; pthread_cleanup_push_cast(free_scandir_result, &sr); for (i = 0; i < r; i++) { const char *msg, *fn, *c; struct foreign *fgn; size_t len, namesz; fn = di[i]->d_name; len = strlen(fn); c = strchr(fn, '-'); if (len < sizeof(foreign_pattern) - 1 || c == NULL) { condlog(0, "%s: bad file name %s, fnmatch error?", __func__, fn); continue; } c++; condlog(4, "%s: found %s", __func__, fn); namesz = len - sizeof(foreign_pattern) + 3; fgn = malloc(sizeof(*fgn) + namesz); if (fgn == NULL) continue; memset(fgn, 0, sizeof(*fgn)); strlcpy((char*)fgn + offsetof(struct foreign, name), c, namesz); if (enable_re != NULL) { int ret = regexec(enable_re, fgn->name, 0, NULL, 0); if (ret == REG_NOMATCH) { condlog(3, "%s: foreign library \"%s\" is not enabled", __func__, fgn->name); free(fgn); continue; } else if (ret != 0) /* assume it matches */ condlog(2, "%s: error %d in regexec() for %s", __func__, ret, fgn->name); } snprintf(pathbuf, sizeof(pathbuf), "%s/%s", multipath_dir, fn); fgn->handle = dlopen(pathbuf, RTLD_NOW|RTLD_LOCAL); msg = dlerror(); if (fgn->handle == NULL) { condlog(1, "%s: failed to dlopen %s: %s", __func__, pathbuf, msg); goto dl_err; } get_dlsym(fgn, init, dl_err); get_dlsym(fgn, cleanup, dl_err); get_dlsym(fgn, add, dl_err); get_dlsym(fgn, change, dl_err); get_dlsym(fgn, delete, dl_err); get_dlsym(fgn, delete_all, dl_err); get_dlsym(fgn, check, dl_err); get_dlsym(fgn, lock, dl_err); get_dlsym(fgn, unlock, dl_err); get_dlsym(fgn, get_multipaths, dl_err); get_dlsym(fgn, release_multipaths, dl_err); get_dlsym(fgn, get_paths, dl_err); get_dlsym(fgn, release_paths, dl_err); fgn->context = fgn->init(LIBMP_FOREIGN_API, fgn->name); if (fgn->context == NULL) { condlog(0, "%s: init() failed for %s", __func__, fn); goto dl_err; } if (vector_alloc_slot(foreigns) == NULL) { goto dl_err; } vector_set_slot(foreigns, fgn); condlog(3, "foreign library \"%s\" loaded successfully", fgn->name); continue; dl_err: free_foreign(fgn); } r = 0; pthread_cleanup_pop(1); /* free_scandir_result */ out_free_pre: pthread_cleanup_pop(1); /* free_pre */ return r; } int init_foreign(const char *multipath_dir, const char *enable) { int ret; wrlock_foreigns(); if (foreigns != NULL) { unlock_foreigns(NULL); condlog(0, "%s: already initialized", __func__); return -EEXIST; } pthread_cleanup_push(unlock_foreigns, NULL); ret = _init_foreign(multipath_dir, enable); pthread_cleanup_pop(1); return ret; } int add_foreign(struct udev_device *udev) { struct foreign *fgn; dev_t dt; int j; int r = FOREIGN_IGNORED; if (udev == NULL) { condlog(1, "%s called with NULL udev", __func__); return FOREIGN_ERR; } rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); dt = udev_device_get_devnum(udev); vector_foreach_slot(foreigns, fgn, j) { r = fgn->add(fgn->context, udev); if (r == FOREIGN_CLAIMED) { condlog(3, "%s: foreign \"%s\" claims device %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r == FOREIGN_OK) { condlog(4, "%s: foreign \"%s\" owns device %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r != FOREIGN_IGNORED) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return r; } int change_foreign(struct udev_device *udev) { struct foreign *fgn; int j; dev_t dt; int r = FOREIGN_IGNORED; if (udev == NULL) { condlog(1, "%s called with NULL udev", __func__); return FOREIGN_ERR; } rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); dt = udev_device_get_devnum(udev); vector_foreach_slot(foreigns, fgn, j) { r = fgn->change(fgn->context, udev); if (r == FOREIGN_OK) { condlog(4, "%s: foreign \"%s\" completed %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r != FOREIGN_IGNORED) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return r; } int delete_foreign(struct udev_device *udev) { struct foreign *fgn; int j; dev_t dt; int r = FOREIGN_IGNORED; if (udev == NULL) { condlog(1, "%s called with NULL udev", __func__); return FOREIGN_ERR; } rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); dt = udev_device_get_devnum(udev); vector_foreach_slot(foreigns, fgn, j) { r = fgn->delete(fgn->context, udev); if (r == FOREIGN_OK) { condlog(3, "%s: foreign \"%s\" deleted device %d:%d", __func__, fgn->name, major(dt), minor(dt)); break; } else if (r != FOREIGN_IGNORED) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return r; } int delete_all_foreign(void) { struct foreign *fgn; int j; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return FOREIGN_ERR; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, j) { int r; r = fgn->delete_all(fgn->context); if (r != FOREIGN_IGNORED && r != FOREIGN_OK) { condlog(1, "%s: unexpected return value %d from \"%s\"", __func__, r, fgn->name); } } pthread_cleanup_pop(1); return FOREIGN_OK; } void check_foreign(void) { struct foreign *fgn; int j; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, j) { fgn->check(fgn->context); } pthread_cleanup_pop(1); } /* Call this after get_path_layout */ void foreign_path_layout(void) { struct foreign *fgn; int i; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct _vector *vec; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_paths(fgn->context); if (vec != NULL) { _get_path_layout(vec, LAYOUT_RESET_NOT); } fgn->release_paths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); } /* Call this after get_multipath_layout */ void foreign_multipath_layout(void) { struct foreign *fgn; int i; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct _vector *vec; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_multipaths(fgn->context); if (vec != NULL) { _get_multipath_layout(vec, LAYOUT_RESET_NOT); } fgn->release_multipaths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); } int snprint_foreign_topology(char *buf, int len, int verbosity) { struct foreign *fgn; int i; char *c = buf; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return 0; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct _vector *vec; const struct gen_multipath *gm; int j; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_multipaths(fgn->context); if (vec != NULL) { vector_foreach_slot(vec, gm, j) { c += _snprint_multipath_topology(gm, c, buf + len - c, verbosity); if (c >= buf + len - 1) break; } if (c >= buf + len - 1) break; } fgn->release_multipaths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); return c - buf; } void print_foreign_topology(int verbosity) { int buflen = MAX_LINE_LEN * MAX_LINES; char *buf = NULL, *tmp = NULL; buf = malloc(buflen); buf[0] = '\0'; while (buf != NULL) { char *c = buf; c += snprint_foreign_topology(buf, buflen, verbosity); if (c < buf + buflen - 1) break; buflen *= 2; tmp = buf; buf = realloc(buf, buflen); } if (buf == NULL && tmp != NULL) buf = tmp; if (buf != NULL) { printf("%s", buf); free(buf); } } int snprint_foreign_paths(char *buf, int len, const char *style, int pretty) { struct foreign *fgn; int i; char *c = buf; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return 0; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct _vector *vec; const struct gen_path *gp; int j; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_paths(fgn->context); if (vec != NULL) { vector_foreach_slot(vec, gp, j) { c += _snprint_path(gp, c, buf + len - c, style, pretty); if (c >= buf + len - 1) break; } if (c >= buf + len - 1) break; } fgn->release_paths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); return c - buf; } int snprint_foreign_multipaths(char *buf, int len, const char *style, int pretty) { struct foreign *fgn; int i; char *c = buf; rdlock_foreigns(); if (foreigns == NULL) { unlock_foreigns(NULL); return 0; } pthread_cleanup_push(unlock_foreigns, NULL); vector_foreach_slot(foreigns, fgn, i) { const struct _vector *vec; const struct gen_multipath *gm; int j; fgn->lock(fgn->context); pthread_cleanup_push(fgn->unlock, fgn->context); vec = fgn->get_multipaths(fgn->context); if (vec != NULL) { vector_foreach_slot(vec, gm, j) { c += _snprint_multipath(gm, c, buf + len - c, style, pretty); if (c >= buf + len - 1) break; } if (c >= buf + len - 1) break; } fgn->release_multipaths(fgn->context, vec); pthread_cleanup_pop(1); } pthread_cleanup_pop(1); return c - buf; }