On Thu, Sep 14, 2023 at 04:51:30PM +0200, mwi...@suse.com wrote:
> From: Martin Wilck <mwi...@suse.com>
> 
> Since "libmultipath: keep bindings in memory", we don't re-read the
> bindings file after every modification. Add a notification mechanism
> that makes multipathd aware of changes to the bindings file. Because
> multipathd itself will change the bindings file, it must compare
> timestamps in order to avoid reading the file repeatedly.
> 
> Because select_alias() can be called from multiple thread contexts (uxlsnr,
> uevent handler), we need to add locking for the bindings file. The
> timestamp must also be protected by a lock, because it can't be read
> or written atomically.
> 
> Note: The notification mechanism expects the bindings file to be
> atomically replaced by rename(2). Changes must be made in a temporary file and
> applied using rename(2), as in update_bindings_file(). The inotify
> mechanism deliberately does not listen to close-after-write events
> that would be generated by editing the bindings file directly. This
> 
> Note also: new bindings will only be read from add_map_with_path(),
> i.e. either during reconfigure(), or when a new map is created during
> runtime. Existing maps will not be renamed if the binding file changes,
> unless the user runs "multipathd reconfigure". This is not a change
> wrt the previous code, but it should be mentioned anyway.
> 
Reviewed-by: Benjamin Marzinski <bmarz...@redhat.com>
> Signed-off-by: Martin Wilck <mwi...@suse.com>
> 
> libmultipath: protect global_bindings with a mutex
> 
> Signed-off-by: Martin Wilck <mwi...@suse.com>
> 
> libmultipath: check timestamp of bindings file before reading it
> 
> Signed-off-by: Martin Wilck <mwi...@suse.com>
> ---
>  libmultipath/alias.c              | 252 +++++++++++++++++++++++++-----
>  libmultipath/alias.h              |   3 +-
>  libmultipath/libmultipath.version |   5 +
>  multipathd/uxlsnr.c               |  36 ++++-
>  tests/alias.c                     |   3 +
>  5 files changed, 254 insertions(+), 45 deletions(-)
> 
> diff --git a/libmultipath/alias.c b/libmultipath/alias.c
> index 66e34e3..964b8a7 100644
> --- a/libmultipath/alias.c
> +++ b/libmultipath/alias.c
> @@ -10,6 +10,7 @@
>  #include <stdio.h>
>  #include <stdbool.h>
>  #include <assert.h>
> +#include <sys/inotify.h>
>  
>  #include "debug.h"
>  #include "util.h"
> @@ -22,6 +23,7 @@
>  #include "config.h"
>  #include "devmapper.h"
>  #include "strbuf.h"
> +#include "time-util.h"
>  
>  /*
>   * significant parts of this file were taken from iscsi-bindings.c of the
> @@ -50,6 +52,12 @@
>  "# alias wwid\n" \
>  "#\n"
>  
> +/* uatomic access only */
> +static int bindings_file_changed = 1;
> +
> +static pthread_mutex_t timestamp_mutex = PTHREAD_MUTEX_INITIALIZER;
> +static struct timespec bindings_last_updated;
> +
>  struct binding {
>       char *alias;
>       char *wwid;
> @@ -60,6 +68,9 @@ struct binding {
>   * an abstract type.
>   */
>  typedef struct _vector Bindings;
> +
> +/* Protect global_bindings */
> +static pthread_mutex_t bindings_mutex = PTHREAD_MUTEX_INITIALIZER;
>  static Bindings global_bindings = { .allocated = 0 };
>  
>  enum {
> @@ -78,6 +89,27 @@ static void _free_binding(struct binding *bdg)
>       free(bdg);
>  }
>  
> +static void free_bindings(Bindings *bindings)
> +{
> +     struct binding *bdg;
> +     int i;
> +
> +     vector_foreach_slot(bindings, bdg, i)
> +             _free_binding(bdg);
> +     vector_reset(bindings);
> +}
> +
> +static void set_global_bindings(Bindings *bindings)
> +{
> +     Bindings old_bindings;
> +
> +     pthread_mutex_lock(&bindings_mutex);
> +     old_bindings = global_bindings;
> +     global_bindings = *bindings;
> +     pthread_mutex_unlock(&bindings_mutex);
> +     free_bindings(&old_bindings);
> +}
> +
>  static const struct binding *get_binding_for_alias(const Bindings *bindings,
>                                                  const char *alias)
>  {
> @@ -199,7 +231,8 @@ static int delete_binding(Bindings *bindings, const char 
> *wwid)
>       return BINDING_DELETED;
>  }
>  
> -static int write_bindings_file(const Bindings *bindings, int fd)
> +static int write_bindings_file(const Bindings *bindings, int fd,
> +                            struct timespec *ts)
>  {
>       struct binding *bnd;
>       STRBUF_ON_STACK(content);
> @@ -227,9 +260,56 @@ static int write_bindings_file(const Bindings *bindings, 
> int fd)
>               }
>               len -= n;
>       }
> +     fsync(fd);
> +     if (ts) {
> +             struct stat st;
> +
> +             if (fstat(fd, &st) == 0)
> +                     *ts = st.st_mtim;
> +             else
> +                     clock_gettime(CLOCK_REALTIME_COARSE, ts);
> +     }
>       return 0;
>  }
>  
> +void handle_bindings_file_inotify(const struct inotify_event *event)
> +{
> +     struct config *conf;
> +     const char *base;
> +     bool changed = false;
> +     struct stat st;
> +     struct timespec ts = { 0, 0 };
> +     int ret;
> +
> +     if (!(event->mask & IN_MOVED_TO))
> +             return;
> +
> +     conf = get_multipath_config();
> +     base = strrchr(conf->bindings_file, '/');
> +     changed = base && base > conf->bindings_file &&
> +             !strcmp(base + 1, event->name);
> +     ret = stat(conf->bindings_file, &st);
> +     put_multipath_config(conf);
> +
> +     if (!changed)
> +             return;
> +
> +     pthread_mutex_lock(&timestamp_mutex);
> +     if (ret == 0) {
> +             ts = st.st_mtim;
> +             changed = timespeccmp(&ts, &bindings_last_updated) > 0;
> +     }
> +     pthread_mutex_unlock(&timestamp_mutex);
> +
> +     if (changed) {
> +             uatomic_xchg(&bindings_file_changed, 1);
> +             condlog(3, "%s: bindings file must be re-read, new timestamp: 
> %ld.%06ld",
> +                     __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000);
> +     } else
> +             condlog(3, "%s: bindings file is up-to-date, timestamp: 
> %ld.%06ld",
> +                     __func__, (long)ts.tv_sec, (long)ts.tv_nsec / 1000);
> +}
> +
>  static int update_bindings_file(const Bindings *bindings,
>                               const char *bindings_file)
>  {
> @@ -237,6 +317,7 @@ static int update_bindings_file(const Bindings *bindings,
>       int fd = -1;
>       char tempname[PATH_MAX];
>       mode_t old_umask;
> +     struct timespec ts;
>  
>       if (safe_sprintf(tempname, "%s.XXXXXX", bindings_file))
>               return -1;
> @@ -248,7 +329,7 @@ static int update_bindings_file(const Bindings *bindings,
>       }
>       umask(old_umask);
>       pthread_cleanup_push(cleanup_fd_ptr, &fd);
> -     rc = write_bindings_file(bindings, fd);
> +     rc = write_bindings_file(bindings, fd, &ts);
>       pthread_cleanup_pop(1);
>       if (rc == -1) {
>               condlog(1, "failed to write new bindings file");
> @@ -257,8 +338,12 @@ static int update_bindings_file(const Bindings *bindings,
>       }
>       if ((rc = rename(tempname, bindings_file)) == -1)
>               condlog(0, "%s: rename: %m", __func__);
> -     else
> +     else {
> +             pthread_mutex_lock(&timestamp_mutex);
> +             bindings_last_updated = ts;
> +             pthread_mutex_unlock(&timestamp_mutex);
>               condlog(1, "updated bindings file %s", bindings_file);
> +     }
>       return rc;
>  }
>  
> @@ -387,6 +472,7 @@ int get_free_id(const Bindings *bindings, const char 
> *prefix, const char *map_ww
>       return id;
>  }
>  
> +/* Called with binding_mutex held */
>  static char *
>  allocate_binding(const char *filename, const char *wwid, int id, const char 
> *prefix)
>  {
> @@ -423,6 +509,30 @@ allocate_binding(const char *filename, const char *wwid, 
> int id, const char *pre
>       return alias;
>  }
>  
> +enum {
> +     BINDINGS_FILE_UP2DATE,
> +     BINDINGS_FILE_READ,
> +     BINDINGS_FILE_ERROR,
> +     BINDINGS_FILE_BAD,
> +};
> +
> +static int _read_bindings_file(const struct config *conf, Bindings *bindings,
> +                            bool force);
> +
> +static void read_bindings_file(void)
> +{
> +     struct config *conf;
> +     Bindings bindings = {.allocated = 0, };
> +     int rc;
> +
> +     conf = get_multipath_config();
> +     pthread_cleanup_push(put_multipath_config, conf);
> +     rc = _read_bindings_file(conf, &bindings, false);
> +     pthread_cleanup_pop(1);
> +     if (rc == BINDINGS_FILE_READ)
> +             set_global_bindings(&bindings);
> +}
> +
>  /*
>   * get_user_friendly_alias() action table
>   *
> @@ -463,6 +573,11 @@ char *get_user_friendly_alias(const char *wwid, const 
> char *file, const char *al
>       bool new_binding = false;
>       const struct binding *bdg;
>  
> +     read_bindings_file();
> +
> +     pthread_mutex_lock(&bindings_mutex);
> +     pthread_cleanup_push(cleanup_mutex, &bindings_mutex);
> +
>       if (!*alias_old)
>               goto new_alias;
>  
> @@ -514,40 +629,40 @@ new_alias:
>                       alias, wwid);
>  
>  out:
> +     /* unlock bindings_mutex */
> +     pthread_cleanup_pop(1);
>       return alias;
>  }
>  
>  int get_user_friendly_wwid(const char *alias, char *buff)
>  {
>       const struct binding *bdg;
> +     int rc = -1;
>  
>       if (!alias || *alias == '\0') {
>               condlog(3, "Cannot find binding for empty alias");
>               return -1;
>       }
>  
> +     read_bindings_file();
> +
> +     pthread_mutex_lock(&bindings_mutex);
> +     pthread_cleanup_push(cleanup_mutex, &bindings_mutex);
>       bdg = get_binding_for_alias(&global_bindings, alias);
> -     if (!bdg) {
> +     if (bdg) {
> +             strlcpy(buff, bdg->wwid, WWID_SIZE);
> +             rc = 0;
> +     } else
>               *buff = '\0';
> -             return -1;
> -     }
> -     strlcpy(buff, bdg->wwid, WWID_SIZE);
> -     return 0;
> -}
> -
> -static void free_bindings(Bindings *bindings)
> -{
> -     struct binding *bdg;
> -     int i;
> -
> -     vector_foreach_slot(bindings, bdg, i)
> -             _free_binding(bdg);
> -     vector_reset(bindings);
> +     pthread_cleanup_pop(1);
> +     return rc;
>  }
>  
>  void cleanup_bindings(void)
>  {
> +     pthread_mutex_lock(&bindings_mutex);
>       free_bindings(&global_bindings);
> +     pthread_mutex_unlock(&bindings_mutex);
>  }
>  
>  enum {
> @@ -595,7 +710,20 @@ static int _check_bindings_file(const struct config 
> *conf, FILE *file,
>       char *line = NULL;
>       size_t line_len = 0;
>       ssize_t n;
> +     char header[sizeof(BINDINGS_FILE_HEADER)];
>  
> +     header[sizeof(BINDINGS_FILE_HEADER) - 1] = '\0';
> +     if (fread(header, sizeof(BINDINGS_FILE_HEADER) - 1, 1, file) < 1) {
> +             condlog(2, "%s: failed to read header from %s", __func__,
> +                     conf->bindings_file);
> +             fseek(file, 0, SEEK_SET);
> +             rc = -1;
> +     } else if (strcmp(header, BINDINGS_FILE_HEADER)) {
> +             condlog(2, "%s: invalid header in %s", __func__,
> +                     conf->bindings_file);
> +             fseek(file, 0, SEEK_SET);
> +             rc = -1;
> +     }
>       pthread_cleanup_push(cleanup_free_ptr, &line);
>       while ((n = getline(&line, &line_len, file)) >= 0) {
>               char *alias, *wwid;
> @@ -643,6 +771,68 @@ static int mp_alias_compar(const void *p1, const void 
> *p2)
>                           &((*(struct mpentry * const *)p2)->alias));
>  }
>  
> +static int _read_bindings_file(const struct config *conf, Bindings *bindings,
> +                            bool force)
> +{
> +     int can_write;
> +     int rc = 0, ret, fd;
> +     FILE *file;
> +     struct stat st;
> +     int has_changed = uatomic_xchg(&bindings_file_changed, 0);
> +
> +     if (!force) {
> +             if (!has_changed) {
> +                     condlog(4, "%s: bindings are unchanged", __func__);
> +                     return BINDINGS_FILE_UP2DATE;
> +             }
> +     }
> +
> +     fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER);
> +     if (fd == -1)
> +             return BINDINGS_FILE_ERROR;
> +
> +     file = fdopen(fd, "r");
> +     if (file != NULL) {
> +             condlog(3, "%s: reading %s", __func__, conf->bindings_file);
> +
> +             pthread_cleanup_push(cleanup_fclose, file);
> +             ret = _check_bindings_file(conf, file, bindings);
> +             if (ret == 0) {
> +                     struct timespec ts;
> +
> +                     rc = BINDINGS_FILE_READ;
> +                     ret = fstat(fd, &st);
> +                     if (ret == 0)
> +                             ts = st.st_mtim;
> +                     else {
> +                             condlog(1, "%s: fstat failed (%m), using 
> current time", __func__);
> +                             clock_gettime(CLOCK_REALTIME_COARSE, &ts);
> +                     }
> +                     pthread_mutex_lock(&timestamp_mutex);
> +                     bindings_last_updated = ts;
> +                     pthread_mutex_unlock(&timestamp_mutex);
> +             } else if (ret == -1 && can_write && !conf->bindings_read_only) 
> {
> +                     ret = update_bindings_file(bindings, 
> conf->bindings_file);
> +                     if (ret == 0)
> +                             rc = BINDINGS_FILE_READ;
> +                     else
> +                             rc = BINDINGS_FILE_BAD;
> +             } else {
> +                     condlog(0, "ERROR: bad settings in read-only bindings 
> file %s",
> +                             conf->bindings_file);
> +                     rc = BINDINGS_FILE_BAD;
> +             }
> +             pthread_cleanup_pop(1);
> +     } else {
> +             condlog(1, "failed to fdopen %s: %m",
> +                     conf->bindings_file);
> +             close(fd);
> +             rc = BINDINGS_FILE_ERROR;
> +     }
> +
> +     return rc;
> +}
> +
>  /*
>   * check_alias_settings(): test for inconsistent alias configuration
>   *
> @@ -661,8 +851,7 @@ static int mp_alias_compar(const void *p1, const void *p2)
>   */
>  int check_alias_settings(const struct config *conf)
>  {
> -     int can_write;
> -     int rc = 0, i, fd;
> +     int i, rc;
>       Bindings bindings = {.allocated = 0, };
>       vector mptable = NULL;
>       struct mpentry *mpe;
> @@ -695,27 +884,12 @@ int check_alias_settings(const struct config *conf)
>       pthread_cleanup_pop(1);
>       pthread_cleanup_pop(1);
>  
> -     fd = open_file(conf->bindings_file, &can_write, BINDINGS_FILE_HEADER);
> -     if (fd != -1) {
> -             FILE *file = fdopen(fd, "r");
> +     rc = _read_bindings_file(conf, &bindings, true);
>  
> -             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 = update_bindings_file(&bindings, 
> conf->bindings_file);
> -                     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);
> -             }
> +     if (rc == BINDINGS_FILE_READ) {
> +             set_global_bindings(&bindings);
> +             rc = 0;
>       }
>  
> -     cleanup_bindings();
> -     global_bindings = bindings;
>       return rc;
>  }
> diff --git a/libmultipath/alias.h b/libmultipath/alias.h
> index 5ef6720..ca8911f 100644
> --- a/libmultipath/alias.h
> +++ b/libmultipath/alias.h
> @@ -10,5 +10,6 @@ char *get_user_friendly_alias(const char *wwid, const char 
> *file,
>  struct config;
>  int check_alias_settings(const struct config *);
>  void cleanup_bindings(void);
> -
> +struct inotify_event;
> +void handle_bindings_file_inotify(const struct inotify_event *event);
>  #endif /* _ALIAS_H */
> diff --git a/libmultipath/libmultipath.version 
> b/libmultipath/libmultipath.version
> index ddd302f..57e50c1 100644
> --- a/libmultipath/libmultipath.version
> +++ b/libmultipath/libmultipath.version
> @@ -238,3 +238,8 @@ global:
>  local:
>       *;
>  };
> +
> +LIBMULTIPATH_20.1.0 {
> +global:
> +     handle_bindings_file_inotify;
> +};
> diff --git a/multipathd/uxlsnr.c b/multipathd/uxlsnr.c
> index 02e89fb..d1f8f23 100644
> --- a/multipathd/uxlsnr.c
> +++ b/multipathd/uxlsnr.c
> @@ -41,6 +41,7 @@
>  #include "cli.h"
>  #include "uxlsnr.h"
>  #include "strbuf.h"
> +#include "alias.h"
>  
>  /* state of client connection */
>  enum {
> @@ -190,6 +191,7 @@ void wakeup_cleanup(void *arg)
>  struct watch_descriptors {
>       int conf_wd;
>       int dir_wd;
> +     int mp_wd; /* /etc/multipath; for bindings file */
>  };
>  
>  /* failing to set the watch descriptor is o.k. we just miss a warning
> @@ -200,6 +202,8 @@ static void reset_watch(int notify_fd, struct 
> watch_descriptors *wds,
>       struct config *conf;
>       int dir_reset = 0;
>       int conf_reset = 0;
> +     int mp_reset = 0;
> +     char *bindings_file __attribute__((cleanup(cleanup_charp))) = NULL;
>  
>       if (notify_fd == -1)
>               return;
> @@ -214,7 +218,10 @@ static void reset_watch(int notify_fd, struct 
> watch_descriptors *wds,
>                       conf_reset = 1;
>               if (wds->dir_wd == -1)
>                       dir_reset = 1;
> +             if (wds->mp_wd == -1)
> +                     mp_reset = 1;
>       }
> +     bindings_file = strdup(conf->bindings_file);
>       put_multipath_config(conf);
>  
>       if (dir_reset) {
> @@ -235,7 +242,18 @@ static void reset_watch(int notify_fd, struct 
> watch_descriptors *wds,
>               if (wds->conf_wd == -1)
>                       condlog(3, "didn't set up notifications on 
> /etc/multipath.conf: %m");
>       }
> -     return;
> +     if (mp_reset && bindings_file) {
> +             char *slash = strrchr(bindings_file, '/');
> +
> +             if (slash && slash > bindings_file) {
> +                     *slash = '\0';
> +                     wds->mp_wd = inotify_add_watch(notify_fd, bindings_file,
> +                                                    IN_MOVED_TO|IN_ONLYDIR);
> +                     if (wds->mp_wd == -1)
> +                             condlog(3, "didn't set up notifications on %s: 
> %m",
> +                                     bindings_file);
> +             }
> +     }
>  }
>  
>  static void handle_inotify(int fd, struct watch_descriptors *wds)
> @@ -256,12 +274,13 @@ static void handle_inotify(int fd, struct 
> watch_descriptors *wds)
>                                       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;
> +                             if (wds->mp_wd != -1)
> +                                     inotify_rm_watch(fd, wds->mp_wd);
> +                             wds->conf_wd = wds->dir_wd = wds->mp_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;
> @@ -273,7 +292,13 @@ static void handle_inotify(int fd, struct 
> watch_descriptors *wds)
>                                       wds->conf_wd = 
> inotify_add_watch(notify_fd, DEFAULT_CONFIGFILE, IN_CLOSE_WRITE);
>                               else if (wds->dir_wd == event->wd)
>                                       wds->dir_wd = -1;
> +                             else if (wds->mp_wd == event->wd)
> +                                     wds->mp_wd = -1;
>                       }
> +                     if (wds->mp_wd != -1 && wds->mp_wd == event->wd)
> +                             handle_bindings_file_inotify(event);
> +                     else
> +                             got_notify = 1;
>               }
>       }
>       if (got_notify)
> @@ -599,7 +624,7 @@ void *uxsock_listen(long ux_sock, void *trigger_data)
>       int max_pfds = MIN_POLLS + POLLFDS_BASE;
>       /* 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 };
> +     struct watch_descriptors wds = { .conf_wd = -1, .dir_wd = -1, .mp_wd = 
> -1, };
>       struct vectors *vecs = trigger_data;
>  
>       condlog(3, "uxsock: startup listener");
> @@ -666,7 +691,8 @@ void *uxsock_listen(long ux_sock, void *trigger_data)
>  
>               reset_watch(notify_fd, &wds, &sequence_nr);
>               polls[POLLFD_NOTIFY].fd = notify_fd;
> -             if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1))
> +             if (notify_fd == -1 || (wds.conf_wd == -1 && wds.dir_wd == -1
> +                                     && wds.mp_wd == -1))
>                       polls[POLLFD_NOTIFY].events = 0;
>               else
>                       polls[POLLFD_NOTIFY].events = POLLIN;
> diff --git a/tests/alias.c b/tests/alias.c
> index 4d0adba..d41d396 100644
> --- a/tests/alias.c
> +++ b/tests/alias.c
> @@ -1954,6 +1954,9 @@ int main(void)
>       int ret = 0;
>       init_test_verbosity(3);
>  
> +     /* avoid open_file() call in _read_bindings_file */
> +     bindings_file_changed = 0;
> +
>       ret += test_format_devname();
>       ret += test_scan_devname();
>       ret += test_lookup_binding();
> -- 
> 2.42.0
--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel

Reply via email to