/* * 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 * . * * Author: Daniel P. Berrange * File created Jul 18, 2007 - Shuveb Hussain */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_LIBDEVMAPPER_H # include #endif #ifdef HAVE_PATHS_H # include #endif #include #ifdef HAVE_GETPWUID_R # include # include #endif #if WITH_CAPNG # include # include #endif #ifdef WIN32 # ifdef HAVE_WINSOCK2_H # include # endif # include # include #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) { virReportOOMError(); 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, µ) < 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)) { virReportOOMError(); 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: if (result == NULL) virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); 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 = virGetUserEnt(geteuid(), VIR_USER_ENT_DIRECTORY); if (path && path[0]) { if (virAsprintf(&ret, "%s/libvirt", path) < 0) goto no_memory; } else { if (virAsprintf(&ret, "%s/%s/libvirt", home, xdgdefdir) < 0) goto no_memory; } cleanup: VIR_FREE(home); return ret; no_memory: virReportOOMError(); goto cleanup; } 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; if (virAsprintf(&ret, "%s/libvirt", path) < 0) { virReportOOMError(); return NULL; } 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) { virReportOOMError(); goto cleanup; } while ((rc = getpwnam_r(name, &pwbuf, strbuf, strbuflen, &pw)) == ERANGE) { if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) { virReportOOMError(); 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) { virReportOOMError(); goto cleanup; } while ((rc = getgrnam_r(name, &grbuf, strbuf, strbuflen, &gr)) == ERANGE) { if (VIR_RESIZE_N(strbuf, strbuflen, strbuflen, strbuflen) < 0) { virReportOOMError(); 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) { virReportOOMError(); err = ENOMEM; goto error; } while ((rc = getpwuid_r(uid, &pwd, buf, bufsize, &pwd_result)) == ERANGE) { if (VIR_RESIZE_N(buf, bufsize, bufsize, bufsize) < 0) { virReportOOMError(); 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; } if (virAsprintf(&sysfs_path, "%s/%d:%d/queue/unpriv_sgio", sysfs_dir ? sysfs_dir : SYSFS_DEV_BLOCK_PATH, maj, min) < 0) { virReportOOMError(); return NULL; } 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) { virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); return false; } if (virAsprintf(&scsi_host_path, "%s/host%d/%s", sysfs_prefix ? sysfs_prefix : SYSFS_SCSI_HOST_PATH, host, "vport_create") < 0) { virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); 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", wwnn, wwpn) < 0) { virReportOOMError(); 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) { virReportOOMError(); 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) { virReportOOMError(); 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; }