On Wed, Aug 19, 2020 at 03:18:16PM +0200, mwi...@suse.com wrote:
> From: Martin Wilck <mwi...@suse.com>
> 
> 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 <bmarz...@redhat.com>
> Signed-off-by: Martin Wilck <mwi...@suse.com>
> ---
>  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@redhat.com
https://www.redhat.com/mailman/listinfo/dm-devel

Reply via email to