From 374ad934a915767b1fee81a9362e3522833a5fa8 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Fri, 27 Jul 2012 15:55:24 -0500 Subject: [PATCH] multipath: check if a device belongs to multipath This patch adds a new multipath option "-c", which checks if a device belongs to multipath. This can be done during the add uevent for the device, before the multipath device has even been created. This allows udev to be able to handle multipath path devices differently. To do this multipath now keeps track of the wwids of all previously created multipath devices in /etc/multipath/wwids. The file creating and editting code from alias.[ch] has been split out into file.[ch]. When a device is checked to see if it's a multipath path, it's wwid is compared against the ones in the wwids file. Also, since the uid_attribute may not have been added to the udev database entry for the device if this is called in a udev rule, when using the "-c" option, get_uid will now also check the process' envirionment variables for the uid_attribute. Signed-off-by: Benjamin Marzinski --- libmultipath/Makefile | 2 +- libmultipath/alias.c | 153 +--------------------------------------- libmultipath/alias.h | 1 - libmultipath/configure.c | 3 + libmultipath/defaults.h | 1 + libmultipath/discovery.c | 2 + libmultipath/file.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++ libmultipath/file.h | 11 +++ libmultipath/wwids.c | 139 ++++++++++++++++++++++++++++++++++++ libmultipath/wwids.h | 18 +++++ multipath/main.c | 37 ++++++++-- 11 files changed, 389 insertions(+), 158 deletions(-) create mode 100644 libmultipath/file.c create mode 100644 libmultipath/file.h create mode 100644 libmultipath/wwids.c create mode 100644 libmultipath/wwids.h diff --git a/libmultipath/Makefile b/libmultipath/Makefile index b0513df..0395602 100644 --- a/libmultipath/Makefile +++ b/libmultipath/Makefile @@ -15,7 +15,7 @@ OBJS = memory.o parser.o vector.o devmapper.o \ pgpolicies.o debug.o regex.o defaults.o uevent.o \ switchgroup.o uxsock.o print.o alias.o log_pthread.o \ log.o configure.o structs_vec.o sysfs.o prio.o checkers.o \ - lock.o waiter.o + lock.o waiter.o file.o wwids.o LIBDM_API_FLUSH = $(shell grep -Ecs '^[a-z]*[[:space:]]+dm_task_no_flush' /usr/include/libdevmapper.h) diff --git a/libmultipath/alias.c b/libmultipath/alias.c index a6a06ad..e1f3073 100644 --- a/libmultipath/alias.c +++ b/libmultipath/alias.c @@ -3,19 +3,16 @@ * Copyright (c) 2005 Benjamin Marzinski, Redhat */ #include -#include -#include -#include #include #include #include #include #include -#include #include "debug.h" #include "uxsock.h" #include "alias.h" +#include "file.h" /* @@ -36,150 +33,6 @@ * See the file COPYING included with this distribution for more details. */ -static int -ensure_directories_exist(char *str, mode_t dir_mode) -{ - char *pathname; - char *end; - int err; - - pathname = strdup(str); - if (!pathname){ - condlog(0, "Cannot copy bindings file pathname : %s", - strerror(errno)); - return -1; - } - end = pathname; - /* skip leading slashes */ - while (end && *end && (*end == '/')) - end++; - - while ((end = strchr(end, '/'))) { - /* if there is another slash, make the dir. */ - *end = '\0'; - err = mkdir(pathname, dir_mode); - if (err && errno != EEXIST) { - condlog(0, "Cannot make directory [%s] : %s", - pathname, strerror(errno)); - free(pathname); - return -1; - } - if (!err) - condlog(3, "Created dir [%s]", pathname); - *end = '/'; - end++; - } - free(pathname); - return 0; -} - -static void -sigalrm(int sig) -{ - /* do nothing */ -} - -static int -lock_bindings_file(int fd) -{ - struct sigaction act, oldact; - sigset_t set, oldset; - struct flock lock; - int err; - - memset(&lock, 0, sizeof(lock)); - lock.l_type = F_WRLCK; - lock.l_whence = SEEK_SET; - - act.sa_handler = sigalrm; - sigemptyset(&act.sa_mask); - act.sa_flags = 0; - sigemptyset(&set); - sigaddset(&set, SIGALRM); - - sigaction(SIGALRM, &act, &oldact); - sigprocmask(SIG_UNBLOCK, &set, &oldset); - - alarm(BINDINGS_FILE_TIMEOUT); - err = fcntl(fd, F_SETLKW, &lock); - alarm(0); - - if (err) { - if (errno != EINTR) - condlog(0, "Cannot lock bindings file : %s", - strerror(errno)); - else - condlog(0, "Bindings file is locked. Giving up."); - } - - sigprocmask(SIG_SETMASK, &oldset, NULL); - sigaction(SIGALRM, &oldact, NULL); - return err; - -} - - -static int -open_bindings_file(char *file, int *can_write) -{ - int fd; - struct stat s; - - if (ensure_directories_exist(file, 0700)) - return -1; - *can_write = 1; - fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); - if (fd < 0) { - if (errno == EROFS) { - *can_write = 0; - condlog(3, "Cannot open bindings file [%s] read/write. " - " trying readonly", file); - fd = open(file, O_RDONLY); - if (fd < 0) { - condlog(0, "Cannot open bindings file [%s] " - "readonly : %s", file, strerror(errno)); - return -1; - } - } - else { - condlog(0, "Cannot open bindings file [%s] : %s", file, - strerror(errno)); - return -1; - } - } - if (*can_write && lock_bindings_file(fd) < 0) - goto fail; - - memset(&s, 0, sizeof(s)); - if (fstat(fd, &s) < 0){ - condlog(0, "Cannot stat bindings file : %s", strerror(errno)); - goto fail; - } - if (s.st_size == 0) { - if (*can_write == 0) - goto fail; - /* If bindings file is empty, write the header */ - size_t len = strlen(BINDINGS_FILE_HEADER); - if (write_all(fd, BINDINGS_FILE_HEADER, len) != len) { - condlog(0, - "Cannot write header to bindings file : %s", - strerror(errno)); - /* cleanup partially written header */ - if (ftruncate(fd, 0)) - condlog(0, "Cannot truncate the header : %s", - strerror(errno)); - goto fail; - } - fsync(fd); - condlog(3, "Initialized new bindings file [%s]", file); - } - - return fd; - -fail: - close(fd); - return -1; -} static int format_devname(char *name, int id, int len, char *prefix) @@ -370,7 +223,7 @@ get_user_friendly_alias(char *wwid, char *file, char *prefix, return NULL; } - fd = open_bindings_file(file, &can_write); + fd = open_file(file, &can_write, BINDINGS_FILE_HEADER); if (fd < 0) return NULL; @@ -414,7 +267,7 @@ get_user_friendly_wwid(char *alias, char *file) return NULL; } - fd = open_bindings_file(file, &unused); + fd = open_file(file, &unused, BINDINGS_FILE_HEADER); if (fd < 0) return NULL; diff --git a/libmultipath/alias.h b/libmultipath/alias.h index c489a86..c625090 100644 --- a/libmultipath/alias.h +++ b/libmultipath/alias.h @@ -1,4 +1,3 @@ -#define BINDINGS_FILE_TIMEOUT 30 #define BINDINGS_FILE_HEADER \ "# Multipath bindings, Version : 1.0\n" \ "# NOTE: this file is automatically maintained by the multipath program.\n" \ diff --git a/libmultipath/configure.c b/libmultipath/configure.c index 9977296..1bb45a3 100644 --- a/libmultipath/configure.c +++ b/libmultipath/configure.c @@ -37,6 +37,7 @@ #include "prio.h" #include "util.h" #include "uxsock.h" +#include "wwids.h" extern int setup_map (struct multipath * mpp, char * params, int params_size) @@ -415,6 +416,8 @@ domap (struct multipath * mpp, char * params) * DM_DEVICE_CREATE, DM_DEVICE_RENAME, or DM_DEVICE_RELOAD * succeeded */ + if (mpp->action == ACT_CREATE) + remember_wwid(mpp->wwid); if (!conf->daemon) { /* multipath client mode */ dm_switchgroup(mpp->alias, mpp->bestpg); diff --git a/libmultipath/defaults.h b/libmultipath/defaults.h index 7697195..74b236f 100644 --- a/libmultipath/defaults.h +++ b/libmultipath/defaults.h @@ -24,5 +24,6 @@ #define DEFAULT_SOCKET "/var/run/multipathd.sock" #define DEFAULT_CONFIGFILE "/etc/multipath.conf" #define DEFAULT_BINDINGS_FILE "/etc/multipath/bindings" +#define DEFAULT_WWIDS_FILE "/etc/multipath/wwids" char * set_default (char * str); diff --git a/libmultipath/discovery.c b/libmultipath/discovery.c index abc686f..3315b5e 100644 --- a/libmultipath/discovery.c +++ b/libmultipath/discovery.c @@ -810,6 +810,8 @@ get_uid (struct path * pp) memset(pp->wwid, 0, WWID_SIZE); value = udev_device_get_property_value(pp->udev, pp->uid_attribute); + if ((!value || strlen(value) == 0) && conf->dry_run == 2) + value = getenv(pp->uid_attribute); if (value && strlen(value)) { size_t len = WWID_SIZE; diff --git a/libmultipath/file.c b/libmultipath/file.c new file mode 100644 index 0000000..5ee46dc --- /dev/null +++ b/libmultipath/file.c @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2005 Christophe Varoqui + * Copyright (c) 2005 Benjamin Marzinski, Redhat + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "file.h" +#include "debug.h" +#include "uxsock.h" + + +/* + * significant parts of this file were taken from iscsi-bindings.c of the + * linux-iscsi project. + * Copyright (C) 2002 Cisco Systems, Inc. + * + * 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. + * + * See the file COPYING included with this distribution for more details. + */ + +static int +ensure_directories_exist(char *str, mode_t dir_mode) +{ + char *pathname; + char *end; + int err; + + pathname = strdup(str); + if (!pathname){ + condlog(0, "Cannot copy file pathname %s : %s", + str, strerror(errno)); + return -1; + } + end = pathname; + /* skip leading slashes */ + while (end && *end && (*end == '/')) + end++; + + while ((end = strchr(end, '/'))) { + /* if there is another slash, make the dir. */ + *end = '\0'; + err = mkdir(pathname, dir_mode); + if (err && errno != EEXIST) { + condlog(0, "Cannot make directory [%s] : %s", + pathname, strerror(errno)); + free(pathname); + return -1; + } + if (!err) + condlog(3, "Created dir [%s]", pathname); + *end = '/'; + end++; + } + free(pathname); + return 0; +} + +static void +sigalrm(int sig) +{ + /* do nothing */ +} + +static int +lock_file(int fd, char *file_name) +{ + struct sigaction act, oldact; + sigset_t set, oldset; + struct flock lock; + int err; + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + + act.sa_handler = sigalrm; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigemptyset(&set); + sigaddset(&set, SIGALRM); + + sigaction(SIGALRM, &act, &oldact); + sigprocmask(SIG_UNBLOCK, &set, &oldset); + + alarm(FILE_TIMEOUT); + err = fcntl(fd, F_SETLKW, &lock); + alarm(0); + + if (err) { + if (errno != EINTR) + condlog(0, "Cannot lock %s : %s", file_name, + strerror(errno)); + else + condlog(0, "%s is locked. Giving up.", file_name); + } + + sigprocmask(SIG_SETMASK, &oldset, NULL); + sigaction(SIGALRM, &oldact, NULL); + return err; +} + +int +open_file(char *file, int *can_write, char *header) +{ + int fd; + struct stat s; + + if (ensure_directories_exist(file, 0700)) + return -1; + *can_write = 1; + fd = open(file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fd < 0) { + if (errno == EROFS) { + *can_write = 0; + condlog(3, "Cannot open file [%s] read/write. " + " trying readonly", file); + fd = open(file, O_RDONLY); + if (fd < 0) { + condlog(0, "Cannot open file [%s] " + "readonly : %s", file, strerror(errno)); + return -1; + } + } + else { + condlog(0, "Cannot open file [%s] : %s", file, + strerror(errno)); + return -1; + } + } + if (*can_write && lock_file(fd, file) < 0) + goto fail; + + memset(&s, 0, sizeof(s)); + if (fstat(fd, &s) < 0){ + condlog(0, "Cannot stat file %s : %s", file, strerror(errno)); + goto fail; + } + if (s.st_size == 0) { + if (*can_write == 0) + goto fail; + /* If file is empty, write the header */ + size_t len = strlen(header); + if (write_all(fd, header, len) != len) { + condlog(0, + "Cannot write header to file %s : %s", file, + strerror(errno)); + /* cleanup partially written header */ + if (ftruncate(fd, 0)) + condlog(0, "Cannot truncate header : %s", + strerror(errno)); + goto fail; + } + fsync(fd); + condlog(3, "Initialized new file [%s]", file); + } + + return fd; + +fail: + close(fd); + return -1; +} diff --git a/libmultipath/file.h b/libmultipath/file.h new file mode 100644 index 0000000..4f96dbf --- /dev/null +++ b/libmultipath/file.h @@ -0,0 +1,11 @@ +/* + * Copyright (c) 2010 Benjamin Marzinski, Redhat + */ + +#ifndef _FILE_H +#define _FILE_H + +#define FILE_TIMEOUT 30 +int open_file(char *file, int *can_write, char *header); + +#endif /* _FILE_H */ diff --git a/libmultipath/wwids.c b/libmultipath/wwids.c new file mode 100644 index 0000000..ba1f5a5 --- /dev/null +++ b/libmultipath/wwids.c @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include + +#include "checkers.h" +#include "vector.h" +#include "structs.h" +#include "debug.h" +#include "uxsock.h" +#include "file.h" +#include "wwids.h" +#include "defaults.h" + +/* + * Copyright (c) 2010 Benjamin Marzinski, Redhat + */ + +static int +lookup_wwid(FILE *f, char *wwid) { + int c; + char buf[LINE_MAX]; + int count; + + while ((c = fgetc(f)) != EOF){ + if (c != '/') { + if (fgets(buf, LINE_MAX, f) == NULL) + return 0; + else + continue; + } + count = 0; + while ((c = fgetc(f)) != '/') { + if (c == EOF) + return 0; + if (count >= WWID_SIZE - 1) + goto next; + if (wwid[count] == '\0') + goto next; + if (c != wwid[count++]) + goto next; + } + if (wwid[count] == '\0') + return 1; +next: + if (fgets(buf, LINE_MAX, f) == NULL) + return 0; + } + return 0; +} + +static int +write_out_wwid(int fd, char *wwid) { + int ret; + off_t offset; + char buf[WWID_SIZE + 3]; + + ret = snprintf(buf, WWID_SIZE + 3, "/%s/\n", wwid); + if (ret >= (WWID_SIZE + 3) || ret < 0){ + condlog(0, "can't format wwid for writing (%d) : %s", + ret, strerror(errno)); + return -1; + } + offset = lseek(fd, 0, SEEK_END); + if (offset < 0) { + condlog(0, "can't seek to the end of wwids file : %s", + strerror(errno)); + return -1; + } + if (write_all(fd, buf, strlen(buf)) != strlen(buf)) { + condlog(0, "cannot write wwid to wwids file : %s", + strerror(errno)); + if (ftruncate(fd, offset)) + condlog(0, "cannot truncate failed wwid write : %s", + strerror(errno)); + return -1; + } + return 1; +} + +int +check_wwids_file(char *wwid, int write_wwid) +{ + int fd, can_write, found, ret; + FILE *f; + fd = open_file(DEFAULT_WWIDS_FILE, &can_write, WWIDS_FILE_HEADER); + if (fd < 0) + return -1; + + f = fdopen(fd, "r"); + if (!f) { + condlog(0,"can't fdopen wwids file : %s", strerror(errno)); + close(fd); + return -1; + } + found = lookup_wwid(f, wwid); + if (found) { + ret = 0; + goto out; + } + if (!write_wwid) { + ret = -1; + goto out; + } + if (!can_write) { + condlog(0, "wwids file is read-only. Can't write wwid"); + ret = -1; + goto out; + } + + if (fflush(f) != 0) { + condlog(0, "cannot fflush wwids file stream : %s", + strerror(errno)); + ret = -1; + goto out; + } + + ret = write_out_wwid(fd, wwid); +out: + fclose(f); + return ret; +} + +int +remember_wwid(char *wwid) +{ + int ret = check_wwids_file(wwid, 1); + if (ret < 0){ + condlog(3, "failed writing wwid %s to wwids file", wwid); + return -1; + } + if (ret == 1) + condlog(3, "wrote wwid %s to wwids file", wwid); + else + condlog(4, "wwid %s already in wwids file", wwid); + return 0; +} diff --git a/libmultipath/wwids.h b/libmultipath/wwids.h new file mode 100644 index 0000000..1678f9d --- /dev/null +++ b/libmultipath/wwids.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2010 Benjamin Marzinski, Redhat + */ + +#ifndef _WWIDS_H +#define _WWIDS_H + +#define WWIDS_FILE_HEADER \ +"# Multipath wwids, Version : 1.0\n" \ +"# NOTE: This file is automatically maintained by multipath and multipathd.\n" \ +"# You should not need to edit this file in normal circumstances.\n" \ +"#\n" \ +"# Valid WWIDs:\n" + +int remember_wwid(char *wwid); +int check_wwids_file(char *wwid, int write_wwid); + +#endif /* _WWIDS_H */ diff --git a/multipath/main.c b/multipath/main.c index 883ef59..92e852b 100644 --- a/multipath/main.c +++ b/multipath/main.c @@ -53,6 +53,7 @@ #include #include #include +#include #include "dev_t.h" int logsink; @@ -82,7 +83,7 @@ usage (char * progname) { fprintf (stderr, VERSION_STRING); fprintf (stderr, "Usage:\n"); - fprintf (stderr, " %s [-d] [-r] [-v lvl] [-p pol] [-b fil] [-q] [dev]\n", progname); + fprintf (stderr, " %s [-c] [-d] [-r] [-v lvl] [-p pol] [-b fil] [-q] [dev]\n", progname); fprintf (stderr, " %s -l|-ll|-f [-v lvl] [-b fil] [dev]\n", progname); fprintf (stderr, " %s -F [-v lvl]\n", progname); fprintf (stderr, " %s -t\n", progname); @@ -95,6 +96,7 @@ usage (char * progname) " -ll show multipath topology (maximum info)\n" \ " -f flush a multipath device map\n" \ " -F flush all multipath device maps\n" \ + " -c check if a device should be a path in a multipath device\n" \ " -q allow queue_if_no_path when multipathd is not running\n"\ " -d dry run, do not create or update devmaps\n" \ " -t dump internal hardware table\n" \ @@ -209,6 +211,7 @@ get_dm_mpvec (vector curmp, vector pathvec, char * refwwid) if (!conf->dry_run) reinstate_paths(mpp); + remember_wwid(mpp->wwid); } return 0; } @@ -259,9 +262,13 @@ configure (void) * if we have a blacklisted device parameter, exit early */ if (dev && - (filter_devnode(conf->blist_devnode, conf->elist_devnode, dev) > 0)) - goto out; - + (filter_devnode(conf->blist_devnode, + conf->elist_devnode, dev) > 0)) { + if (conf->dry_run == 2) + printf("%s is not a valid multipath device path\n", + conf->dev); + goto out; + } /* * scope limiting must be translated into a wwid * failing the translation is fatal (by policy) @@ -277,6 +284,15 @@ configure (void) if (filter_wwid(conf->blist_wwid, conf->elist_wwid, refwwid) > 0) goto out; + if (conf->dry_run == 2) { + if (check_wwids_file(refwwid, 0) == 0){ + printf("%s is a valid multipath device path\n", conf->dev); + r = 0; + } + else + printf("%s is not a valid multipath device path\n", conf->dev); + goto out; + } } /* @@ -422,7 +438,7 @@ main (int argc, char *argv[]) if (load_config(DEFAULT_CONFIGFILE)) exit(1); - while ((arg = getopt(argc, argv, ":dhl::FfM:v:p:b:Brtq")) != EOF ) { + while ((arg = getopt(argc, argv, ":dchl::FfM:v:p:b:Brtq")) != EOF ) { switch(arg) { case 1: printf("optarg : %s\n",optarg); break; @@ -444,8 +460,12 @@ main (int argc, char *argv[]) case 'q': conf->allow_queueing = 1; break; + case 'c': + conf->dry_run = 2; + break; case 'd': - conf->dry_run = 1; + if (!conf->dry_run) + conf->dry_run = 1; break; case 'f': conf->remove = FLUSH_ONE; @@ -529,6 +549,11 @@ main (int argc, char *argv[]) } dm_init(); + if (conf->dry_run == 2 && + (!conf->dev || conf->dev_type == DEV_DEVMAP)) { + condlog(0, "the -c option requires a path to check"); + goto out; + } if (conf->remove == FLUSH_ONE) { if (conf->dev_type == DEV_DEVMAP) r = dm_flush_map(conf->dev); -- 2.7.4