libmultipath: add get_uid fallback code for NVMe devices
[multipath-tools/.git] / libmultipath / discovery.c
index 4582a20..bece67c 100644 (file)
@@ -6,6 +6,7 @@
 #include <stdio.h>
 #include <ctype.h>
 #include <unistd.h>
+#include <limits.h>
 #include <fcntl.h>
 #include <sys/ioctl.h>
 #include <sys/stat.h>
 #include "discovery.h"
 #include "prio.h"
 #include "defaults.h"
+#include "unaligned.h"
+#include "prioritizers/alua_rtpg.h"
+#include "foreign.h"
 
 int
-alloc_path_with_pathinfo (vector hwtable, struct udev_device *udevice,
-                         int flag, struct path **pp_ptr)
+alloc_path_with_pathinfo (struct config *conf, struct udev_device *udevice,
+                         const char *wwid, int flag, struct path **pp_ptr)
 {
        int err = PATHINFO_FAILED;
        struct path * pp;
@@ -50,14 +54,17 @@ alloc_path_with_pathinfo (vector hwtable, struct udev_device *udevice,
        if (!pp)
                return PATHINFO_FAILED;
 
+       if (wwid)
+               strlcpy(pp->wwid, wwid, sizeof(pp->wwid));
+
        if (safe_sprintf(pp->dev, "%s", devname)) {
                condlog(0, "pp->dev too small");
        } else {
                pp->udev = udev_device_ref(udevice);
-               err = pathinfo(pp, hwtable, flag | DI_BLACKLIST);
+               err = pathinfo(pp, conf, flag | DI_BLACKLIST);
        }
 
-       if (err)
+       if (err || !pp_ptr)
                free_path(pp);
        else if (pp_ptr)
                *pp_ptr = pp;
@@ -65,8 +72,8 @@ alloc_path_with_pathinfo (vector hwtable, struct udev_device *udevice,
 }
 
 int
-store_pathinfo (vector pathvec, vector hwtable, struct udev_device *udevice,
-               int flag, struct path **pp_ptr)
+store_pathinfo (vector pathvec, struct config *conf,
+               struct udev_device *udevice, int flag, struct path **pp_ptr)
 {
        int err = PATHINFO_FAILED;
        struct path * pp;
@@ -89,15 +96,14 @@ store_pathinfo (vector pathvec, vector hwtable, struct udev_device *udevice,
                goto out;
        }
        pp->udev = udev_device_ref(udevice);
-       err = pathinfo(pp, hwtable,
-                      (conf->cmd == CMD_REMOVE_WWID)? flag :
-                                                      (flag | DI_BLACKLIST));
+       err = pathinfo(pp, conf, flag);
        if (err)
                goto out;
 
        err = store_path(pathvec, pp);
        if (err)
                goto out;
+       pp->checkint = conf->checkint;
 
 out:
        if (err)
@@ -118,35 +124,37 @@ path_discover (vector pathvec, struct config * conf,
        if (!devname)
                return PATHINFO_FAILED;
 
-       if (filter_property(conf, udevice) > 0)
-               return PATHINFO_SKIPPED;
-
-       if (filter_devnode(conf->blist_devnode, conf->elist_devnode,
-                          (char *)devname) > 0)
-               return PATHINFO_SKIPPED;
-
-       pp = find_path_by_dev(pathvec, (char *)devname);
+       pp = find_path_by_dev(pathvec, devname);
        if (!pp) {
-               return store_pathinfo(pathvec, conf->hwtable,
-                                     udevice, flag, NULL);
+               char devt[BLK_DEV_SIZE];
+               dev_t devnum = udev_device_get_devnum(udevice);
+
+               snprintf(devt, BLK_DEV_SIZE, "%d:%d",
+                        major(devnum), minor(devnum));
+               pp = find_path_by_devt(pathvec, devt);
+               if (!pp)
+                       return store_pathinfo(pathvec, conf,
+                                             udevice, flag, NULL);
        }
-       return pathinfo(pp, conf->hwtable, flag);
+       return pathinfo(pp, conf, flag);
 }
 
 int
-path_discovery (vector pathvec, struct config * conf, int flag)
+path_discovery (vector pathvec, int flag)
 {
        struct udev_enumerate *udev_iter;
        struct udev_list_entry *entry;
        struct udev_device *udevice;
+       struct config *conf;
        const char *devpath;
        int num_paths = 0, total_paths = 0;
 
-       udev_iter = udev_enumerate_new(conf->udev);
+       udev_iter = udev_enumerate_new(udev);
        if (!udev_iter)
                return -ENOMEM;
 
        udev_enumerate_add_match_subsystem(udev_iter, "block");
+       udev_enumerate_add_match_is_initialized(udev_iter);
        udev_enumerate_scan_devices(udev_iter);
 
        udev_list_entry_foreach(entry,
@@ -154,7 +162,7 @@ path_discovery (vector pathvec, struct config * conf, int flag)
                const char *devtype;
                devpath = udev_list_entry_get_name(entry);
                condlog(4, "Discover device %s", devpath);
-               udevice = udev_device_new_from_syspath(conf->udev, devpath);
+               udevice = udev_device_new_from_syspath(udev, devpath);
                if (!udevice) {
                        condlog(4, "%s: no udev information", devpath);
                        continue;
@@ -162,9 +170,12 @@ path_discovery (vector pathvec, struct config * conf, int flag)
                devtype = udev_device_get_devtype(udevice);
                if(devtype && !strncmp(devtype, "disk", 4)) {
                        total_paths++;
+                       conf = get_multipath_config();
+                       pthread_cleanup_push(put_multipath_config, conf);
                        if (path_discover(pathvec, conf,
                                          udevice, flag) == PATHINFO_OK)
                                num_paths++;
+                       pthread_cleanup_pop(1);
                }
                udev_device_unref(udevice);
        }
@@ -174,7 +185,7 @@ path_discovery (vector pathvec, struct config * conf, int flag)
 }
 
 #define declare_sysfs_get_str(fname)                                   \
-extern ssize_t                                                         \
+ssize_t                                                                        \
 sysfs_get_##fname (struct udev_device * udev, char * buff, size_t len) \
 {                                                                      \
        int l;                                                  \
@@ -237,8 +248,8 @@ sysfs_get_timeout(struct path *pp, unsigned int *timeout)
        const char *attr = NULL;
        const char *subsys;
        struct udev_device *parent;
-       int r;
-       unsigned int t;
+       char *eptr;
+       unsigned long t;
 
        if (!pp->udev || pp->bus != SYSFS_BUS_SCSI)
                return -ENOSYS;
@@ -256,17 +267,20 @@ sysfs_get_timeout(struct path *pp, unsigned int *timeout)
                return -ENXIO;
        }
 
-       r = sscanf(attr, "%u\n", &t);
-
-       if (r != 1) {
+       t = strtoul(attr, &eptr, 0);
+       if (attr == eptr || t == ULONG_MAX) {
                condlog(3, "%s: Cannot parse timeout attribute '%s'",
                        pp->dev, attr);
                return -EINVAL;
        }
-
+       if (t > UINT_MAX) {
+               condlog(3, "%s: Overflow in timeout value '%s'",
+                       pp->dev, attr);
+               return -ERANGE;
+       }
        *timeout = t;
 
-       return 0;
+       return 1;
 }
 
 int
@@ -320,7 +334,7 @@ sysfs_get_tgt_nodename (struct path *pp, char * node)
        value = udev_device_get_sysname(tgtdev);
        if (sscanf(value, "rport-%d:%d-%d",
                   &host, &channel, &tgtid) == 3) {
-               tgtdev = udev_device_new_from_subsystem_sysname(conf->udev,
+               tgtdev = udev_device_new_from_subsystem_sysname(udev,
                                "fc_remote_ports", value);
                if (tgtdev) {
                        condlog(3, "SCSI target %d:%d:%d -> "
@@ -353,7 +367,7 @@ sysfs_get_tgt_nodename (struct path *pp, char * node)
                tgtid = -1;
        }
        if (parent && tgtname) {
-               tgtdev = udev_device_new_from_subsystem_sysname(conf->udev,
+               tgtdev = udev_device_new_from_subsystem_sysname(udev,
                                "iscsi_session", tgtname);
                if (tgtdev) {
                        const char *value;
@@ -391,7 +405,7 @@ sysfs_get_tgt_nodename (struct path *pp, char * node)
        return 0;
 }
 
-int sysfs_get_host_adapter_name(struct path *pp, char *adapter_name)
+int sysfs_get_host_adapter_name(const struct path *pp, char *adapter_name)
 {
        int proto_id;
 
@@ -417,7 +431,7 @@ int sysfs_get_host_adapter_name(struct path *pp, char *adapter_name)
        return sysfs_get_host_pci_name(pp, adapter_name);
 }
 
-int sysfs_get_host_pci_name(struct path *pp, char *pci_name)
+int sysfs_get_host_pci_name(const struct path *pp, char *pci_name)
 {
        struct udev_device *hostdev, *parent;
        char host_name[HOST_NAME_LEN];
@@ -427,7 +441,7 @@ int sysfs_get_host_pci_name(struct path *pp, char *pci_name)
                return 1;
 
        sprintf(host_name, "host%d", pp->sg_id.host_no);
-       hostdev = udev_device_new_from_subsystem_sysname(conf->udev,
+       hostdev = udev_device_new_from_subsystem_sysname(udev,
                        "scsi_host", host_name);
        if (!hostdev)
                return 1;
@@ -456,14 +470,14 @@ int sysfs_get_host_pci_name(struct path *pp, char *pci_name)
        return 1;
 }
 
-int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address)
+int sysfs_get_iscsi_ip_address(const struct path *pp, char *ip_address)
 {
        struct udev_device *hostdev;
        char host_name[HOST_NAME_LEN];
        const char *value;
 
        sprintf(host_name, "host%d", pp->sg_id.host_no);
-       hostdev = udev_device_new_from_subsystem_sysname(conf->udev,
+       hostdev = udev_device_new_from_subsystem_sysname(udev,
                        "iscsi_host", host_name);
        if (hostdev) {
                value = udev_device_get_sysattr_value(hostdev,
@@ -478,18 +492,49 @@ int sysfs_get_iscsi_ip_address(struct path *pp, char *ip_address)
        return 1;
 }
 
+int
+sysfs_get_asymmetric_access_state(struct path *pp, char *buff, int buflen)
+{
+       struct udev_device *parent = pp->udev;
+       char value[16], *eptr;
+       unsigned long preferred;
+
+       while (parent) {
+               const char *subsys = udev_device_get_subsystem(parent);
+               if (subsys && !strncmp(subsys, "scsi", 4))
+                       break;
+               parent = udev_device_get_parent(parent);
+       }
+
+       if (!parent)
+               return -1;
+
+       if (sysfs_attr_get_value(parent, "access_state", buff, buflen) <= 0)
+               return -1;
+
+       if (sysfs_attr_get_value(parent, "preferred_path", value, 16) <= 0)
+               return 0;
+
+       preferred = strtoul(value, &eptr, 0);
+       if (value == eptr || preferred == ULONG_MAX) {
+               /* Parse error, ignore */
+               return 0;
+       }
+       return !!preferred;
+}
+
 static void
 sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
 {
        struct udev_device *rport_dev = NULL;
-       char value[16];
+       char value[16], *eptr;
        char rport_id[32];
        unsigned long long tmo = 0;
        int ret;
 
        sprintf(rport_id, "rport-%d:%d-%d",
                pp->sg_id.host_no, pp->sg_id.channel, pp->sg_id.transport_id);
-       rport_dev = udev_device_new_from_subsystem_sysname(conf->udev,
+       rport_dev = udev_device_new_from_subsystem_sysname(udev,
                                "fc_remote_ports", rport_id);
        if (!rport_dev) {
                condlog(1, "%s: No fc_remote_port device for '%s'", pp->dev,
@@ -499,6 +544,22 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
        condlog(4, "target%d:%d:%d -> %s", pp->sg_id.host_no,
                pp->sg_id.channel, pp->sg_id.scsi_id, rport_id);
 
+       /*
+        * read the current dev_loss_tmo value from sysfs
+        */
+       ret = sysfs_attr_get_value(rport_dev, "dev_loss_tmo", value, 16);
+       if (ret <= 0) {
+               condlog(0, "%s: failed to read dev_loss_tmo value, "
+                       "error %d", rport_id, -ret);
+               goto out;
+       }
+       tmo = strtoull(value, &eptr, 0);
+       if (value == eptr || tmo == ULLONG_MAX) {
+               condlog(0, "%s: Cannot parse dev_loss_tmo "
+                       "attribute '%s'", rport_id, value);
+               goto out;
+       }
+
        /*
         * This is tricky.
         * dev_loss_tmo will be limited to 600 if fast_io_fail
@@ -510,44 +571,32 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
         * then set fast_io_fail, and _then_ set dev_loss_tmo
         * to the correct value.
         */
-       memset(value, 0, 16);
        if (mpp->fast_io_fail != MP_FAST_IO_FAIL_UNSET &&
            mpp->fast_io_fail != MP_FAST_IO_FAIL_ZERO &&
            mpp->fast_io_fail != MP_FAST_IO_FAIL_OFF) {
                /* Check if we need to temporarily increase dev_loss_tmo */
-               ret = sysfs_attr_get_value(rport_dev, "dev_loss_tmo",
-                                          value, 16);
-               if (ret <= 0) {
-                       condlog(0, "%s: failed to read dev_loss_tmo value, "
-                               "error %d", rport_id, -ret);
-                       goto out;
-               }
-               if (sscanf(value, "%llu\n", &tmo) != 1) {
-                       condlog(0, "%s: Cannot parse dev_loss_tmo "
-                               "attribute '%s'", rport_id, value);
-                       goto out;
-               }
                if (mpp->fast_io_fail >= tmo) {
+                       /* Increase dev_loss_tmo temporarily */
                        snprintf(value, 16, "%u", mpp->fast_io_fail + 1);
+                       ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo",
+                                                  value, strlen(value));
+                       if (ret <= 0) {
+                               if (ret == -EBUSY)
+                                       condlog(3, "%s: rport blocked",
+                                               rport_id);
+                               else
+                                       condlog(0, "%s: failed to set "
+                                               "dev_loss_tmo to %s, error %d",
+                                               rport_id, value, -ret);
+                               goto out;
+                       }
                }
-       } else if (mpp->dev_loss > 600) {
-               condlog(3, "%s: limiting dev_loss_tmo to 600, since "
-                       "fast_io_fail is not set", rport_id);
-               snprintf(value, 16, "%u", 600);
-       } else {
-               snprintf(value, 16, "%u", mpp->dev_loss);
-       }
-       if (strlen(value)) {
-               ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo",
-                                          value, strlen(value));
-               if (ret <= 0) {
-                       if (ret == -EBUSY)
-                               condlog(3, "%s: rport blocked", rport_id);
-                       else
-                               condlog(0, "%s: failed to set dev_loss_tmo to %s, error %d",
-                                       rport_id, value, -ret);
-                       goto out;
-               }
+       } else if (mpp->dev_loss > DEFAULT_DEV_LOSS_TMO &&
+               mpp->no_path_retry != NO_PATH_RETRY_QUEUE) {
+               condlog(3, "%s: limiting dev_loss_tmo to %d, since "
+                       "fast_io_fail is not set",
+                       rport_id, DEFAULT_DEV_LOSS_TMO);
+               mpp->dev_loss = DEFAULT_DEV_LOSS_TMO;
        }
        if (mpp->fast_io_fail != MP_FAST_IO_FAIL_UNSET) {
                if (mpp->fast_io_fail == MP_FAST_IO_FAIL_OFF)
@@ -566,7 +615,7 @@ sysfs_set_rport_tmo(struct multipath *mpp, struct path *pp)
                                        rport_id, value, -ret);
                }
        }
-       if (tmo > 0) {
+       if (mpp->dev_loss > 0) {
                snprintf(value, 16, "%u", mpp->dev_loss);
                ret = sysfs_attr_set_value(rport_dev, "dev_loss_tmo",
                                           value, strlen(value));
@@ -590,7 +639,7 @@ sysfs_set_session_tmo(struct multipath *mpp, struct path *pp)
        char value[11];
 
        sprintf(session_id, "session%d", pp->sg_id.transport_id);
-       session_dev = udev_device_new_from_subsystem_sysname(conf->udev,
+       session_dev = udev_device_new_from_subsystem_sysname(udev,
                                "iscsi_session", session_id);
        if (!session_dev) {
                condlog(1, "%s: No iscsi session for '%s'", pp->dev,
@@ -613,7 +662,7 @@ sysfs_set_session_tmo(struct multipath *mpp, struct path *pp)
                } else {
                        snprintf(value, 11, "%u", mpp->fast_io_fail);
                        if (sysfs_attr_set_value(session_dev, "recovery_tmo",
-                                                value, 11) <= 0) {
+                                                value, strlen(value)) <= 0) {
                                condlog(3, "%s: Failed to set recovery_tmo, "
                                        " error %d", pp->dev, errno);
                        }
@@ -632,7 +681,7 @@ sysfs_set_nexus_loss_tmo(struct multipath *mpp, struct path *pp)
 
        sprintf(end_dev_id, "end_device-%d:%d",
                pp->sg_id.host_no, pp->sg_id.transport_id);
-       sas_dev = udev_device_new_from_subsystem_sysname(conf->udev,
+       sas_dev = udev_device_new_from_subsystem_sysname(udev,
                                "sas_end_device", end_dev_id);
        if (!sas_dev) {
                condlog(1, "%s: No SAS end device for '%s'", pp->dev,
@@ -645,7 +694,7 @@ sysfs_set_nexus_loss_tmo(struct multipath *mpp, struct path *pp)
        if (mpp->dev_loss) {
                snprintf(value, 11, "%u", mpp->dev_loss);
                if (sysfs_attr_set_value(sas_dev, "I_T_nexus_loss_timeout",
-                                        value, 11) <= 0)
+                                        value, strlen(value)) <= 0)
                        condlog(3, "%s: failed to update "
                                "I_T Nexus loss timeout, error %d",
                                pp->dev, errno);
@@ -655,24 +704,24 @@ sysfs_set_nexus_loss_tmo(struct multipath *mpp, struct path *pp)
 }
 
 int
-sysfs_set_scsi_tmo (struct multipath *mpp)
+sysfs_set_scsi_tmo (struct multipath *mpp, int checkint)
 {
        struct path *pp;
        int i;
        int dev_loss_tmo = mpp->dev_loss;
 
        if (mpp->no_path_retry > 0) {
-               int no_path_retry_tmo = mpp->no_path_retry * conf->checkint;
+               uint64_t no_path_retry_tmo = (uint64_t)mpp->no_path_retry * checkint;
 
                if (no_path_retry_tmo > MAX_DEV_LOSS_TMO)
                        no_path_retry_tmo = MAX_DEV_LOSS_TMO;
                if (no_path_retry_tmo > dev_loss_tmo)
                        dev_loss_tmo = no_path_retry_tmo;
-               condlog(3, "%s: update dev_loss_tmo to %d",
+               condlog(3, "%s: update dev_loss_tmo to %u",
                        mpp->alias, dev_loss_tmo);
        } else if (mpp->no_path_retry == NO_PATH_RETRY_QUEUE) {
                dev_loss_tmo = MAX_DEV_LOSS_TMO;
-               condlog(3, "%s: update dev_loss_tmo to %d",
+               condlog(3, "%s: update dev_loss_tmo to %u",
                        mpp->alias, dev_loss_tmo);
        }
        mpp->dev_loss = dev_loss_tmo;
@@ -721,7 +770,7 @@ do_inq(int sg_fd, int cmddt, int evpd, unsigned int pg_op,
        io_hdr.dxferp = resp;
        io_hdr.cmdp = inqCmdBlk;
        io_hdr.sbp = sense_b;
-       io_hdr.timeout = DEF_TIMEOUT;
+       io_hdr.timeout = DEF_TIMEOUT * 1000;
 
        if (ioctl(sg_fd, SG_IO, &io_hdr) < 0)
                return -1;
@@ -770,10 +819,31 @@ get_serial (char * str, int maxlen, int fd)
        return 1;
 }
 
+static void
+detect_alua(struct path * pp, struct config *conf)
+{
+       int ret;
+       int tpgs;
+       unsigned int timeout = conf->checker_timeout;
+
+       if ((tpgs = get_target_port_group_support(pp->fd, timeout)) <= 0) {
+               pp->tpgs = TPGS_NONE;
+               return;
+       }
+       ret = get_target_port_group(pp, timeout);
+       if (ret < 0 || get_asymmetric_access_state(pp->fd, ret, timeout) < 0) {
+               pp->tpgs = TPGS_NONE;
+               return;
+       }
+       pp->tpgs = tpgs;
+}
+
 #define DEFAULT_SGIO_LEN 254
 
+/* Query VPD page @pg. Returns number of INQUIRY bytes
+   upon success and -1 upon failure. */
 static int
-sgio_get_vpd (unsigned char * buff, int maxlen, int fd)
+sgio_get_vpd (unsigned char * buff, int maxlen, int fd, int pg)
 {
        int len = DEFAULT_SGIO_LEN;
 
@@ -782,13 +852,13 @@ sgio_get_vpd (unsigned char * buff, int maxlen, int fd)
                return -1;
        }
 retry:
-       if (0 == do_inq(fd, 0, 1, 0x83, buff, len)) {
-               len = buff[3] + (buff[2] << 8);
+       if (0 == do_inq(fd, 0, 1, pg, buff, len)) {
+               len = get_unaligned_be16(&buff[2]) + 4;
                if (len >= maxlen)
                        return len;
                if (len > DEFAULT_SGIO_LEN)
                        goto retry;
-               return 0;
+               return len;
        }
        return -1;
 }
@@ -814,7 +884,7 @@ static int
 parse_vpd_pg80(const unsigned char *in, char *out, size_t out_len)
 {
        char *p = NULL;
-       int len = in[3] + (in[2] << 8);
+       int len = get_unaligned_be16(&in[2]);
 
        if (len >= out_len) {
                condlog(2, "vpd pg80 overflow, %d/%d bytes required",
@@ -841,12 +911,12 @@ static int
 parse_vpd_pg83(const unsigned char *in, size_t in_len,
               char *out, size_t out_len)
 {
-       unsigned char *d;
-       unsigned char *vpd = NULL;
+       const unsigned char *d;
+       const unsigned char *vpd = NULL;
        int len = -ENODATA, vpd_type, vpd_len, prio = -1, i, naa_prio;
 
-       d = (unsigned char *)in + 4;
-       while (d < (unsigned char *)in + in_len) {
+       d = in + 4;
+       while (d < in + in_len) {
                /* Select 'association: LUN' */
                if ((d[1] & 0x30) != 0) {
                        d += d[3] + 4;
@@ -963,7 +1033,7 @@ parse_vpd_pg83(const unsigned char *in, size_t in_len,
                                out[len] = '\0';
                        }
                } else if (vpd_type == 0x1) {
-                       unsigned char *p;
+                       const unsigned char *p;
                        int p_len;
 
                        out[0] = '1';
@@ -1014,7 +1084,7 @@ get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen)
                        pg, buff[1]);
                return -ENODATA;
        }
-       buff_len = (buff[2] << 8) + buff[3] + 4;
+       buff_len = get_unaligned_be16(&buff[2]) + 4;
        if (buff_len > 4096)
                condlog(3, "vpd pg%02x page truncated", pg);
 
@@ -1028,15 +1098,17 @@ get_vpd_sysfs (struct udev_device *parent, int pg, char * str, int maxlen)
        return len;
 }
 
-static int
+int
 get_vpd_sgio (int fd, int pg, char * str, int maxlen)
 {
        int len, buff_len;
        unsigned char buff[4096];
 
        memset(buff, 0x0, 4096);
-       if (sgio_get_vpd(buff, 4096, fd) <= 0) {
-               condlog(3, "failed to issue vpd inquiry for pg%02x",
+       if (sgio_get_vpd(buff, 4096, fd, pg) < 0) {
+               int lvl = pg == 0x80 || pg == 0x83 ? 3 : 4;
+
+               condlog(lvl, "failed to issue vpd inquiry for pg%02x",
                        pg);
                return -errno;
        }
@@ -1046,22 +1118,30 @@ get_vpd_sgio (int fd, int pg, char * str, int maxlen)
                        pg, buff[1]);
                return -ENODATA;
        }
-       buff_len = (buff[2] << 8) + buff[3] + 4;
-       if (buff_len > 4096)
+       buff_len = get_unaligned_be16(&buff[2]) + 4;
+       if (buff_len > 4096) {
                condlog(3, "vpd pg%02x page truncated", pg);
-
+               buff_len = 4096;
+       }
        if (pg == 0x80)
                len = parse_vpd_pg80(buff, str, maxlen);
        else if (pg == 0x83)
                len = parse_vpd_pg83(buff, buff_len, str, maxlen);
-       else
+       else if (pg == 0xc9 && maxlen >= 8) {
+               if (buff_len < 8)
+                       len = -ENODATA;
+               else {
+                       len = (buff_len <= maxlen)? buff_len : maxlen;
+                       memcpy (str, buff, len);
+               }
+       } else
                len = -ENOSYS;
 
        return len;
 }
 
 static int
-scsi_sysfs_pathinfo (struct path * pp)
+scsi_sysfs_pathinfo (struct path * pp, vector hwtable)
 {
        struct udev_device *parent;
        const char *attr_path = NULL;
@@ -1083,27 +1163,27 @@ scsi_sysfs_pathinfo (struct path * pp)
                parent = udev_device_get_parent(parent);
        }
        if (!attr_path || pp->sg_id.host_no == -1)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (sysfs_get_vendor(parent, pp->vendor_id, SCSI_VENDOR_SIZE) <= 0)
-               return 1;
+               return PATHINFO_FAILED;;
 
        condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
 
-       if (sysfs_get_model(parent, pp->product_id, SCSI_PRODUCT_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0)
+               return PATHINFO_FAILED;;
 
        condlog(3, "%s: product = %s", pp->dev, pp->product_id);
 
-       if (sysfs_get_rev(parent, pp->rev, SCSI_REV_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) < 0)
+               return PATHINFO_FAILED;;
 
        condlog(3, "%s: rev = %s", pp->dev, pp->rev);
 
        /*
         * set the hwe configlet pointer
         */
-       pp->hwe = find_hwe(conf->hwtable, pp->vendor_id, pp->product_id, pp->rev);
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev, pp->hwe);
 
        /*
         * host / bus / target / lun
@@ -1119,16 +1199,61 @@ scsi_sysfs_pathinfo (struct path * pp)
         * target node name
         */
        if(sysfs_get_tgt_nodename(pp, pp->tgt_node_name))
-               return 1;
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: tgt_node_name = %s",
                pp->dev, pp->tgt_node_name);
 
-       return 0;
+       return PATHINFO_OK;
+}
+
+static int
+nvme_sysfs_pathinfo (struct path * pp, vector hwtable)
+{
+       struct udev_device *parent;
+       const char *attr_path = NULL;
+       const char *attr;
+
+       attr_path = udev_device_get_sysname(pp->udev);
+       if (!attr_path)
+               return PATHINFO_FAILED;
+
+       if (sscanf(attr_path, "nvme%dn%d",
+                  &pp->sg_id.host_no,
+                  &pp->sg_id.scsi_id) != 2)
+               return PATHINFO_FAILED;
+
+       parent = udev_device_get_parent_with_subsystem_devtype(pp->udev,
+                                                              "nvme", NULL);
+       if (!parent)
+               return PATHINFO_SKIPPED;
+
+       attr = udev_device_get_sysattr_value(pp->udev, "nsid");
+       pp->sg_id.lun = attr ? atoi(attr) : 0;
+
+       attr = udev_device_get_sysattr_value(parent, "cntlid");
+       pp->sg_id.channel = attr ? atoi(attr) : 0;
+
+       snprintf(pp->vendor_id, SCSI_VENDOR_SIZE, "NVME");
+       snprintf(pp->product_id, PATH_PRODUCT_SIZE, "%s",
+                udev_device_get_sysattr_value(parent, "model"));
+       snprintf(pp->serial, SERIAL_SIZE, "%s",
+                udev_device_get_sysattr_value(parent, "serial"));
+       snprintf(pp->rev, PATH_REV_SIZE, "%s",
+                udev_device_get_sysattr_value(parent, "firmware_rev"));
+
+       condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
+       condlog(3, "%s: product = %s", pp->dev, pp->product_id);
+       condlog(3, "%s: serial = %s", pp->dev, pp->serial);
+       condlog(3, "%s: rev = %s", pp->dev, pp->rev);
+
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL, pp->hwe);
+
+       return PATHINFO_OK;
 }
 
 static int
-ccw_sysfs_pathinfo (struct path * pp)
+ccw_sysfs_pathinfo (struct path * pp, vector hwtable)
 {
        struct udev_device *parent;
        char attr_buff[NAME_SIZE];
@@ -1142,14 +1267,14 @@ ccw_sysfs_pathinfo (struct path * pp)
                parent = udev_device_get_parent(parent);
        }
        if (!parent)
-               return 1;
+               return PATHINFO_FAILED;
 
        sprintf(pp->vendor_id, "IBM");
 
        condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
 
        if (sysfs_get_devtype(parent, attr_buff, FILE_NAME_SIZE) <= 0)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (!strncmp(attr_buff, "3370", 4)) {
                sprintf(pp->product_id,"S/390 DASD FBA");
@@ -1164,29 +1289,30 @@ ccw_sysfs_pathinfo (struct path * pp)
        /*
         * set the hwe configlet pointer
         */
-       pp->hwe = find_hwe(conf->hwtable, pp->vendor_id, pp->product_id, NULL);
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, NULL, pp->hwe);
 
        /*
         * host / bus / target / lun
         */
        attr_path = udev_device_get_sysname(parent);
        pp->sg_id.lun = 0;
-       sscanf(attr_path, "%i.%i.%x",
-                       &pp->sg_id.host_no,
-                       &pp->sg_id.channel,
-                       &pp->sg_id.scsi_id);
-       condlog(3, "%s: h:b:t:l = %i:%i:%i:%i",
+       if (sscanf(attr_path, "%i.%i.%x",
+                  &pp->sg_id.host_no,
+                  &pp->sg_id.channel,
+                  &pp->sg_id.scsi_id) == 3) {
+               condlog(3, "%s: h:b:t:l = %i:%i:%i:%i",
                        pp->dev,
                        pp->sg_id.host_no,
                        pp->sg_id.channel,
                        pp->sg_id.scsi_id,
                        pp->sg_id.lun);
+       }
 
-       return 0;
+       return PATHINFO_OK;
 }
 
 static int
-cciss_sysfs_pathinfo (struct path * pp)
+cciss_sysfs_pathinfo (struct path * pp, vector hwtable)
 {
        const char * attr_path = NULL;
        struct udev_device *parent;
@@ -1206,27 +1332,27 @@ cciss_sysfs_pathinfo (struct path * pp)
                parent = udev_device_get_parent(parent);
        }
        if (!attr_path || pp->sg_id.host_no == -1)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (sysfs_get_vendor(parent, pp->vendor_id, SCSI_VENDOR_SIZE) <= 0)
-               return 1;
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: vendor = %s", pp->dev, pp->vendor_id);
 
-       if (sysfs_get_model(parent, pp->product_id, SCSI_PRODUCT_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_model(parent, pp->product_id, PATH_PRODUCT_SIZE) <= 0)
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: product = %s", pp->dev, pp->product_id);
 
-       if (sysfs_get_rev(parent, pp->rev, SCSI_REV_SIZE) <= 0)
-               return 1;
+       if (sysfs_get_rev(parent, pp->rev, PATH_REV_SIZE) <= 0)
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: rev = %s", pp->dev, pp->rev);
 
        /*
         * set the hwe configlet pointer
         */
-       pp->hwe = find_hwe(conf->hwtable, pp->vendor_id, pp->product_id, pp->rev);
+       find_hwe(hwtable, pp->vendor_id, pp->product_id, pp->rev, pp->hwe);
 
        /*
         * host / bus / target / lun
@@ -1239,7 +1365,8 @@ cciss_sysfs_pathinfo (struct path * pp)
                pp->sg_id.channel,
                pp->sg_id.scsi_id,
                pp->sg_id.lun);
-       return 0;
+
+       return PATHINFO_OK;
 }
 
 static int
@@ -1248,23 +1375,23 @@ common_sysfs_pathinfo (struct path * pp)
        dev_t devt;
 
        if (!pp)
-               return 1;
+               return PATHINFO_FAILED;
 
        if (!pp->udev) {
                condlog(4, "%s: udev not initialised", pp->dev);
-               return 1;
+               return PATHINFO_FAILED;
        }
        devt = udev_device_get_devnum(pp->udev);
        snprintf(pp->dev_t, BLK_DEV_SIZE, "%d:%d", major(devt), minor(devt));
 
-       condlog(3, "%s: dev_t = %s", pp->dev, pp->dev_t);
+       condlog(4, "%s: dev_t = %s", pp->dev, pp->dev_t);
 
        if (sysfs_get_size(pp, &pp->size))
-               return 1;
+               return PATHINFO_FAILED;
 
        condlog(3, "%s: size = %llu", pp->dev, pp->size);
 
-       return 0;
+       return PATHINFO_OK;
 }
 
 int
@@ -1273,14 +1400,22 @@ path_offline (struct path * pp)
        struct udev_device * parent;
        char buff[SCSI_STATE_SIZE];
        int err;
+       const char *subsys_type;
 
-       if (pp->bus != SYSFS_BUS_SCSI)
+       if (pp->bus == SYSFS_BUS_SCSI) {
+               subsys_type = "scsi";
+       }
+       else if (pp->bus == SYSFS_BUS_NVME) {
+               subsys_type = "nvme";
+       }
+       else {
                return PATH_UP;
+       }
 
        parent = pp->udev;
        while (parent) {
                const char *subsys = udev_device_get_subsystem(parent);
-               if (subsys && !strncmp(subsys, "scsi", 4))
+               if (subsys && !strncmp(subsys, subsys_type, 4))
                        break;
                parent = udev_device_get_parent(parent);
        }
@@ -1300,26 +1435,44 @@ path_offline (struct path * pp)
        }
 
 
-       condlog(3, "%s: path state = %s", pp->dev, buff);
+       condlog(4, "%s: path state = %s", pp->dev, buff);
+
+       if (pp->bus == SYSFS_BUS_SCSI) {
+               if (!strncmp(buff, "offline", 7)) {
+                       pp->offline = 1;
+                       return PATH_DOWN;
+               }
+               pp->offline = 0;
+               if (!strncmp(buff, "blocked", 7) ||
+                   !strncmp(buff, "quiesce", 7))
+                       return PATH_PENDING;
+               else if (!strncmp(buff, "running", 7))
+                       return PATH_UP;
 
-       if (!strncmp(buff, "offline", 7)) {
-               pp->offline = 1;
-               return PATH_DOWN;
        }
-       pp->offline = 0;
-       if (!strncmp(buff, "blocked", 7) || !strncmp(buff, "quiesce", 7))
-               return PATH_PENDING;
-       else if (!strncmp(buff, "running", 7))
-               return PATH_UP;
+       else if (pp->bus == SYSFS_BUS_NVME) {
+               if (!strncmp(buff, "dead", 4)) {
+                       pp->offline = 1;
+                       return PATH_DOWN;
+               }
+               pp->offline = 0;
+               if (!strncmp(buff, "new", 3) ||
+                   !strncmp(buff, "deleting", 8))
+                       return PATH_PENDING;
+               else if (!strncmp(buff, "live", 4))
+                       return PATH_UP;
+       }
 
        return PATH_DOWN;
 }
 
 int
-sysfs_pathinfo(struct path * pp)
+sysfs_pathinfo(struct path * pp, vector hwtable)
 {
-       if (common_sysfs_pathinfo(pp))
-               return 1;
+       int r = common_sysfs_pathinfo(pp);
+
+       if (r != PATHINFO_OK)
+               return r;
 
        pp->bus = SYSFS_BUS_UNDEF;
        if (!strncmp(pp->dev,"cciss",5))
@@ -1328,30 +1481,35 @@ sysfs_pathinfo(struct path * pp)
                pp->bus = SYSFS_BUS_CCW;
        if (!strncmp(pp->dev,"sd", 2))
                pp->bus = SYSFS_BUS_SCSI;
-
-       if (pp->bus == SYSFS_BUS_UNDEF)
-               return 0;
-       else if (pp->bus == SYSFS_BUS_SCSI) {
-               if (scsi_sysfs_pathinfo(pp))
-                       return 1;
-       } else if (pp->bus == SYSFS_BUS_CCW) {
-               if (ccw_sysfs_pathinfo(pp))
-                       return 1;
-       } else if (pp->bus == SYSFS_BUS_CCISS) {
-               if (cciss_sysfs_pathinfo(pp))
-                       return 1;
+       if (!strncmp(pp->dev,"nvme", 4))
+               pp->bus = SYSFS_BUS_NVME;
+
+       switch (pp->bus) {
+       case SYSFS_BUS_SCSI:
+               return scsi_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_CCW:
+               return ccw_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_CCISS:
+               return cciss_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_NVME:
+               return nvme_sysfs_pathinfo(pp, hwtable);
+       case SYSFS_BUS_UNDEF:
+       default:
+               return PATHINFO_OK;
        }
-       return 0;
 }
 
-static int
-scsi_ioctl_pathinfo (struct path * pp, int mask)
+static void
+scsi_ioctl_pathinfo (struct path * pp, struct config *conf, int mask)
 {
        struct udev_device *parent;
        const char *attr_path = NULL;
 
+       if (pp->tpgs == TPGS_UNDEF)
+               detect_alua(pp, conf);
+
        if (!(mask & DI_SERIAL))
-               return 0;
+               return;
 
        parent = pp->udev;
        while (parent) {
@@ -1370,49 +1528,49 @@ scsi_ioctl_pathinfo (struct path * pp, int mask)
                parent = udev_device_get_parent(parent);
        }
        if (!attr_path || pp->sg_id.host_no == -1)
-               return 0;
+               return;
 
-       if (get_vpd_sysfs(parent, 0x80, pp->serial, SERIAL_SIZE) > 0)
-               condlog(3, "%s: serial = %s",
-                       pp->dev, pp->serial);
+       if (get_vpd_sysfs(parent, 0x80, pp->serial, SERIAL_SIZE) <= 0) {
+               if (get_serial(pp->serial, SERIAL_SIZE, pp->fd)) {
+                       condlog(3, "%s: fail to get serial", pp->dev);
+                       return;
+               }
+       }
 
-       return 0;
+       condlog(3, "%s: serial = %s", pp->dev, pp->serial);
+       return;
 }
 
-static int
-cciss_ioctl_pathinfo (struct path * pp, int mask)
+static void
+cciss_ioctl_pathinfo(struct path *pp)
 {
-       if (mask & DI_SERIAL) {
-               get_serial(pp->serial, SERIAL_SIZE, pp->fd);
-               condlog(3, "%s: serial = %s", pp->dev, pp->serial);
-       }
-       return 0;
+       get_serial(pp->serial, SERIAL_SIZE, pp->fd);
+       condlog(3, "%s: serial = %s", pp->dev, pp->serial);
 }
 
 int
-get_state (struct path * pp, int daemon)
+get_state (struct path * pp, struct config *conf, int daemon, int oldstate)
 {
        struct checker * c = &pp->checker;
        int state;
 
-       condlog(3, "%s: get_state", pp->dev);
-
        if (!checker_selected(c)) {
                if (daemon) {
-                       if (pathinfo(pp, conf->hwtable, DI_SYSFS) != PATHINFO_OK) {
+                       if (pathinfo(pp, conf, DI_SYSFS) != PATHINFO_OK) {
                                condlog(3, "%s: couldn't get sysfs pathinfo",
                                        pp->dev);
                                return PATH_UNCHECKED;
                        }
                }
-               select_checker(pp);
+               select_detect_checker(conf, pp);
+               select_checker(conf, pp);
                if (!checker_selected(c)) {
                        condlog(3, "%s: No checker selected", pp->dev);
                        return PATH_UNCHECKED;
                }
                checker_set_fd(c, pp->fd);
                if (checker_init(c, pp->mpp?&pp->mpp->mpcontext:NULL)) {
-                       memset(c, 0x0, sizeof(struct checker));
+                       checker_clear(c);
                        condlog(3, "%s: checker init failed", pp->dev);
                        return PATH_UNCHECKED;
                }
@@ -1427,61 +1585,149 @@ get_state (struct path * pp, int daemon)
        if (!conf->checker_timeout &&
            sysfs_get_timeout(pp, &(c->timeout)) <= 0)
                c->timeout = DEF_TIMEOUT;
-       state = checker_check(c);
-       condlog(3, "%s: state = %s", pp->dev, checker_state_name(state));
+       state = checker_check(c, oldstate);
+       condlog(3, "%s: %s state = %s", pp->dev,
+               checker_name(c), checker_state_name(state));
        if (state != PATH_UP && state != PATH_GHOST &&
            strlen(checker_message(c)))
-               condlog(3, "%s: checker msg is \"%s\"",
-                       pp->dev, checker_message(c));
+               condlog(3, "%s: %s checker%s",
+                       pp->dev, checker_name(c), checker_message(c));
        return state;
 }
 
 static int
 get_prio (struct path * pp)
 {
+       struct prio * p;
+       struct config *conf;
+       int checker_timeout;
+       int old_prio;
+
        if (!pp)
                return 0;
 
-       struct prio * p = &pp->prio;
-
+       p = &pp->prio;
        if (!prio_selected(p)) {
-               select_detect_prio(pp);
-               select_prio(pp);
+               conf = get_multipath_config();
+               pthread_cleanup_push(put_multipath_config, conf);
+               select_detect_prio(conf, pp);
+               select_prio(conf, pp);
+               pthread_cleanup_pop(1);
                if (!prio_selected(p)) {
                        condlog(3, "%s: no prio selected", pp->dev);
                        pp->priority = PRIO_UNDEF;
                        return 1;
                }
        }
-       pp->priority = prio_getprio(p, pp);
+       conf = get_multipath_config();
+       checker_timeout = conf->checker_timeout;
+       put_multipath_config(conf);
+       old_prio = pp->priority;
+       pp->priority = prio_getprio(p, pp, checker_timeout);
        if (pp->priority < 0) {
                condlog(3, "%s: %s prio error", pp->dev, prio_name(p));
                pp->priority = PRIO_UNDEF;
                return 1;
        }
-       condlog(3, "%s: %s prio = %u",
+       condlog((old_prio == pp->priority ? 4 : 3), "%s: %s prio = %u",
                pp->dev, prio_name(p), pp->priority);
        return 0;
 }
 
+/*
+ * Mangle string of length *len starting at start
+ * by removing character sequence "00" (hex for a 0 byte),
+ * starting at end, backwards.
+ * Changes the value of *len if characters were removed.
+ * Returns a pointer to the position where "end" was moved to.
+ */
+static char
+*skip_zeroes_backward(char* start, int *len, char *end)
+{
+       char *p = end;
+
+       while (p >= start + 2 && *(p - 1) == '0' && *(p - 2) == '0')
+               p -= 2;
+
+       if (p == end)
+               return p;
+
+       memmove(p, end, start + *len + 1 - end);
+       *len -= end - p;
+
+       return p;
+}
+
+/*
+ * Fix for NVME wwids looking like this:
+ * nvme.0000-3163653363666438366239656630386200-4c696e75780000000000000000000000000000000000000000000000000000000000000000000000-00000002
+ * which are encountered in some combinations of Linux NVME host and target.
+ * The '00' are hex-encoded 0-bytes which are forbidden in the serial (SN)
+ * and model (MN) fields. Discard them.
+ * If a WWID of the above type is found, sets pp->wwid and returns a value > 0.
+ * Otherwise, returns 0.
+ */
+static int
+fix_broken_nvme_wwid(struct path *pp, const char *value, int size)
+{
+       static const char _nvme[] = "nvme.";
+       int len, i;
+       char mangled[256];
+       char *p;
+
+       len = strlen(value);
+       if (len >= sizeof(mangled))
+               return 0;
+
+       /* Check that value starts with "nvme.%04x-" */
+       if (memcmp(value, _nvme, sizeof(_nvme) - 1) || value[9] != '-')
+               return 0;
+       for (i = 5; i < 9; i++)
+               if (!isxdigit(value[i]))
+                       return 0;
+
+       memcpy(mangled, value, len + 1);
+
+       /* search end of "model" part and strip trailing '00' */
+       p = memrchr(mangled, '-', len);
+       if (p == NULL)
+               return 0;
+
+       p = skip_zeroes_backward(mangled, &len, p);
+
+       /* search end of "serial" part */
+       p = memrchr(mangled, '-', p - mangled);
+       if (p == NULL || memrchr(mangled, '-', p - mangled) != mangled + 9)
+           /* We expect exactly 3 '-' in the value */
+               return 0;
+
+       p = skip_zeroes_backward(mangled, &len, p);
+       if (len >= size)
+               return 0;
+
+       memcpy(pp->wwid, mangled, len + 1);
+       condlog(2, "%s: over-long WWID shortened to %s", pp->dev, pp->wwid);
+       return len;
+}
+
 static int
-get_udev_uid(struct path * pp, char *uid_attribute)
+get_udev_uid(struct path * pp, char *uid_attribute, struct udev_device *udev)
 {
        ssize_t len;
        const char *value;
 
-       value = udev_device_get_property_value(pp->udev,
-                                              uid_attribute);
-       if ((!value || strlen(value) == 0) && conf->cmd == CMD_VALID_PATH)
+       value = udev_device_get_property_value(udev, uid_attribute);
+       if (!value || strlen(value) == 0)
                value = getenv(uid_attribute);
        if (value && strlen(value)) {
-               if (strlen(value) + 1 > WWID_SIZE) {
+               len = strlcpy(pp->wwid, value, WWID_SIZE);
+               if (len >= WWID_SIZE) {
+                       len = fix_broken_nvme_wwid(pp, value, WWID_SIZE);
+                       if (len > 0)
+                               return len;
                        condlog(0, "%s: wwid overflow", pp->dev);
                        len = WWID_SIZE;
-               } else {
-                       len = strlen(value);
                }
-               strncpy(pp->wwid, value, len);
        } else {
                condlog(3, "%s: no %s attribute", pp->dev,
                        uid_attribute);
@@ -1502,22 +1748,70 @@ get_vpd_uid(struct path * pp)
                parent = udev_device_get_parent(parent);
        }
 
+       if (!parent)
+               return -EINVAL;
+
        return get_vpd_sysfs(parent, 0x83, pp->wwid, WWID_SIZE);
 }
 
-static int
-get_uid (struct path * pp)
+static ssize_t uid_fallback(struct path *pp, int path_state,
+                           const char **origin, ssize_t old_len)
+{
+       ssize_t len = old_len;
+       int retrigger;
+       struct config *conf;
+
+       conf = get_multipath_config();
+       retrigger = conf->retrigger_tries;
+       put_multipath_config(conf);
+       if (pp->retriggers >= retrigger) {
+               if (pp->bus == SYSFS_BUS_SCSI &&
+                   !strcmp(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE)) {
+                       len = get_vpd_uid(pp);
+                       *origin = "sysfs";
+                       pp->uid_attribute = NULL;
+                       if (len < 0 && path_state == PATH_UP) {
+                               condlog(1, "%s: failed to get sysfs uid: %s",
+                                       pp->dev, strerror(-len));
+                               len = get_vpd_sgio(pp->fd, 0x83, pp->wwid,
+                                                  WWID_SIZE);
+                               *origin = "sgio";
+                       }
+               } else if (pp->bus == SYSFS_BUS_NVME) {
+                       char value[256];
+                       len = sysfs_attr_get_value(pp->udev, "wwid", value,
+                                                  sizeof(value));
+                       if (len <= 0)
+                               return -1;
+                       len = strlcpy(pp->wwid, value, WWID_SIZE);
+                       if (len >= WWID_SIZE) {
+                               len = fix_broken_nvme_wwid(pp, value,
+                                                          WWID_SIZE);
+                               if (len > 0)
+                                       return len;
+                               condlog(0, "%s: wwid overflow", pp->dev);
+                               len = WWID_SIZE;
+                       }
+                       *origin = "sysfs";
+                       pp->uid_attribute = NULL;
+               }
+       }
+       return len;
+}
+
+int
+get_uid (struct path * pp, int path_state, struct udev_device *udev)
 {
        char *c;
        const char *origin = "unknown";
        ssize_t len = 0;
+       struct config *conf;
 
-       if (!pp->uid_attribute && !pp->getuid)
-               select_getuid(pp);
-
-       if (!pp->udev) {
-               condlog(1, "%s: no udev information", pp->dev);
-               return 1;
+       if (!pp->uid_attribute && !pp->getuid) {
+               conf = get_multipath_config();
+               pthread_cleanup_push(put_multipath_config, conf);
+               select_getuid(conf, pp);
+               pthread_cleanup_pop(1);
        }
 
        memset(pp->wwid, 0, WWID_SIZE);
@@ -1526,45 +1820,40 @@ get_uid (struct path * pp)
 
                /* Use 'getuid' callout, deprecated */
                condlog(1, "%s: using deprecated getuid callout", pp->dev);
-               if (apply_format(pp->getuid, &buff[0], pp)) {
+               if (path_state != PATH_UP) {
+                       condlog(3, "%s: path inaccessible", pp->dev);
+                       len = -EWOULDBLOCK;
+               } else if (apply_format(pp->getuid, &buff[0], pp)) {
                        condlog(0, "error formatting uid callout command");
-                       memset(pp->wwid, 0, WWID_SIZE);
                        len = -EINVAL;
                } else if (execute_program(buff, pp->wwid, WWID_SIZE)) {
                        condlog(3, "error calling out %s", buff);
-                       memset(pp->wwid, 0, WWID_SIZE);
                        len = -EIO;
                } else
                        len = strlen(pp->wwid);
                origin = "callout";
        } else {
-               if (pp->uid_attribute) {
-                       len = get_udev_uid(pp, pp->uid_attribute);
+
+               if (udev && pp->uid_attribute) {
+                       len = get_udev_uid(pp, pp->uid_attribute, udev);
                        origin = "udev";
                        if (len <= 0)
                                condlog(1,
                                        "%s: failed to get udev uid: %s",
                                        pp->dev, strerror(-len));
 
-               }
-               if (len <= 0 &&
-                   !strcmp(pp->uid_attribute, DEFAULT_UID_ATTRIBUTE)) {
+               } else if (pp->bus == SYSFS_BUS_SCSI) {
                        len = get_vpd_uid(pp);
                        origin = "sysfs";
-                       pp->uid_attribute = NULL;
-                       if (len < 0) {
-                               condlog(1, "%s: failed to get sysfs uid: %s",
-                                       pp->dev, strerror(-len));
-                               len = get_vpd_sgio(pp->fd, 0x83, pp->wwid,
-                                                  WWID_SIZE);
-                               origin = "sgio";
-                       }
                }
+               if (len <= 0)
+                       len = uid_fallback(pp, path_state, &origin, len);
        }
        if ( len < 0 ) {
                condlog(1, "%s: failed to get %s uid: %s",
                        pp->dev, origin, strerror(-len));
                memset(pp->wwid, 0x0, WWID_SIZE);
+               return 1;
        } else {
                /* Strip any trailing blanks */
                c = strchr(pp->wwid, '\0');
@@ -1579,32 +1868,79 @@ get_uid (struct path * pp)
        return 0;
 }
 
-extern int
-pathinfo (struct path *pp, vector hwtable, int mask)
+int pathinfo(struct path *pp, struct config *conf, int mask)
 {
        int path_state;
 
-       if (!pp)
+       if (!pp || !conf)
                return PATHINFO_FAILED;
 
-       condlog(3, "%s: mask = 0x%x", pp->dev, mask);
+       /*
+        * For behavior backward-compatibility with multipathd,
+        * the blacklisting by filter_property|devnode() is not
+        * limited by DI_BLACKLIST and occurs before this debug
+        * message with the mask value.
+        */
+       if (pp->udev) {
+               const char *hidden =
+                       udev_device_get_sysattr_value(pp->udev, "hidden");
+
+               if (hidden && !strcmp(hidden, "1")) {
+                       condlog(4, "%s: hidden", pp->dev);
+                       return PATHINFO_SKIPPED;
+               }
+               if (is_claimed_by_foreign(pp->udev) ||
+                   filter_property(conf, pp->udev, 4) > 0)
+                       return PATHINFO_SKIPPED;
+       }
+
+       if (filter_devnode(conf->blist_devnode,
+                          conf->elist_devnode,
+                          pp->dev) > 0)
+               return PATHINFO_SKIPPED;
+
+       condlog(4, "%s: mask = 0x%x", pp->dev, mask);
+
+       /*
+        * Sanity check: we need the device number to
+        * avoid inconsistent information in
+        * find_path_by_dev()/find_path_by_devt()
+        */
+       if (!strlen(pp->dev_t) && !(mask & DI_SYSFS)) {
+               condlog(1, "%s: empty device number", pp->dev);
+               mask |= DI_SYSFS;
+       }
 
        /*
         * fetch info available in sysfs
         */
-       if (mask & DI_SYSFS && sysfs_pathinfo(pp))
-               return PATHINFO_FAILED;
+       if (mask & DI_SYSFS) {
+               int rc = sysfs_pathinfo(pp, conf->hwtable);
+
+               if (rc != PATHINFO_OK)
+                       return rc;
+       }
 
        if (mask & DI_BLACKLIST && mask & DI_SYSFS) {
                if (filter_device(conf->blist_device, conf->elist_device,
-                                 pp->vendor_id, pp->product_id) > 0) {
+                                 pp->vendor_id, pp->product_id, pp->dev) > 0 ||
+                   filter_protocol(conf->blist_protocol, conf->elist_protocol,
+                                   pp) > 0)
                        return PATHINFO_SKIPPED;
-               }
        }
 
        path_state = path_offline(pp);
        if (path_state == PATH_REMOVED)
                goto blank;
+       else if (mask & DI_NOIO) {
+               if (mask & DI_CHECKER)
+                       /*
+                        * Avoid any IO on the device itself.
+                        * simply use the path_offline() return as its state
+                        */
+                       pp->chkrstate = pp->state = path_state;
+               return PATHINFO_OK;
+       }
 
        /*
         * fetch info not available through sysfs
@@ -1621,25 +1957,24 @@ pathinfo (struct path *pp, vector hwtable, int mask)
        if (mask & DI_SERIAL)
                get_geometry(pp);
 
-       if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI &&
-           scsi_ioctl_pathinfo(pp, mask))
-               goto blank;
+       if (path_state == PATH_UP && pp->bus == SYSFS_BUS_SCSI)
+               scsi_ioctl_pathinfo(pp, conf, mask);
 
-       if (pp->bus == SYSFS_BUS_CCISS &&
-           cciss_ioctl_pathinfo(pp, mask))
-               goto blank;
+       if (pp->bus == SYSFS_BUS_CCISS && mask & DI_SERIAL)
+               cciss_ioctl_pathinfo(pp);
 
        if (mask & DI_CHECKER) {
                if (path_state == PATH_UP) {
-                       pp->chkrstate = pp->state = get_state(pp, 0);
-                       if (pp->state == PATH_UNCHECKED ||
+                       int newstate = get_state(pp, conf, 0, path_state);
+                       if (newstate != PATH_PENDING ||
+                           pp->state == PATH_UNCHECKED ||
                            pp->state == PATH_WILD)
-                               goto blank;
+                               pp->chkrstate = pp->state = newstate;
                        if (pp->state == PATH_TIMEOUT)
                                pp->state = PATH_DOWN;
                        if (pp->state == PATH_UP && !pp->size) {
                                condlog(3, "%s: device size is 0, "
-                                       "path unuseable", pp->dev);
+                                       "path unusable", pp->dev);
                                pp->state = PATH_GHOST;
                        }
                } else {
@@ -1651,11 +1986,23 @@ pathinfo (struct path *pp, vector hwtable, int mask)
                }
        }
 
-       if ((mask & DI_WWID) && !strlen(pp->wwid))
-               get_uid(pp);
+       if ((mask & DI_WWID) && !strlen(pp->wwid)) {
+               get_uid(pp, path_state, pp->udev);
+               if (!strlen(pp->wwid)) {
+                       if (pp->bus == SYSFS_BUS_UNDEF)
+                               return PATHINFO_SKIPPED;
+                       if (pp->initialized != INIT_FAILED) {
+                               pp->initialized = INIT_MISSING_UDEV;
+                               pp->tick = conf->retrigger_delay;
+                       }
+                       return PATHINFO_OK;
+               }
+               else
+                       pp->tick = 1;
+       }
+
        if (mask & DI_BLACKLIST && mask & DI_WWID) {
-               if (!strlen(pp->wwid) ||
-                   filter_wwid(conf->blist_wwid, conf->elist_wwid,
+               if (filter_wwid(conf->blist_wwid, conf->elist_wwid,
                                pp->wwid, pp->dev) > 0) {
                        return PATHINFO_SKIPPED;
                }
@@ -1665,26 +2012,23 @@ pathinfo (struct path *pp, vector hwtable, int mask)
          * Retrieve path priority, even for PATH_DOWN paths if it has never
          * been successfully obtained before.
          */
-       if ((mask & DI_PRIO) && path_state == PATH_UP) {
+       if ((mask & DI_PRIO) && path_state == PATH_UP && strlen(pp->wwid)) {
                if (pp->state != PATH_DOWN || pp->priority == PRIO_UNDEF) {
-                       if (!strlen(pp->wwid))
-                               get_uid(pp);
-                       if (!strlen(pp->wwid))
-                               return PATHINFO_SKIPPED;
                        get_prio(pp);
                }
        }
 
-       pp->initialized = 1;
+       if ((mask & DI_ALL) == DI_ALL)
+               pp->initialized = INIT_OK;
        return PATHINFO_OK;
 
 blank:
        /*
         * Recoverable error, for example faulty or offline path
         */
-       memset(pp->wwid, 0, WWID_SIZE);
        pp->chkrstate = pp->state = PATH_DOWN;
-       pp->initialized = 0;
+       if (pp->initialized == INIT_NEW || pp->initialized == INIT_FAILED)
+               memset(pp->wwid, 0, WWID_SIZE);
 
-       return 0;
+       return PATHINFO_OK;
 }