On Wed, Aug 19, 2020 at 03:18:16PM +0200, mwilck@xxxxxxxx wrote: > From: Martin Wilck <mwilck@xxxxxxxx> > > A typo in a config file, assigning the same alias to multiple WWIDs, > can cause massive confusion and even data corruption. Check and > if possible fix the bindings file in such cases. > Reviewed-by: Benjamin Marzinski <bmarzins@xxxxxxxxxx> > Signed-off-by: Martin Wilck <mwilck@xxxxxxxx> > --- > libmultipath/alias.c | 265 +++++++++++++++++++++++++++++++++++++++++++ > libmultipath/alias.h | 3 + > multipath/main.c | 3 + > multipathd/main.c | 3 + > 4 files changed, 274 insertions(+) > > diff --git a/libmultipath/alias.c b/libmultipath/alias.c > index 0759c4e..df44bdc 100644 > --- a/libmultipath/alias.c > +++ b/libmultipath/alias.c > @@ -4,6 +4,7 @@ > */ > #include <stdlib.h> > #include <errno.h> > +#include <stdlib.h> > #include <unistd.h> > #include <string.h> > #include <limits.h> > @@ -17,6 +18,9 @@ > #include "vector.h" > #include "checkers.h" > #include "structs.h" > +#include "config.h" > +#include "util.h" > +#include "errno.h" > > > /* > @@ -438,3 +442,264 @@ get_user_friendly_wwid(const char *alias, char *buff, const char *file) > fclose(f); > return 0; > } > + > +struct binding { > + char *alias; > + char *wwid; > +}; > + > +static void _free_binding(struct binding *bdg) > +{ > + free(bdg->wwid); > + free(bdg->alias); > + free(bdg); > +} > + > +/* > + * Perhaps one day we'll implement this more efficiently, thus use > + * an abstract type. > + */ > +typedef struct _vector Bindings; > + > +static void free_bindings(Bindings *bindings) > +{ > + struct binding *bdg; > + int i; > + > + vector_foreach_slot(bindings, bdg, i) > + _free_binding(bdg); > + vector_reset(bindings); > +} > + > +enum { > + BINDING_EXISTS, > + BINDING_CONFLICT, > + BINDING_ADDED, > + BINDING_DELETED, > + BINDING_NOTFOUND, > + BINDING_ERROR, > +}; > + > +static int add_binding(Bindings *bindings, const char *alias, const char *wwid) > +{ > + struct binding *bdg; > + int i, cmp = 0; > + > + /* > + * Keep the bindings array sorted by alias. > + * Optimization: Search backwards, assuming that the bindings file is > + * sorted already. > + */ > + vector_foreach_slot_backwards(bindings, bdg, i) { > + if ((cmp = strcmp(bdg->alias, alias)) <= 0) > + break; > + } > + > + /* Check for exact match */ > + if (i >= 0 && cmp == 0) > + return strcmp(bdg->wwid, wwid) ? > + BINDING_CONFLICT : BINDING_EXISTS; > + > + i++; > + bdg = calloc(1, sizeof(*bdg)); > + if (bdg) { > + bdg->wwid = strdup(wwid); > + bdg->alias = strdup(alias); > + if (bdg->wwid && bdg->alias && > + vector_insert_slot(bindings, i, bdg)) > + return BINDING_ADDED; > + else > + _free_binding(bdg); > + } > + > + return BINDING_ERROR; > +} > + > +static int write_bindings_file(const Bindings *bindings, int fd) > +{ > + struct binding *bnd; > + char line[LINE_MAX]; > + int i; > + > + if (write(fd, BINDINGS_FILE_HEADER, sizeof(BINDINGS_FILE_HEADER) - 1) > + != sizeof(BINDINGS_FILE_HEADER) - 1) > + return -1; > + > + vector_foreach_slot(bindings, bnd, i) { > + int len; > + > + len = snprintf(line, sizeof(line), "%s %s\n", > + bnd->alias, bnd->wwid); > + > + if (len < 0 || (size_t)len >= sizeof(line)) { > + condlog(1, "%s: line overflow", __func__); > + return -1; > + } > + > + if (write(fd, line, len) != len) > + return -1; > + } > + return 0; > +} > + > +static int fix_bindings_file(const struct config *conf, > + const Bindings *bindings) > +{ > + int rc; > + long fd; > + char tempname[PATH_MAX]; > + > + if (safe_sprintf(tempname, "%s.XXXXXX", conf->bindings_file)) > + return -1; > + if ((fd = mkstemp(tempname)) == -1) { > + condlog(1, "%s: mkstemp: %m", __func__); > + return -1; > + } > + pthread_cleanup_push(close_fd, (void*)fd); > + rc = write_bindings_file(bindings, fd); > + pthread_cleanup_pop(1); > + if (rc == -1) { > + condlog(1, "failed to write new bindings file %s", > + tempname); > + unlink(tempname); > + return rc; > + } > + if ((rc = rename(tempname, conf->bindings_file)) == -1) > + condlog(0, "%s: rename: %m", __func__); > + else > + condlog(1, "updated bindings file %s", conf->bindings_file); > + return rc; > +} > + > +static int _check_bindings_file(const struct config *conf, FILE *file, > + Bindings *bindings) > +{ > + int rc = 0; > + unsigned int linenr = 0; > + char *line = NULL; > + size_t line_len = 0; > + ssize_t n; > + > + pthread_cleanup_push(free, line); > + while ((n = getline(&line, &line_len, file)) >= 0) { > + char *c, *alias, *wwid; > + const char *mpe_wwid; > + > + linenr++; > + c = strpbrk(line, "#\n\r"); > + if (c) > + *c = '\0'; > + alias = strtok(line, " \t"); > + if (!alias) /* blank line */ > + continue; > + wwid = strtok(NULL, " \t"); > + if (!wwid) { > + condlog(1, "invalid line %d in bindings file, missing WWID", > + linenr); > + continue; > + } > + c = strtok(NULL, " \t"); > + if (c) > + /* This is non-fatal */ > + condlog(1, "invalid line %d in bindings file, extra args \"%s\"", > + linenr, c); > + > + mpe_wwid = get_mpe_wwid(conf->mptable, alias); > + if (mpe_wwid && strcmp(mpe_wwid, wwid)) { > + condlog(0, "ERROR: alias \"%s\" for WWID %s in bindings file " > + "on line %u conflicts with multipath.conf entry for %s", > + alias, wwid, linenr, mpe_wwid); > + rc = -1; > + continue; > + } > + > + switch (add_binding(bindings, alias, wwid)) { > + case BINDING_CONFLICT: > + condlog(0, "ERROR: multiple bindings for alias \"%s\" in " > + "bindings file on line %u, discarding binding to WWID %s", > + alias, linenr, wwid); > + rc = -1; > + break; > + case BINDING_EXISTS: > + condlog(2, "duplicate line for alias %s in bindings file on line %u", > + alias, linenr); > + break; > + case BINDING_ERROR: > + condlog(2, "error adding binding %s -> %s", > + alias, wwid); > + break; > + default: > + break; > + } > + } > + pthread_cleanup_pop(1); > + return rc; > +} > + > +static void cleanup_fclose(void *p) > +{ > + fclose(p); > +} > + > +/* > + * check_alias_settings(): test for inconsistent alias configuration > + * > + * It's a fatal configuration error if the same alias is assigned to > + * multiple WWIDs. In the worst case, it can cause data corruption > + * by mangling devices with different WWIDs into the same multipath map. > + * This function tests the configuration from multipath.conf and the > + * bindings file for consistency, drops inconsistent multipath.conf > + * alias settings, and rewrites the bindings file if necessary, dropping > + * conflicting lines (if user_friendly_names is on, multipathd will > + * fill in the deleted lines with a newly generated alias later). > + * Note that multipath.conf is not rewritten. Use "multipath -T" for that. > + * > + * Returns: 0 in case of success, -1 if the configuration was bad > + * and couldn't be fixed. > + */ > +int check_alias_settings(const struct config *conf) > +{ > + int can_write; > + int rc = 0, i, fd; > + Bindings bindings = {.allocated = 0, }; > + struct mpentry *mpe; > + > + pthread_cleanup_push_cast(free_bindings, &bindings); > + vector_foreach_slot(conf->mptable, mpe, i) { > + if (!mpe->wwid || !mpe->alias) > + continue; > + if (add_binding(&bindings, mpe->alias, mpe->wwid) == > + BINDING_CONFLICT) { > + condlog(0, "ERROR: alias \"%s\" bound to multiple wwids in multipath.conf, " > + "discarding binding to %s", > + mpe->alias, mpe->wwid); > + free(mpe->alias); > + mpe->alias = NULL; > + } > + } > + /* This clears the bindings */ > + pthread_cleanup_pop(1); > + > + pthread_cleanup_push_cast(free_bindings, &bindings); > + fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER); > + if (fd != -1) { > + FILE *file = fdopen(fd, "r"); > + > + if (file != NULL) { > + pthread_cleanup_push(cleanup_fclose, file); > + rc = _check_bindings_file(conf, file, &bindings); > + pthread_cleanup_pop(1); > + if (rc == -1 && can_write && !conf->bindings_read_only) > + rc = fix_bindings_file(conf, &bindings); > + else if (rc == -1) > + condlog(0, "ERROR: bad settings in read-only bindings file %s", > + conf->bindings_file); > + } else { > + condlog(1, "failed to fdopen %s: %m", > + conf->bindings_file); > + close(fd); > + } > + } > + pthread_cleanup_pop(1); > + return rc; > +} > diff --git a/libmultipath/alias.h b/libmultipath/alias.h > index 236b3ba..dbc950c 100644 > --- a/libmultipath/alias.h > +++ b/libmultipath/alias.h > @@ -10,4 +10,7 @@ char *use_existing_alias (const char *wwid, const char *file, > const char *alias_old, > const char *prefix, int bindings_read_only); > > +struct config; > +int check_alias_settings(const struct config *); > + > #endif /* _ALIAS_H */ > diff --git a/multipath/main.c b/multipath/main.c > index 9e65070..80bc4b5 100644 > --- a/multipath/main.c > +++ b/multipath/main.c > @@ -64,6 +64,7 @@ > #include "time-util.h" > #include "file.h" > #include "valid.h" > +#include "alias.h" > > int logsink; > struct udev *udev; > @@ -958,6 +959,8 @@ main (int argc, char *argv[]) > exit(RTVL_FAIL); > } > > + check_alias_settings(conf); > + > if (optind < argc) { > dev = MALLOC(FILE_NAME_SIZE); > > diff --git a/multipathd/main.c b/multipathd/main.c > index 343ee95..9f12a57 100644 > --- a/multipathd/main.c > +++ b/multipathd/main.c > @@ -63,6 +63,7 @@ > #include "uevent.h" > #include "log.h" > #include "uxsock.h" > +#include "alias.h" > > #include "mpath_cmd.h" > #include "mpath_persist.h" > @@ -2717,6 +2718,8 @@ reconfigure (struct vectors * vecs) > conf->verbosity = verbosity; > if (bindings_read_only) > conf->bindings_read_only = bindings_read_only; > + check_alias_settings(conf); > + > uxsock_timeout = conf->uxsock_timeout; > > old = rcu_dereference(multipath_conf); > -- > 2.28.0 -- dm-devel mailing list dm-devel@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/dm-devel