/*
 * virutil.c: common, generic utility functions
 *
 * Copyright (C) 2006-2013 Red Hat, Inc.
 * Copyright (C) 2006 Daniel P. Berrange
 * Copyright (C) 2006, 2007 Binary Karma
 * Copyright (C) 2006 Shuveb Hussain
 *
 * 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
 * <http://www.gnu.org/licenses/>.
 *
 * Author: Daniel P. Berrange <berrange@redhat.com>
 * File created Jul 18, 2007 - Shuveb Hussain <shuveb@binarykarma.com>
 */

#include <config.h>

#include <stdlib.h>
#include <dirent.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <string.h>
#include <termios.h>
#include <locale.h>

#if HAVE_LIBDEVMAPPER_H
# include <libdevmapper.h>
#endif

#ifdef HAVE_PATHS_H
# include <paths.h>
#endif
#include <netdb.h>
#ifdef HAVE_GETPWUID_R
# include <pwd.h>
# include <grp.h>
#endif
#if WITH_CAPNG
# include <cap-ng.h>
# include <sys/prctl.h>
#endif

#ifdef WIN32
# ifdef HAVE_WINSOCK2_H
#  include <winsock2.h>
# endif
# include <windows.h>
# include <shlobj.h>
#endif

#include "c-ctype.h"
#include "virerror.h"
#include "virlog.h"
#include "virbuffer.h"
#include "viralloc.h"
#include "virthread.h"
#include "verify.h"
#include "virfile.h"
#include "vircommand.h"
#include "nonblocking.h"
#include "virprocess.h"
#include "virstring.h"
#include "virutil.h"

#ifndef NSIG
# define NSIG 32
#endif

verify(sizeof(gid_t) <= sizeof(unsigned int) &&
       sizeof(uid_t) <= sizeof(unsigned int));

#define VIR_FROM_THIS VIR_FROM_NONE

#ifndef WIN32

int virSetInherit(int fd, bool inherit) {
    int fflags;
    if ((fflags = fcntl(fd, F_GETFD)) < 0)
        return -1;
    if (inherit)
        fflags &= ~FD_CLOEXEC;
    else
        fflags |= FD_CLOEXEC;
    if ((fcntl(fd, F_SETFD, fflags)) < 0)
        return -1;
    return 0;
}

#else /* WIN32 */

int virSetInherit(int fd ATTRIBUTE_UNUSED, bool inherit ATTRIBUTE_UNUSED)
{
    /* FIXME: Currently creating child processes is not supported on
     * Win32, so there is no point in failing calls that are only relevant
     * when creating child processes. So just pretend that we changed the
     * inheritance property of the given fd as requested. */
    return 0;
}

#endif /* WIN32 */

int virSetBlocking(int fd, bool blocking) {
    return set_nonblocking_flag(fd, !blocking);
}

int virSetNonBlock(int fd) {
    return virSetBlocking(fd, false);
}

int virSetCloseExec(int fd)
{
    return virSetInherit(fd, false);
}

int
virPipeReadUntilEOF(int outfd, int errfd,
                    char **outbuf, char **errbuf) {

    struct pollfd fds[2];
    int i;
    bool finished[2];

    fds[0].fd = outfd;
    fds[0].events = POLLIN;
    fds[0].revents = 0;
    finished[0] = false;
    fds[1].fd = errfd;
    fds[1].events = POLLIN;
    fds[1].revents = 0;
    finished[1] = false;

    while (!(finished[0] && finished[1])) {

        if (poll(fds, ARRAY_CARDINALITY(fds), -1) < 0) {
            if ((errno == EAGAIN) || (errno == EINTR))
                continue;
            goto pollerr;
        }

        for (i = 0; i < ARRAY_CARDINALITY(fds); ++i) {
            char data[1024], **buf;
            int got, size;

            if (!(fds[i].revents))
                continue;
            else if (fds[i].revents & POLLHUP)
                finished[i] = true;

            if (!(fds[i].revents & POLLIN)) {
                if (fds[i].revents & POLLHUP)
                    continue;

                virReportError(VIR_ERR_INTERNAL_ERROR,
                               "%s", _("Unknown poll response."));
                goto error;
            }

            got = read(fds[i].fd, data, sizeof(data));

            if (got == sizeof(data))
                finished[i] = false;

            if (got == 0) {
                finished[i] = true;
                continue;
            }
            if (got < 0) {
                if (errno == EINTR)
                    continue;
                if (errno == EAGAIN)
                    break;
                goto pollerr;
            }

            buf = ((fds[i].fd == outfd) ? outbuf : errbuf);
            size = (*buf ? strlen(*buf) : 0);
            if (VIR_REALLOC_N(*buf, size+got+1) < 0)
                goto error;
            memmove(*buf+size, data, got);
            (*buf)[size+got] = '\0';
        }
        continue;

    pollerr:
        virReportSystemError(errno,
                             "%s", _("poll error"));
        goto error;
    }

    return 0;

error:
    VIR_FREE(*outbuf);
    VIR_FREE(*errbuf);
    return -1;
}

/* Convert C from hexadecimal character to integer.  */
int
virHexToBin(unsigned char c)
{
    switch (c) {
    default: return c - '0';
    case 'a': case 'A': return 10;
    case 'b': case 'B': return 11;
    case 'c': case 'C': return 12;
    case 'd': case 'D': return 13;
    case 'e': case 'E': return 14;
    case 'f': case 'F': return 15;
    }
}

/* Scale an integer VALUE in-place by an optional case-insensitive
 * SUFFIX, defaulting to SCALE if suffix is NULL or empty (scale is
 * typically 1 or 1024).  Recognized suffixes include 'b' or 'bytes',
 * as well as power-of-two scaling via binary abbreviations ('KiB',
 * 'MiB', ...) or their one-letter counterpart ('k', 'M', ...), and
 * power-of-ten scaling via SI abbreviations ('KB', 'MB', ...).
 * Ensure that the result does not exceed LIMIT.  Return 0 on success,
 * -1 with error message raised on failure.  */
int
virScaleInteger(unsigned long long *value, const char *suffix,
                unsigned long long scale, unsigned long long limit)
{
    if (!suffix || !*suffix) {
        if (!scale) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("invalid scale %llu"), scale);
            return -1;
        }
        suffix = "";
    } else if (STRCASEEQ(suffix, "b") || STRCASEEQ(suffix, "byte") ||
               STRCASEEQ(suffix, "bytes")) {
        scale = 1;
    } else {
        int base;

        if (!suffix[1] || STRCASEEQ(suffix + 1, "iB")) {
            base = 1024;
        } else if (c_tolower(suffix[1]) == 'b' && !suffix[2]) {
            base = 1000;
        } else {
            virReportError(VIR_ERR_INVALID_ARG,
                         _("unknown suffix '%s'"), suffix);
            return -1;
        }
        scale = 1;
        switch (c_tolower(*suffix)) {
        case 'e':
            scale *= base;
            /* fallthrough */
        case 'p':
            scale *= base;
            /* fallthrough */
        case 't':
            scale *= base;
            /* fallthrough */
        case 'g':
            scale *= base;
            /* fallthrough */
        case 'm':
            scale *= base;
            /* fallthrough */
        case 'k':
            scale *= base;
            break;
        default:
            virReportError(VIR_ERR_INVALID_ARG,
                           _("unknown suffix '%s'"), suffix);
            return -1;
        }
    }

    if (*value && *value > (limit / scale)) {
        virReportError(VIR_ERR_OVERFLOW, _("value too large: %llu%s"),
                       *value, suffix);
        return -1;
    }
    *value *= scale;
    return 0;
}


/**
 * virParseNumber:
 * @str: pointer to the char pointer used
 *
 * Parse an unsigned number
 *
 * Returns the unsigned number or -1 in case of error. @str will be
 *         updated to skip the number.
 */
int
virParseNumber(const char **str)
{
    int ret = 0;
    const char *cur = *str;

    if ((*cur < '0') || (*cur > '9'))
        return -1;

    while (c_isdigit(*cur)) {
        unsigned int c = *cur - '0';

        if ((ret > INT_MAX / 10) ||
            ((ret == INT_MAX / 10) && (c > INT_MAX % 10)))
            return -1;
        ret = ret * 10 + c;
        cur++;
    }
    *str = cur;
    return ret;
}

/**
 * virParseVersionString:
 * @str: const char pointer to the version string
 * @version: unsigned long pointer to output the version number
 * @allowMissing: true to treat 3 like 3.0.0, false to error out on
 * missing minor or micro
 *
 * Parse an unsigned version number from a version string. Expecting
 * 'major.minor.micro' format, ignoring an optional suffix.
 *
 * The major, minor and micro numbers are encoded into a single version number:
 *
 *   1000000 * major + 1000 * minor + micro
 *
 * Returns the 0 for success, -1 for error.
 */
int
virParseVersionString(const char *str, unsigned long *version,
                      bool allowMissing)
{
    unsigned int major, minor = 0, micro = 0;
    char *tmp;

    if (virStrToLong_ui(str, &tmp, 10, &major) < 0)
        return -1;

    if (!allowMissing && *tmp != '.')
        return -1;

    if ((*tmp == '.') && virStrToLong_ui(tmp + 1, &tmp, 10, &minor) < 0)
        return -1;

    if (!allowMissing && *tmp != '.')
        return -1;

    if ((*tmp == '.') && virStrToLong_ui(tmp + 1, &tmp, 10, &micro) < 0)
        return -1;

    if (major > UINT_MAX / 1000000 || minor > 999 || micro > 999)
        return -1;

    *version = 1000000 * major + 1000 * minor + micro;

    return 0;
}

int virEnumFromString(const char *const*types,
                      unsigned int ntypes,
                      const char *type)
{
    unsigned int i;
    if (!type)
        return -1;

    for (i = 0; i < ntypes; i++)
        if (STREQ(types[i], type))
            return i;

    return -1;
}

/* In case thread-safe locales are available */
#if HAVE_NEWLOCALE

static locale_t virLocale;

static int
virLocaleOnceInit(void)
{
    virLocale = newlocale(LC_ALL_MASK, "C", (locale_t)0);
    if (!virLocale)
        return -1;
    return 0;
}

VIR_ONCE_GLOBAL_INIT(virLocale)
#endif

/**
 * virDoubleToStr
 *
 * converts double to string with C locale (thread-safe).
 *
 * Returns -1 on error, size of the string otherwise.
 */
int
virDoubleToStr(char **strp, double number)
{
    int ret = -1;

#if HAVE_NEWLOCALE

    locale_t old_loc;

    if (virLocaleInitialize() < 0)
        goto error;

    old_loc = uselocale(virLocale);
    ret = virAsprintf(strp, "%lf", number);
    uselocale(old_loc);

#else

    char *radix, *tmp;
    struct lconv *lc;

    if ((ret = virAsprintf(strp, "%lf", number) < 0))
        goto error;

    lc = localeconv();
    radix = lc->decimal_point;
    tmp = strstr(*strp, radix);
    if (tmp) {
        *tmp = '.';
        if (strlen(radix) > 1)
            memmove(tmp + 1, tmp + strlen(radix), strlen(*strp) - (tmp - *strp));
    }

#endif /* HAVE_NEWLOCALE */
 error:
    return ret;
}


/**
 * Format @val as a base-10 decimal number, in the
 * buffer @buf of size @buflen. To allocate a suitable
 * sized buffer, the INT_BUFLEN(int) macro should be
 * used
 *
 * Returns pointer to start of the number in @buf
 */
char *
virFormatIntDecimal(char *buf, size_t buflen, int val)
{
    char *p = buf + buflen - 1;
    *p = '\0';
    if (val >= 0) {
        do {
            *--p = '0' + (val % 10);
            val /= 10;
        } while (val != 0);
    } else {
        do {
            *--p = '0' - (val % 10);
            val /= 10;
        } while (val != 0);
        *--p = '-';
    }
    return p;
}


const char *virEnumToString(const char *const*types,
                            unsigned int ntypes,
                            int type)
{
    if (type < 0 || type >= ntypes)
        return NULL;

    return types[type];
}

/* Translates a device name of the form (regex) /^[fhv]d[a-z]+[0-9]*$/
 * into the corresponding index (e.g. sda => 0, hdz => 25, vdaa => 26)
 * Note that any trailing string of digits is simply ignored.
 * @param name The name of the device
 * @return name's index, or -1 on failure
 */
int virDiskNameToIndex(const char *name) {
    const char *ptr = NULL;
    int idx = 0;
    static char const* const drive_prefix[] = {"fd", "hd", "vd", "sd", "xvd", "ubd"};
    unsigned int i;

    for (i = 0; i < ARRAY_CARDINALITY(drive_prefix); i++) {
        if (STRPREFIX(name, drive_prefix[i])) {
            ptr = name + strlen(drive_prefix[i]);
            break;
        }
    }

    if (!ptr)
        return -1;

    for (i = 0; *ptr; i++) {
        if (!c_islower(*ptr))
            break;

        idx = (idx + (i < 1 ? 0 : 1)) * 26;
        idx += *ptr - 'a';
        ptr++;
    }

    /* Count the trailing digits.  */
    size_t n_digits = strspn(ptr, "0123456789");
    if (ptr[n_digits] != '\0')
        return -1;

    return idx;
}

char *virIndexToDiskName(int idx, const char *prefix)
{
    char *name = NULL;
    int i, k, offset;

    if (idx < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("Disk index %d is negative"), idx);
        return NULL;
    }

    for (i = 0, k = idx; k >= 0; ++i, k = k / 26 - 1) { }

    offset = strlen(prefix);

    if (VIR_ALLOC_N(name, offset + i + 1))
        return NULL;

    strcpy(name, prefix);
    name[offset + i] = '\0';

    for (i = i - 1, k = idx; k >= 0; --i, k = k / 26 - 1) {
        name[offset + i] = 'a' + (k % 26);
    }

    return name;
}

#ifndef AI_CANONIDN
# define AI_CANONIDN 0
#endif

/* Who knew getting a hostname could be so delicate.  In Linux (and Unices
 * in general), many things depend on "hostname" returning a value that will
 * resolve one way or another.  In the modern world where networks frequently
 * come and go this is often being hard-coded to resolve to "localhost".  If
 * it *doesn't* resolve to localhost, then we would prefer to have the FQDN.
 * That leads us to 3 possibilities:
 *
 * 1)  gethostname() returns an FQDN (not localhost) - we return the string
 *     as-is, it's all of the information we want
 * 2)  gethostname() returns "localhost" - we return localhost; doing further
 *     work to try to resolve it is pointless
 * 3)  gethostname() returns a shortened hostname - in this case, we want to
 *     try to resolve this to a fully-qualified name.  Therefore we pass it
 *     to getaddrinfo().  There are two possible responses:
 *     a)  getaddrinfo() resolves to a FQDN - return the FQDN
 *     b)  getaddrinfo() fails or resolves to localhost - in this case, the
 *         data we got from gethostname() is actually more useful than what
 *         we got from getaddrinfo().  Return the value from gethostname()
 *         and hope for the best.
 */
char *virGetHostname(void)
{
    int r;
    char hostname[HOST_NAME_MAX+1], *result;
    struct addrinfo hints, *info;

    r = gethostname(hostname, sizeof(hostname));
    if (r == -1) {
        virReportSystemError(errno,
                             "%s", _("failed to determine host name"));
        return NULL;
    }
    NUL_TERMINATE(hostname);

    if (STRPREFIX(hostname, "localhost") || strchr(hostname, '.')) {
        /* in this case, gethostname returned localhost (meaning we can't
         * do any further canonicalization), or it returned an FQDN (and
         * we don't need to do any further canonicalization).  Return the
         * string as-is; it's up to callers to check whether "localhost"
         * is allowed.
         */
        ignore_value(VIR_STRDUP(result, hostname));
        goto cleanup;
    }

    /* otherwise, it's a shortened, non-localhost, hostname.  Attempt to
     * canonicalize the hostname by running it through getaddrinfo
     */

    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_CANONNAME|AI_CANONIDN;
    hints.ai_family = AF_UNSPEC;
    r = getaddrinfo(hostname, NULL, &hints, &info);
    if (r != 0) {
        VIR_WARN("getaddrinfo failed for '%s': %s",
                 hostname, gai_strerror(r));
        ignore_value(VIR_STRDUP(result, hostname));
        goto cleanup;
    }

    /* Tell static analyzers about getaddrinfo semantics.  */
    sa_assert(info);

    if (info->ai_canonname == NULL ||
        STRPREFIX(info->ai_canonname, "localhost"))
        /* in this case, we tried to canonicalize and we ended up back with
         * localhost.  Ignore the canonicalized name and just return the
         * original hostname
         */
        ignore_value(VIR_STRDUP(result, hostname));
    else
        /* Caller frees this string. */
        ignore_value(VIR_STRDUP(result, info->ai_canonname));

    freeaddrinfo(info);

cleanup:
    return result;
}

#ifdef HAVE_GETPWUID_R
enum {
    VIR_USER_ENT_DIRECTORY,
    VIR_USER_ENT_NAME,
};

static char *virGetUserEnt(uid_t uid,
                           int field)
{
    char *strbuf;
    char *ret;
    struct passwd pwbuf;
    struct passwd *pw = NULL;
    long val = sysconf(_SC_GETPW_R_SIZE_MAX);
    size_t strbuflen = val;
    int rc;

    /* sysconf is a hint; if it fails, fall back to a reasonable size */
    if (val < 0)
        strbuflen = 1024;

    if (VIR_ALLOC_N(strbuf, strbuflen) < 0)
        return NULL;

    /*
     * From the manpage (terrifying but true):
     *
     * ERRORS
     *  0 or ENOENT or ESRCH or EBADF or EPERM or ...
     *        The given name or uid was not found.
     */
    while ((rc = getpwuid_r(uid, &pwbuf, strbuf, strbuflen, &pw)) == ERANGE) {
        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0)
            VIR_FREE(strbuf);
            return NULL;
    }
    if (rc != 0 || pw == NULL) {
        virReportSystemError(rc,
                             _("Failed to find user record for uid '%u'"),
                             (unsigned int) uid);
        VIR_FREE(strbuf);
        return NULL;
    }

    ignore_value(VIR_STRDUP(ret, field == VIR_USER_ENT_DIRECTORY ?
                            pw->pw_dir : pw->pw_name));
    VIR_FREE(strbuf);
    return ret;
}

static char *virGetGroupEnt(gid_t gid)
{
    char *strbuf;
    char *ret;
    struct group grbuf;
    struct group *gr = NULL;
    long val = sysconf(_SC_GETGR_R_SIZE_MAX);
    size_t strbuflen = val;
    int rc;

    /* sysconf is a hint; if it fails, fall back to a reasonable size */
    if (val < 0)
        strbuflen = 1024;

    if (VIR_ALLOC_N(strbuf, strbuflen) < 0)
        return NULL;

    /*
     * From the manpage (terrifying but true):
     *
     * ERRORS
     *  0 or ENOENT or ESRCH or EBADF or EPERM or ...
     *        The given name or gid was not found.
     */
    while ((rc = getgrgid_r(gid, &grbuf, strbuf, strbuflen, &gr)) == ERANGE) {
        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) {
            VIR_FREE(strbuf);
            return NULL;
        }
    }
    if (rc != 0 || gr == NULL) {
        virReportSystemError(rc,
                             _("Failed to find group record for gid '%u'"),
                             (unsigned int) gid);
        VIR_FREE(strbuf);
        return NULL;
    }

    ignore_value(VIR_STRDUP(ret, gr->gr_name));
    VIR_FREE(strbuf);
    return ret;
}

char *virGetUserDirectory(void)
{
    return virGetUserEnt(geteuid(), VIR_USER_ENT_DIRECTORY);
}

static char *virGetXDGDirectory(const char *xdgenvname, const char *xdgdefdir)
{
    const char *path = getenv(xdgenvname);
    char *ret = NULL;
    char *home = NULL;

    if (path && path[0]) {
        ignore_value(virAsprintf(&ret, "%s/libvirt", path));
    } else {
        home = virGetUserEnt(geteuid(), VIR_USER_ENT_DIRECTORY);
        ignore_value(virAsprintf(&ret, "%s/%s/libvirt", home, xdgdefdir));
    }

    VIR_FREE(home);
    return ret;
}

char *virGetUserConfigDirectory(void)
{
    return virGetXDGDirectory("XDG_CONFIG_HOME", ".config");
}

char *virGetUserCacheDirectory(void)
{
     return virGetXDGDirectory("XDG_CACHE_HOME", ".cache");
}

char *virGetUserRuntimeDirectory(void)
{
    const char *path = getenv("XDG_RUNTIME_DIR");

    if (!path || !path[0]) {
        return virGetUserCacheDirectory();
    } else {
        char *ret;

        ignore_value(virAsprintf(&ret, "%s/libvirt", path));
        return ret;
    }
}

char *virGetUserName(uid_t uid)
{
    return virGetUserEnt(uid, VIR_USER_ENT_NAME);
}

char *virGetGroupName(gid_t gid)
{
    return virGetGroupEnt(gid);
}

/* Search in the password database for a user id that matches the user name
 * `name`. Returns 0 on success, -1 on failure or 1 if name cannot be found.
 */
static int
virGetUserIDByName(const char *name, uid_t *uid)
{
    char *strbuf = NULL;
    struct passwd pwbuf;
    struct passwd *pw = NULL;
    long val = sysconf(_SC_GETPW_R_SIZE_MAX);
    size_t strbuflen = val;
    int rc;
    int ret = -1;

    /* sysconf is a hint; if it fails, fall back to a reasonable size */
    if (val < 0)
        strbuflen = 1024;

    if (VIR_ALLOC_N(strbuf, strbuflen) < 0)
        goto cleanup;

    while ((rc = getpwnam_r(name, &pwbuf, strbuf, strbuflen, &pw)) == ERANGE) {
        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0)
            goto cleanup;
    }

    if (!pw) {
        if (rc != 0) {
            char buf[1024];
            /* log the possible error from getpwnam_r. Unfortunately error
             * reporting from this function is bad and we can't really
             * rely on it, so we just report that the user wasn't found */
            VIR_WARN("User record for user '%s' was not found: %s",
                     name, virStrerror(rc, buf, sizeof(buf)));
        }

        ret = 1;
        goto cleanup;
    }

    *uid = pw->pw_uid;
    ret = 0;

cleanup:
    VIR_FREE(strbuf);

    return ret;
}

/* Try to match a user id based on `user`. The default behavior is to parse
 * `user` first as a user name and then as a user id. However if `user`
 * contains a leading '+', the rest of the string is always parsed as a uid.
 *
 * Returns 0 on success and -1 otherwise.
 */
int
virGetUserID(const char *user, uid_t *uid)
{
    unsigned int uint_uid;

    if (*user == '+') {
        user++;
    } else {
        int rc = virGetUserIDByName(user, uid);
        if (rc <= 0)
            return rc;
    }

    if (virStrToLong_ui(user, NULL, 10, &uint_uid) < 0 ||
        ((uid_t) uint_uid) != uint_uid) {
        virReportError(VIR_ERR_INVALID_ARG, _("Failed to parse user '%s'"),
                       user);
        return -1;
    }

    *uid = uint_uid;

    return 0;
}

/* Search in the group database for a group id that matches the group name
 * `name`. Returns 0 on success, -1 on failure or 1 if name cannot be found.
 */
static int
virGetGroupIDByName(const char *name, gid_t *gid)
{
    char *strbuf = NULL;
    struct group grbuf;
    struct group *gr = NULL;
    long val = sysconf(_SC_GETGR_R_SIZE_MAX);
    size_t strbuflen = val;
    int rc;
    int ret = -1;

    /* sysconf is a hint; if it fails, fall back to a reasonable size */
    if (val < 0)
        strbuflen = 1024;

    if (VIR_ALLOC_N(strbuf, strbuflen) < 0)
        goto cleanup;

    while ((rc = getgrnam_r(name, &grbuf, strbuf, strbuflen, &gr)) == ERANGE) {
        if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0)
            goto cleanup;
    }

    if (!gr) {
        if (rc != 0) {
            char buf[1024];
            /* log the possible error from getgrnam_r. Unfortunately error
             * reporting from this function is bad and we can't really
             * rely on it, so we just report that the user wasn't found */
            VIR_WARN("Group record for user '%s' was not found: %s",
                     name, virStrerror(rc, buf, sizeof(buf)));
        }

        ret = 1;
        goto cleanup;
    }

    *gid = gr->gr_gid;
    ret = 0;

cleanup:
    VIR_FREE(strbuf);

    return ret;
}

/* Try to match a group id based on `group`. The default behavior is to parse
 * `group` first as a group name and then as a group id. However if `group`
 * contains a leading '+', the rest of the string is always parsed as a guid.
 *
 * Returns 0 on success and -1 otherwise.
 */
int
virGetGroupID(const char *group, gid_t *gid)
{
    unsigned int uint_gid;

    if (*group == '+') {
        group++;
    } else {
        int rc = virGetGroupIDByName(group, gid);
        if (rc <= 0)
            return rc;
    }

    if (virStrToLong_ui(group, NULL, 10, &uint_gid) < 0 ||
        ((gid_t) uint_gid) != uint_gid) {
        virReportError(VIR_ERR_INVALID_ARG, _("Failed to parse group '%s'"),
                       group);
        return -1;
    }

    *gid = uint_gid;

    return 0;
}

/* Set the real and effective uid and gid to the given values, and call
 * initgroups so that the process has all the assumed group membership of
 * that uid. return 0 on success, -1 on failure (the original system error
 * remains in errno).
 */
int
virSetUIDGID(uid_t uid, gid_t gid)
{
    int err;
    char *buf = NULL;

    if (gid != (gid_t)-1) {
        if (setregid(gid, gid) < 0) {
            virReportSystemError(err = errno,
                                 _("cannot change to '%u' group"),
                                 (unsigned int) gid);
            goto error;
        }
    }

    if (uid != (uid_t)-1) {
# ifdef HAVE_INITGROUPS
        struct passwd pwd, *pwd_result;
        size_t bufsize;
        int rc;

        bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
        if (bufsize == -1)
            bufsize = 16384;

        if (VIR_ALLOC_N(buf, bufsize) < 0) {
            err = ENOMEM;
            goto error;
        }
        while ((rc = getpwuid_r(uid, &pwd, buf, bufsize,
                                &pwd_result)) == ERANGE) {
            if (VIR_RESIZE_N(buf, bufsize, bufsize, bufsize) < 0) {
                err = ENOMEM;
                goto error;
            }
        }

        if (rc) {
            virReportSystemError(err = rc, _("cannot getpwuid_r(%u)"),
                                 (unsigned int) uid);
            goto error;
        }

        if (!pwd_result) {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("getpwuid_r failed to retrieve data "
                             "for uid '%u'"),
                           (unsigned int) uid);
            err = EINVAL;
            goto error;
        }

        if (initgroups(pwd.pw_name, pwd.pw_gid) < 0) {
            virReportSystemError(err = errno,
                                 _("cannot initgroups(\"%s\", %d)"),
                                 pwd.pw_name, (unsigned int) pwd.pw_gid);
            goto error;
        }
# endif
        if (setreuid(uid, uid) < 0) {
            virReportSystemError(err = errno,
                                 _("cannot change to uid to '%u'"),
                                 (unsigned int) uid);
            goto error;
        }
    }

    VIR_FREE(buf);
    return 0;

error:
    VIR_FREE(buf);
    errno = err;
    return -1;
}

#else /* ! HAVE_GETPWUID_R */

# ifdef WIN32
/* These methods are adapted from GLib2 under terms of LGPLv2+ */
static int
virGetWin32SpecialFolder(int csidl, char **path)
{
    char buf[MAX_PATH+1];
    LPITEMIDLIST pidl = NULL;
    int ret = 0;

    *path = NULL;

    if (SHGetSpecialFolderLocation(NULL, csidl, &pidl) == S_OK) {
        if (SHGetPathFromIDList(pidl, buf) && VIR_STRDUP(*path, buf) < 0)
            ret = -1;
        CoTaskMemFree(pidl);
    }
    return ret;
}

static int
virGetWin32DirectoryRoot(char **path)
{
    char windowsdir[MAX_PATH];

    *path = NULL;

    if (GetWindowsDirectory(windowsdir, ARRAY_CARDINALITY(windowsdir)))
    {
        const char *tmp;
        /* Usually X:\Windows, but in terminal server environments
         * might be an UNC path, AFAIK.
         */
        tmp = virFileSkipRoot(windowsdir);
        if (VIR_FILE_IS_DIR_SEPARATOR(tmp[-1]) &&
            tmp[-2] != ':')
            tmp--;

        windowsdir[tmp - windowsdir] = '\0';
    } else {
        strcpy(windowsdir, "C:\\");
    }

    return VIR_STRDUP(*path, windowsdir) < 0 ? -1 : 0;
}



char *
virGetUserDirectory(void)
{
    const char *dir;
    char *ret;

    dir = getenv("HOME");

    /* Only believe HOME if it is an absolute path and exists */
    if (dir) {
        if (!virFileIsAbsPath(dir) ||
            !virFileExists(dir))
            dir = NULL;
    }

    /* In case HOME is Unix-style (it happens), convert it to
     * Windows style.
     */
    if (dir) {
        char *p;
        while ((p = strchr(dir, '/')) != NULL)
            *p = '\\';
    }

    if (!dir)
        /* USERPROFILE is probably the closest equivalent to $HOME? */
        dir = getenv("USERPROFILE");

    if (VIR_STRDUP(ret, dir) < 0)
        return NULL;

    if (!ret &&
        virGetWin32SpecialFolder(CSIDL_PROFILE, &ret) < 0)
        return NULL;

    if (!ret &&
        virGetWin32DirectoryRoot(&ret) < 0)
        return NULL;

    if (!ret) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to determine home directory"));
        return NULL;
    }

    return ret;
}

char *
virGetUserConfigDirectory(void)
{
    char *ret;
    if (virGetWin32SpecialFolder(CSIDL_LOCAL_APPDATA, &ret) < 0)
        return NULL;

    if (!ret) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to determine config directory"));
        return NULL;
    }
    return ret;
}

char *
virGetUserCacheDirectory(void)
{
    char *ret;
    if (virGetWin32SpecialFolder(CSIDL_INTERNET_CACHE, &ret) < 0)
        return NULL;

    if (!ret) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Unable to determine config directory"));
        return NULL;
    }
    return ret;
}

char *
virGetUserRuntimeDirectory(void)
{
    return virGetUserCacheDirectory();
}
# else /* !HAVE_GETPWUID_R && !WIN32 */
char *
virGetUserDirectory(void)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetUserDirectory is not available"));

    return NULL;
}

char *
virGetUserConfigDirectory(void)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetUserConfigDirectory is not available"));

    return NULL;
}

char *
virGetUserCacheDirectory(void)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetUserCacheDirectory is not available"));

    return NULL;
}

char *
virGetUserRuntimeDirectory(void)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetUserRuntimeDirectory is not available"));

    return NULL;
}
# endif /* ! HAVE_GETPWUID_R && ! WIN32 */

char *
virGetUserName(uid_t uid ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetUserName is not available"));

    return NULL;
}

int virGetUserID(const char *name ATTRIBUTE_UNUSED,
                 uid_t *uid ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetUserID is not available"));

    return 0;
}


int virGetGroupID(const char *name ATTRIBUTE_UNUSED,
                  gid_t *gid ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetGroupID is not available"));

    return 0;
}

int
virSetUIDGID(uid_t uid ATTRIBUTE_UNUSED,
             gid_t gid ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virSetUIDGID is not available"));
    return -1;
}

char *
virGetGroupName(gid_t gid ATTRIBUTE_UNUSED)
{
    virReportError(VIR_ERR_INTERNAL_ERROR,
                   "%s", _("virGetGroupName is not available"));

    return NULL;
}
#endif /* HAVE_GETPWUID_R */

#if WITH_CAPNG
/* Set the real and effective uid and gid to the given values, while
 * maintaining the capabilities indicated by bits in @capBits. Return
 * 0 on success, -1 on failure (the original system error remains in
 * errno).
 */
int
virSetUIDGIDWithCaps(uid_t uid, gid_t gid, unsigned long long capBits,
                     bool clearExistingCaps)
{
    int ii, capng_ret, ret = -1;
    bool need_setgid = false, need_setuid = false;
    bool need_setpcap = false;

    /* First drop all caps (unless the requested uid is "unchanged" or
     * root and clearExistingCaps wasn't requested), then add back
     * those in capBits + the extra ones we need to change uid/gid and
     * change the capabilities bounding set.
     */

    if (clearExistingCaps || (uid != (uid_t)-1 && uid != 0))
       capng_clear(CAPNG_SELECT_BOTH);

    for (ii = 0; ii <= CAP_LAST_CAP; ii++) {
        if (capBits & (1ULL << ii)) {
            capng_update(CAPNG_ADD,
                         CAPNG_EFFECTIVE|CAPNG_INHERITABLE|
                         CAPNG_PERMITTED|CAPNG_BOUNDING_SET,
                         ii);
        }
    }

    if (gid != (gid_t)-1 &&
        !capng_have_capability(CAPNG_EFFECTIVE, CAP_SETGID)) {
        need_setgid = true;
        capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETGID);
    }
    if (uid != (uid_t)-1 &&
        !capng_have_capability(CAPNG_EFFECTIVE, CAP_SETUID)) {
        need_setuid = true;
        capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID);
    }
# ifdef PR_CAPBSET_DROP
    /* If newer kernel, we need also need setpcap to change the bounding set */
    if ((capBits || need_setgid || need_setuid) &&
        !capng_have_capability(CAPNG_EFFECTIVE, CAP_SETPCAP)) {
        need_setpcap = true;
    }
    if (need_setpcap)
        capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP);
# endif

    /* Tell system we want to keep caps across uid change */
    if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0)) {
        virReportSystemError(errno, "%s",
                             _("prctl failed to set KEEPCAPS"));
        goto cleanup;
    }

    /* Change to the temp capabilities */
    if ((capng_ret = capng_apply(CAPNG_SELECT_CAPS)) < 0) {
        /* Failed.  If we are running unprivileged, and the arguments make sense
         * for this scenario, assume we're starting some kind of setuid helper:
         * do not set any of capBits in the permitted or effective sets, and let
         * the program get them on its own.
         *
         * (Too bad we cannot restrict the bounding set to the capabilities we
         * would like the helper to have!).
         */
        if (getuid() > 0 && clearExistingCaps && !need_setuid && !need_setgid) {
            capng_clear(CAPNG_SELECT_CAPS);
        } else {
            virReportError(VIR_ERR_INTERNAL_ERROR,
                           _("cannot apply process capabilities %d"), capng_ret);
            goto cleanup;
        }
    }

    if (virSetUIDGID(uid, gid) < 0)
        goto cleanup;

    /* Tell it we are done keeping capabilities */
    if (prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0)) {
        virReportSystemError(errno, "%s",
                             _("prctl failed to reset KEEPCAPS"));
        goto cleanup;
    }

    /* Set bounding set while we have CAP_SETPCAP.  Unfortunately we cannot
     * do this if we failed to get the capability above, so ignore the
     * return value.
     */
    capng_apply(CAPNG_SELECT_BOUNDS);

    /* Drop the caps that allow setuid/gid (unless they were requested) */
    if (need_setgid)
        capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETGID);
    if (need_setuid)
        capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETUID);
    /* Throw away CAP_SETPCAP so no more changes */
    if (need_setpcap)
        capng_update(CAPNG_DROP, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_SETPCAP);

    if (((capng_ret = capng_apply(CAPNG_SELECT_CAPS)) < 0)) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("cannot apply process capabilities %d"), capng_ret);
        ret = -1;
        goto cleanup;
    }

    ret = 0;
cleanup:
    return ret;
}

#else
/*
 * On platforms without libcapng, the capabilities setting is treated
 * as a NOP.
 */

int
virSetUIDGIDWithCaps(uid_t uid, gid_t gid,
                     unsigned long long capBits ATTRIBUTE_UNUSED,
                     bool clearExistingCaps ATTRIBUTE_UNUSED)
{
    return virSetUIDGID(uid, gid);
}
#endif


#if defined(UDEVADM) || defined(UDEVSETTLE)
void virFileWaitForDevices(void)
{
# ifdef UDEVADM
    const char *const settleprog[] = { UDEVADM, "settle", NULL };
# else
    const char *const settleprog[] = { UDEVSETTLE, NULL };
# endif
    int exitstatus;

    if (access(settleprog[0], X_OK) != 0)
        return;

    /*
     * NOTE: we ignore errors here; this is just to make sure that any device
     * nodes that are being created finish before we try to scan them.
     * If this fails for any reason, we still have the backup of polling for
     * 5 seconds for device nodes.
     */
    if (virRun(settleprog, &exitstatus) < 0)
    {}
}
#else
void virFileWaitForDevices(void) {}
#endif

#if HAVE_LIBDEVMAPPER_H
bool
virIsDevMapperDevice(const char *dev_name)
{
    struct stat buf;

    if (!stat(dev_name, &buf) &&
        S_ISBLK(buf.st_mode) &&
        dm_is_dm_major(major(buf.st_rdev)))
            return true;

    return false;
}
#else
bool virIsDevMapperDevice(const char *dev_name ATTRIBUTE_UNUSED)
{
    return false;
}
#endif

bool
virValidateWWN(const char *wwn) {
    int i;
    const char *p = wwn;

    if (STRPREFIX(wwn, "0x")) {
        p += 2;
    }

    for (i = 0; p[i]; i++) {
        if (!c_isxdigit(p[i]))
            break;
    }

    if (i != 16 || p[i]) {
        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
                       _("Malformed wwn: %s"));
        return false;
    }

    return true;
}

bool
virStrIsPrint(const char *str)
{
    int i;

    for (i = 0; str[i]; i++)
        if (!c_isprint(str[i]))
            return false;

    return true;
}

#if defined(major) && defined(minor)
int
virGetDeviceID(const char *path, int *maj, int *min)
{
    struct stat sb;

    if (stat(path, &sb) < 0)
        return -errno;

    if (!S_ISBLK(sb.st_mode))
        return -EINVAL;

    if (maj)
        *maj = major(sb.st_rdev);
    if (min)
        *min = minor(sb.st_rdev);

    return 0;
}
#else
int
virGetDeviceID(const char *path ATTRIBUTE_UNUSED,
               int *maj ATTRIBUTE_UNUSED,
               int *min ATTRIBUTE_UNUSED)
{

    return -ENOSYS;
}
#endif

#define SYSFS_DEV_BLOCK_PATH "/sys/dev/block"

char *
virGetUnprivSGIOSysfsPath(const char *path,
                          const char *sysfs_dir)
{
    int maj, min;
    char *sysfs_path = NULL;
    int rc;

    if ((rc = virGetDeviceID(path, &maj, &min)) < 0) {
        virReportSystemError(-rc,
                             _("Unable to get device ID '%s'"),
                             path);
        return NULL;
    }

    ignore_value(virAsprintf(&sysfs_path, "%s/%d:%d/queue/unpriv_sgio",
                             sysfs_dir ? sysfs_dir : SYSFS_DEV_BLOCK_PATH,
                             maj, min));
    return sysfs_path;
}

int
virSetDeviceUnprivSGIO(const char *path,
                       const char *sysfs_dir,
                       int unpriv_sgio)
{
    char *sysfs_path = NULL;
    char *val = NULL;
    int ret = -1;
    int rc;

    if (!(sysfs_path = virGetUnprivSGIOSysfsPath(path, sysfs_dir)))
        return -1;

    if (!virFileExists(sysfs_path)) {
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                       _("unpriv_sgio is not supported by this kernel"));
        goto cleanup;
    }

    if (virAsprintf(&val, "%d", unpriv_sgio) < 0)
        goto cleanup;

    if ((rc = virFileWriteStr(sysfs_path, val, 0)) < 0) {
        virReportSystemError(-rc, _("failed to set %s"), sysfs_path);
        goto cleanup;
    }

    ret = 0;
cleanup:
    VIR_FREE(sysfs_path);
    VIR_FREE(val);
    return ret;
}

int
virGetDeviceUnprivSGIO(const char *path,
                       const char *sysfs_dir,
                       int *unpriv_sgio)
{
    char *sysfs_path = NULL;
    char *buf = NULL;
    char *tmp = NULL;
    int ret = -1;

    if (!(sysfs_path = virGetUnprivSGIOSysfsPath(path, sysfs_dir)))
        return -1;

    if (!virFileExists(sysfs_path)) {
        virReportError(VIR_ERR_OPERATION_INVALID, "%s",
                       _("unpriv_sgio is not supported by this kernel"));
        goto cleanup;
    }

    if (virFileReadAll(sysfs_path, 1024, &buf) < 0)
        goto cleanup;

    if ((tmp = strchr(buf, '\n')))
        *tmp = '\0';

    if (virStrToLong_i(buf, NULL, 10, unpriv_sgio) < 0) {
        virReportError(VIR_ERR_INTERNAL_ERROR,
                       _("failed to parse value of %s"), sysfs_path);
        goto cleanup;
    }

    ret = 0;
cleanup:
    VIR_FREE(sysfs_path);
    VIR_FREE(buf);
    return ret;
}

#ifdef __linux__
# define SYSFS_FC_HOST_PATH "/sys/class/fc_host/"
# define SYSFS_SCSI_HOST_PATH "/sys/class/scsi_host/"

/* virReadFCHost:
 * @sysfs_prefix: "fc_host" sysfs path, defaults to SYSFS_FC_HOST_PATH
 * @host: Host number, E.g. 5 of "fc_host/host5"
 * @entry: Name of the sysfs entry to read
 * @result: Return the entry value as string
 *
 * Read the value of sysfs "fc_host" entry.
 *
 * Returns 0 on success, and @result is filled with the entry value.
 * as string, Otherwise returns -1. Caller must free @result after
 * use.
 */
int
virReadFCHost(const char *sysfs_prefix,
              int host,
              const char *entry,
              char **result)
{
    char *sysfs_path = NULL;
    char *p = NULL;
    int ret = -1;
    char *buf = NULL;

    if (virAsprintf(&sysfs_path, "%s/host%d/%s",
                    sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH,
                    host, entry) < 0)
        goto cleanup;

    if (virFileReadAll(sysfs_path, 1024, &buf) < 0)
        goto cleanup;

    if ((p = strchr(buf, '\n')))
        *p = '\0';

    if ((p = strstr(buf, "0x")))
        p += strlen("0x");
    else
        p = buf;

    if (VIR_STRDUP(*result, p) < 0)
        goto cleanup;

    ret = 0;
cleanup:
    VIR_FREE(sysfs_path);
    VIR_FREE(buf);
    return ret;
}

bool
virIsCapableFCHost(const char *sysfs_prefix,
                   int host)
{
    char *sysfs_path = NULL;
    bool ret = false;

    if (virAsprintf(&sysfs_path, "%s/host%d",
                    sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH,
                    host) < 0)
        return false;

    if (access(sysfs_path, F_OK) == 0)
        ret = true;

    VIR_FREE(sysfs_path);
    return ret;
}

bool
virIsCapableVport(const char *sysfs_prefix,
                  int host)
{
    char *scsi_host_path = NULL;
    char *fc_host_path = NULL;
    int ret = false;

    if (virAsprintf(&fc_host_path,
                    "%s/host%d/%s",
                    sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH,
                    host,
                    "vport_create") < 0)
        return false;

    if (virAsprintf(&scsi_host_path,
                    "%s/host%d/%s",
                    sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_HOST_PATH,
                    host,
                    "vport_create") < 0)
        goto cleanup;

    if ((access(fc_host_path, F_OK) == 0) ||
        (access(scsi_host_path, F_OK) == 0))
        ret = true;

cleanup:
    VIR_FREE(fc_host_path);
    VIR_FREE(scsi_host_path);
    return ret;
}

int
virManageVport(const int parent_host,
               const char *wwpn,
               const char *wwnn,
               int operation)
{
    int ret = -1;
    char *operation_path = NULL, *vport_name = NULL;
    const char *operation_file = NULL;

    switch (operation) {
    case VPORT_CREATE:
        operation_file = "vport_create";
        break;
    case VPORT_DELETE:
        operation_file = "vport_delete";
        break;
    default:
        virReportError(VIR_ERR_OPERATION_INVALID,
                       _("Invalid vport operation (%d)"), operation);
        goto cleanup;
    }

    if (virAsprintf(&operation_path,
                    "%s/host%d/%s",
                    SYSFS_FC_HOST_PATH,
                    parent_host,
                    operation_file) < 0)
        goto cleanup;

    if (!virFileExists(operation_path)) {
        VIR_FREE(operation_path);
        if (virAsprintf(&operation_path,
                        "%s/host%d/%s",
                        SYSFS_SCSI_HOST_PATH,
                        parent_host,
                        operation_file) < 0)
            goto cleanup;

        if (!virFileExists(operation_path)) {
            virReportError(VIR_ERR_OPERATION_INVALID,
                           _("vport operation '%s' is not supported for host%d"),
                           operation_file, parent_host);
            goto cleanup;
        }
    }

    if (virAsprintf(&vport_name,
                    "%s:%s",
                    wwpn,
                    wwnn) < 0)
        goto cleanup;

    if (virFileWriteStr(operation_path, vport_name, 0) == 0)
        ret = 0;
    else
        virReportSystemError(errno,
                             _("Write of '%s' to '%s' during "
                               "vport create/delete failed"),
                             vport_name, operation_path);

cleanup:
    VIR_FREE(vport_name);
    VIR_FREE(operation_path);
    return ret;
}

/* virGetHostNameByWWN:
 *
 * Iterate over the sysfs tree to get FC host name (e.g. host5)
 * by wwnn,wwpn pair.
 */
char *
virGetFCHostNameByWWN(const char *sysfs_prefix,
                      const char *wwnn,
                      const char *wwpn)
{
    const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH;
    struct dirent *entry = NULL;
    DIR *dir = NULL;
    char *wwnn_path = NULL;
    char *wwpn_path = NULL;
    char *wwnn_buf = NULL;
    char *wwpn_buf = NULL;
    char *p;
    char *ret = NULL;

    if (!(dir = opendir(prefix))) {
        virReportSystemError(errno,
                             _("Failed to opendir path '%s'"),
                             prefix);
        return NULL;
    }

# define READ_WWN(wwn_path, buf)                      \
    do {                                              \
        if (virFileReadAll(wwn_path, 1024, &buf) < 0) \
            goto cleanup;                             \
        if ((p = strchr(buf, '\n')))                  \
            *p = '\0';                                \
        if (STRPREFIX(buf, "0x"))                     \
            p = buf + strlen("0x");                   \
        else                                          \
            p = buf;                                  \
    } while (0)

    while ((entry = readdir(dir))) {
        if (entry->d_name[0] == '.')
            continue;

        if (virAsprintf(&wwnn_path, "%s/%s/node_name", prefix,
                        entry->d_name) < 0)
            goto cleanup;

        if (!virFileExists(wwnn_path)) {
            VIR_FREE(wwnn_path);
            continue;
        }

        READ_WWN(wwnn_path, wwnn_buf);

        if (STRNEQ(wwnn, p)) {
            VIR_FREE(wwnn_buf);
            VIR_FREE(wwnn_path);
            continue;
        }

        if (virAsprintf(&wwpn_path, "%s/%s/port_name", prefix,
                        entry->d_name) < 0)
            goto cleanup;

        if (!virFileExists(wwpn_path)) {
            VIR_FREE(wwnn_buf);
            VIR_FREE(wwnn_path);
            VIR_FREE(wwpn_path);
            continue;
        }

        READ_WWN(wwpn_path, wwpn_buf);

        if (STRNEQ(wwpn, p)) {
            VIR_FREE(wwnn_path);
            VIR_FREE(wwpn_path);
            VIR_FREE(wwnn_buf);
            VIR_FREE(wwpn_buf);
            continue;
        }

        ignore_value(VIR_STRDUP(ret, entry->d_name));
        break;
    }

cleanup:
# undef READ_WWN
    closedir(dir);
    VIR_FREE(wwnn_path);
    VIR_FREE(wwpn_path);
    VIR_FREE(wwnn_buf);
    VIR_FREE(wwpn_buf);
    return ret;
}

# define PORT_STATE_ONLINE "Online"

/* virFindFCHostCapableVport:
 *
 * Iterate over the sysfs and find out the first online HBA which
 * supports vport, and not saturated. Returns the host name (e.g.
 * host5) on success, or NULL on failure.
 */
char *
virFindFCHostCapableVport(const char *sysfs_prefix)
{
    const char *prefix = sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH;
    DIR *dir = NULL;
    struct dirent *entry = NULL;
    char *max_vports = NULL;
    char *vports = NULL;
    char *state = NULL;
    char *ret = NULL;

    if (!(dir = opendir(prefix))) {
        virReportSystemError(errno,
                             _("Failed to opendir path '%s'"),
                             prefix);
        return NULL;
    }

    while ((entry = readdir(dir))) {
        unsigned int host;
        char *p = NULL;

        if (entry->d_name[0] == '.')
            continue;

        p = entry->d_name + strlen("host");
        if (virStrToLong_ui(p, NULL, 10, &host) == -1) {
            VIR_DEBUG("Failed to parse host number from '%s'",
                      entry->d_name);
            continue;
        }

        if (!virIsCapableVport(prefix, host))
            continue;

        if (virReadFCHost(prefix, host, "port_state", &state) < 0) {
             VIR_DEBUG("Failed to read port_state for host%d", host);
             continue;
        }

        /* Skip the not online FC host */
        if (STRNEQ(state, PORT_STATE_ONLINE)) {
            VIR_FREE(state);
            continue;
        }
        VIR_FREE(state);

        if (virReadFCHost(prefix, host, "max_npiv_vports", &max_vports) < 0) {
             VIR_DEBUG("Failed to read max_npiv_vports for host%d", host);
             continue;
        }

        if (virReadFCHost(prefix, host, "npiv_vports_inuse", &vports) < 0) {
             VIR_DEBUG("Failed to read npiv_vports_inuse for host%d", host);
             VIR_FREE(max_vports);
             continue;
        }

        /* Compare from the strings directly, instead of converting
         * the strings to integers first
         */
        if ((strlen(max_vports) >= strlen(vports)) ||
            ((strlen(max_vports) == strlen(vports)) &&
             strcmp(max_vports, vports) > 0)) {
            ignore_value(VIR_STRDUP(ret, entry->d_name));
            goto cleanup;
        }

        VIR_FREE(max_vports);
        VIR_FREE(vports);
    }

cleanup:
    closedir(dir);
    VIR_FREE(max_vports);
    VIR_FREE(vports);
    return ret;
}
#else
int
virReadFCHost(const char *sysfs_prefix ATTRIBUTE_UNUSED,
              int host ATTRIBUTE_UNUSED,
              const char *entry ATTRIBUTE_UNUSED,
              char **result ATTRIBUTE_UNUSED)
{
    virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
    return -1;
}

bool
virIsCapableFCHost(const char *sysfs_prefix ATTRIBUTE_UNUSED,
                   int host ATTRIBUTE_UNUSED)
{
    virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
    return false;
}

bool
virIsCapableVport(const char *sysfs_prefix ATTRIBUTE_UNUSED,
                  int host ATTRIBUTE_UNUSED)
{
    virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
    return false;
}

int
virManageVport(const int parent_host ATTRIBUTE_UNUSED,
               const char *wwpn ATTRIBUTE_UNUSED,
               const char *wwnn ATTRIBUTE_UNUSED,
               int operation ATTRIBUTE_UNUSED)
{
    virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
    return -1;
}

char *
virGetFCHostNameByWWN(const char *sysfs_prefix ATTRIBUTE_UNUSED,
                      const char *wwnn ATTRIBUTE_UNUSED,
                      const char *wwpn ATTRIBUTE_UNUSED)
{
    virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
    return NULL;
}

char *
virFindFCHostCapableVport(const char *sysfs_prefix ATTRIBUTE_UNUSED)
{
    virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
    return NULL;
}

#endif /* __linux__ */

/**
 * virCompareLimitUlong:
 *
 * Compare two unsigned long long numbers. Value '0' of the arguments has a
 * special meaning of 'unlimited' and thus greater than any other value.
 *
 * Returns 0 if the numbers are equal, -1 if b is greater, 1 if a is greater.
 */
int
virCompareLimitUlong(unsigned long long a, unsigned long b)
{
    if (a == b)
        return 0;

    if (a == 0 || a > b)
        return 1;

    return -1;
}
