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