diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index 8d3671cab4..97999758b6 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2144,6 +2144,7 @@ virParseOwnershipIds;
virParseVersionString;
virPipeReadUntilEOF;
virReadFCHost;
+virReadSCSIUniqueId;
virScaleInteger;
virSetBlocking;
virSetCloseExec;
diff --git a/src/util/virutil.c b/src/util/virutil.c
index 95d1ff9324..bb1d7ea5b2 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -1681,6 +1681,54 @@ virGetDeviceUnprivSGIO(const char *path,
# define SYSFS_FC_HOST_PATH "/sys/class/fc_host/"
# define SYSFS_SCSI_HOST_PATH "/sys/class/scsi_host/"
+/* virReadSCSIUniqueId:
+ * @sysfs_prefix: "scsi_host" sysfs path, defaults to SYSFS_SCSI_HOST_PATH
+ * @host: Host number, E.g. 5 of "scsi_host/host5"
+ * @result: Return the entry value as an unsigned int
+ *
+ * Read the value of the "scsi_host" unique_id file.
+ *
+ * Returns 0 on success, and @result is filled with the unique_id value
+ * Otherwise returns -1
+ */
+int
+virReadSCSIUniqueId(const char *sysfs_prefix,
+ int host,
+ int *result)
+{
+ char *sysfs_path = NULL;
+ char *p = NULL;
+ int ret = -1;
+ char *buf = NULL;
+ int unique_id;
+
+ if (virAsprintf(&sysfs_path, "%s/host%d/unique_id",
+ sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_HOST_PATH,
+ host) < 0)
+ goto cleanup;
+
+ if (virFileReadAll(sysfs_path, 1024, &buf) < 0)
+ goto cleanup;
+
+ if ((p = strchr(buf, '\n')))
+ *p = '\0';
+
+ if (virStrToLong_i(buf, NULL, 10, &unique_id) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("unable to parse unique_id: %s"), buf);
+
+ goto cleanup;
+ }
+
+ *result = unique_id;
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(sysfs_path);
+ VIR_FREE(buf);
+ return ret;
+}
+
/* virReadFCHost:
* @sysfs_prefix: "fc_host" sysfs path, defaults to SYSFS_FC_HOST_PATH
* @host: Host number, E.g. 5 of "fc_host/host5"
@@ -2033,6 +2081,15 @@ virFindFCHostCapableVport(const char *sysfs_prefix)
return ret;
}
#else
+int
+virReadSCSIUniqueId(const char *sysfs_prefix ATTRIBUTE_UNUSED,
+ int host ATTRIBUTE_UNUSED,
+ unsigned int *result ATTRIBUTE_UNUSED)
+{
+ virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
+ return -1;
+}
+
int
virReadFCHost(const char *sysfs_prefix ATTRIBUTE_UNUSED,
int host ATTRIBUTE_UNUSED,
diff --git a/src/util/virutil.h b/src/util/virutil.h
index 2bb74e229b..1407dfd9aa 100644
--- a/src/util/virutil.h
+++ b/src/util/virutil.h
@@ -164,6 +164,10 @@ int virGetDeviceUnprivSGIO(const char *path,
int *unpriv_sgio);
char *virGetUnprivSGIOSysfsPath(const char *path,
const char *sysfs_dir);
+int virReadSCSIUniqueId(const char *sysfs_prefix,
+ int host,
+ int *result)
+ ATTRIBUTE_NONNULL(3);
int virReadFCHost(const char *sysfs_prefix,
int host,
const char *entry,
diff --git a/tests/Makefile.am b/tests/Makefile.am
index bc1040a660..ecb2f3494b 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -83,6 +83,7 @@ EXTRA_DIST = \
domainsnapshotxml2xmlin \
domainsnapshotxml2xmlout \
fchostdata \
+ scsihostdata \
interfaceschemadata \
lxcconf2xmldata \
lxcxml2xmldata \
@@ -188,6 +189,7 @@ endif WITH_REMOTE
if WITH_LINUX
test_programs += fchosttest
+test_programs += scsihosttest
endif WITH_LINUX
if WITH_LIBVIRTD
@@ -1146,8 +1148,13 @@ fchosttest_SOURCES = \
fchosttest.c testutils.h testutils.c
fchosttest_LDADD = $(LDADDS)
+scsihosttest_SOURCES = \
+ scsihosttest.c testutils.h testutils.c
+scsihosttest_LDADD = $(LDADDS)
+
else ! WITH_LINUX
EXTRA_DIST += fchosttest.c
+EXTRA_DIST += scsihosttest.c
endif ! WITH_LINUX
if WITH_LINUX
diff --git a/tests/scsihosttest.c b/tests/scsihosttest.c
new file mode 100644
index 0000000000..990fe80129
--- /dev/null
+++ b/tests/scsihosttest.c
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * .
+ *
+ */
+
+#include
+
+#include "testutils.h"
+
+#ifdef __linux__
+
+# include
+# include
+# include "virstring.h"
+# include "virutil.h"
+# include "virerror.h"
+# include "virlog.h"
+
+# define VIR_FROM_THIS VIR_FROM_NONE
+
+VIR_LOG_INIT("tests.scsihosttest");
+
+char *scsihost_class_path;
+# define TEST_SCSIHOST_CLASS_PATH scsihost_class_path
+
+/*
+ * Initialized/create a mock sysfs environment with 4 scsi_host devices
+ * located on "0000:00:1f.1" and "0000:00:1f.2". Each directory will
+ * contain 4 unique_id files having the same value.
+ *
+ * The environment is:
+ *
+ * 4 files:
+ *
+ * sys/devices/pci0000:00/0000:00:1f.1/ata1/host0/scsi_host/host0/unique_id
+ * sys/devices/pci0000:00/0000:00:1f.1/ata2/host1/scsi_host/host1/unique_id
+ * sys/devices/pci0000:00/0000:00:1f.2/ata1/host0/scsi_host/host0/unique_id
+ * sys/devices/pci0000:00/0000:00:1f.2/ata2/host1/scsi_host/host1/unique_id
+ *
+ * 4 symlinks:
+ *
+ * sys/class/scsi_host/host0 -> link to 1f.1 host 0
+ * sys/class/scsi_host/host1 -> link to 1f.1 host 1
+ * sys/class/scsi_host/host2 -> link to 1f.2 host 0
+ * sys/class/scsi_host/host3 -> link to 1f.2 host 1
+ *
+ * The unique_id's for host0 and host2 are set to "1"
+ * The unique_id's for host1 and host3 are set to "2"
+ */
+
+static int
+create_scsihost(const char *fakesysfsdir, const char *devicepath,
+ const char *unique_id, const char *hostname)
+{
+ char *unique_id_path = NULL;
+ char *link_path = NULL;
+ char *spot;
+ int ret = -1;
+ int fd = -1;
+
+ if (virAsprintfQuiet(&unique_id_path, "%s/devices/pci0000:00/%s/unique_id",
+ fakesysfsdir, devicepath) < 0 ||
+ virAsprintfQuiet(&link_path, "%s/class/scsi_host/%s",
+ fakesysfsdir, hostname) < 0) {
+ fprintf(stderr, "Out of memory\n");
+ goto cleanup;
+ }
+
+ /* Rather than create path & file, temporarily snip off the file to
+ * create the path
+ */
+ if (!(spot = strstr(unique_id_path, "unique_id"))) {
+ fprintf(stderr, "Did not find unique_id in path\n");
+ goto cleanup;
+ }
+ spot--;
+ *spot = '\0';
+ if (virFileMakePathWithMode(unique_id_path, 0755) < 0) {
+ fprintf(stderr, "Unable to make path to '%s'\n", unique_id_path);
+ goto cleanup;
+ }
+ *spot = '/';
+
+ /* Rather than create path & file, temporarily snip off the file to
+ * create the path
+ */
+ if (!(spot = strstr(link_path, hostname))) {
+ fprintf(stderr, "Did not find hostname in path\n");
+ goto cleanup;
+ }
+ spot--;
+ *spot = '\0';
+ if (virFileMakePathWithMode(link_path, 0755) < 0) {
+ fprintf(stderr, "Unable to make path to '%s'\n", link_path);
+ goto cleanup;
+ }
+ *spot = '/';
+
+ if ((fd = open(unique_id_path, O_CREAT|O_WRONLY, 0444)) < 0) {
+ fprintf(stderr, "Unable to create '%s'\n", unique_id_path);
+ goto cleanup;
+ }
+
+ if (safewrite(fd, unique_id, 1) != 1) {
+ fprintf(stderr, "Unable to write '%s'\n", unique_id);
+ goto cleanup;
+ }
+ VIR_DEBUG("Created unique_id '%s'", unique_id_path);
+
+ /* The link is to the path not the file - so remove the file */
+ if (!(spot = strstr(unique_id_path, "unique_id"))) {
+ fprintf(stderr, "Did not find unique_id in path\n");
+ goto cleanup;
+ }
+ spot--;
+ *spot = '\0';
+ if (symlink(unique_id_path, link_path) < 0) {
+ fprintf(stderr, "Unable to create symlink '%s' to '%s'\n",
+ link_path, unique_id_path);
+ goto cleanup;
+ }
+ VIR_DEBUG("Created symlink '%s'", link_path);
+
+ ret = 0;
+
+ cleanup:
+ VIR_FORCE_CLOSE(fd);
+ VIR_FREE(unique_id_path);
+ VIR_FREE(link_path);
+ return ret;
+}
+
+static int
+init_scsihost_sysfs(const char *fakesysfsdir)
+{
+ int ret = 0;
+
+ if (create_scsihost(fakesysfsdir,
+ "0000:00:1f.1/ata1/host0/scsi_host/host0",
+ "1", "host0") < 0 ||
+ create_scsihost(fakesysfsdir,
+ "0000:00:1f.1/ata2/host1/scsi_host/host1",
+ "2", "host1") < 0 ||
+ create_scsihost(fakesysfsdir,
+ "0000:00:1f.2/ata1/host0/scsi_host/host0",
+ "1", "host2") < 0 ||
+ create_scsihost(fakesysfsdir,
+ "0000:00:1f.2/ata2/host1/scsi_host/host1",
+ "2", "host3") < 0)
+ ret = -1;
+
+ return ret;
+}
+
+/* Test virReadSCSIUniqueId */
+static int
+testVirReadSCSIUniqueId(const void *data ATTRIBUTE_UNUSED)
+{
+ int hostnum, unique_id;
+
+ for (hostnum = 0; hostnum < 4; hostnum++) {
+ if (virReadSCSIUniqueId(TEST_SCSIHOST_CLASS_PATH,
+ hostnum, &unique_id) < 0) {
+ fprintf(stderr, "Failed to read hostnum=%d unique_id\n", hostnum);
+ return -1;
+ }
+
+ /* host0 and host2 have unique_id == 1
+ * host1 and host3 have unique_id == 2
+ */
+ if ((hostnum == 0 || hostnum == 2) && unique_id != 1) {
+ fprintf(stderr, "The unique_id='%d' for hostnum=%d is wrong\n",
+ unique_id, hostnum);
+ return -1;
+ } else if ((hostnum == 1 || hostnum == 3) && unique_id != 2) {
+ fprintf(stderr, "The unique_id='%d' for hostnum=%d is wrong\n",
+ unique_id, hostnum);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+# define FAKESYSFSDIRTEMPLATE abs_builddir "/fakesysfsdir-XXXXXX"
+
+static int
+mymain(void)
+{
+ int ret = -1;
+ char *fakesysfsdir = NULL;
+
+ if (VIR_STRDUP_QUIET(fakesysfsdir, FAKESYSFSDIRTEMPLATE) < 0) {
+ fprintf(stderr, "Out of memory\n");
+ goto cleanup;
+ }
+
+ if (!mkdtemp(fakesysfsdir)) {
+ fprintf(stderr, "Cannot create fakesysfsdir");
+ goto cleanup;
+ }
+
+ setenv("LIBVIRT_FAKE_SYSFS_DIR", fakesysfsdir, 1);
+
+ if (init_scsihost_sysfs(fakesysfsdir) < 0) {
+ fprintf(stderr, "Failed to create fakesysfs='%s'\n", fakesysfsdir);
+ goto cleanup;
+ }
+
+ if (virAsprintfQuiet(&scsihost_class_path, "%s/class/scsi_host",
+ fakesysfsdir) < 0) {
+ fprintf(stderr, "Out of memory\n");
+ goto cleanup;
+ }
+ VIR_DEBUG("Reading from '%s'", scsihost_class_path);
+
+ if (virtTestRun("testVirReadSCSIUniqueId",
+ testVirReadSCSIUniqueId, NULL) < 0) {
+ ret = -1;
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+ if (getenv("LIBVIRT_SKIP_CLEANUP") == NULL)
+ virFileDeleteTree(fakesysfsdir);
+ VIR_FREE(fakesysfsdir);
+ VIR_FREE(scsihost_class_path);
+ return ret;
+}
+
+VIRT_TEST_MAIN(mymain)
+#else
+int
+main(void)
+{
+ return EXIT_AM_SKIP;
+}
+#endif