/*
* 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
#if HAVE_MMAP
# include
#endif
#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
#if defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R
# include
#endif
#ifdef WIN32
# ifdef HAVE_WINSOCK2_H
# include
# endif
# include
# include
#endif
#include "c-ctype.h"
#include "dirname.h"
#include "virerror.h"
#include "virlog.h"
#include "virbuffer.h"
#include "virstoragefile.h"
#include "viralloc.h"
#include "virthread.h"
#include "verify.h"
#include "virfile.h"
#include "vircommand.h"
#include "nonblocking.h"
#include "passfd.h"
#include "virprocess.h"
#include "virstring.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
/* Like read(), but restarts after EINTR. Doesn't play
* nicely with nonblocking FD and EAGAIN, in which case
* you want to use bare read(). Or even use virSocket()
* if the FD is related to a socket rather than a plain
* file or pipe. */
ssize_t
saferead(int fd, void *buf, size_t count)
{
size_t nread = 0;
while (count > 0) {
ssize_t r = read(fd, buf, count);
if (r < 0 && errno == EINTR)
continue;
if (r < 0)
return r;
if (r == 0)
return nread;
buf = (char *)buf + r;
count -= r;
nread += r;
}
return nread;
}
/* Like write(), but restarts after EINTR. Doesn't play
* nicely with nonblocking FD and EAGAIN, in which case
* you want to use bare write(). Or even use virSocket()
* if the FD is related to a socket rather than a plain
* file or pipe. */
ssize_t
safewrite(int fd, const void *buf, size_t count)
{
size_t nwritten = 0;
while (count > 0) {
ssize_t r = write(fd, buf, count);
if (r < 0 && errno == EINTR)
continue;
if (r < 0)
return r;
if (r == 0)
return nwritten;
buf = (const char *)buf + r;
count -= r;
nwritten += r;
}
return nwritten;
}
#ifdef HAVE_POSIX_FALLOCATE
int safezero(int fd, off_t offset, off_t len)
{
int ret = posix_fallocate(fd, offset, len);
if (ret == 0)
return 0;
errno = ret;
return -1;
}
#else
# ifdef HAVE_MMAP
int safezero(int fd, off_t offset, off_t len)
{
int r;
char *buf;
/* memset wants the mmap'ed file to be present on disk so create a
* sparse file
*/
r = ftruncate(fd, offset + len);
if (r < 0)
return -1;
buf = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset);
if (buf == MAP_FAILED)
return -1;
memset(buf, 0, len);
munmap(buf, len);
return 0;
}
# else /* HAVE_MMAP */
int safezero(int fd, off_t offset, off_t len)
{
int r;
char *buf;
unsigned long long remain, bytes;
if (lseek(fd, offset, SEEK_SET) < 0)
return -1;
/* Split up the write in small chunks so as not to allocate lots of RAM */
remain = len;
bytes = 1024 * 1024;
r = VIR_ALLOC_N(buf, bytes);
if (r < 0) {
errno = ENOMEM;
return -1;
}
while (remain) {
if (bytes > remain)
bytes = remain;
r = safewrite(fd, buf, bytes);
if (r < 0) {
VIR_FREE(buf);
return -1;
}
/* safewrite() guarantees all data will be written */
remain -= bytes;
}
VIR_FREE(buf);
return 0;
}
# endif /* HAVE_MMAP */
#endif /* HAVE_POSIX_FALLOCATE */
int virFileStripSuffix(char *str,
const char *suffix)
{
int len = strlen(str);
int suffixlen = strlen(suffix);
if (len < suffixlen)
return 0;
if (!STREQ(str + len - suffixlen, suffix))
return 0;
str[len-suffixlen] = '\0';
return 1;
}
#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;
int finished[2];
fds[0].fd = outfd;
fds[0].events = POLLIN;
fds[0].revents = 0;
finished[0] = 0;
fds[1].fd = errfd;
fds[1].events = POLLIN;
fds[1].revents = 0;
finished[1] = 0;
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] = 1;
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] = 0;
if (got == 0) {
finished[i] = 1;
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;
}
/* Like gnulib's fread_file, but read no more than the specified maximum
number of bytes. If the length of the input is <= max_len, and
upon error while reading that data, it works just like fread_file. */
static char *
saferead_lim(int fd, size_t max_len, size_t *length)
{
char *buf = NULL;
size_t alloc = 0;
size_t size = 0;
int save_errno;
for (;;) {
int count;
int requested;
if (size + BUFSIZ + 1 > alloc) {
alloc += alloc / 2;
if (alloc < size + BUFSIZ + 1)
alloc = size + BUFSIZ + 1;
if (VIR_REALLOC_N(buf, alloc) < 0) {
save_errno = errno;
break;
}
}
/* Ensure that (size + requested <= max_len); */
requested = MIN(size < max_len ? max_len - size : 0,
alloc - size - 1);
count = saferead(fd, buf + size, requested);
size += count;
if (count != requested || requested == 0) {
save_errno = errno;
if (count < 0)
break;
buf[size] = '\0';
*length = size;
return buf;
}
}
VIR_FREE(buf);
errno = save_errno;
return NULL;
}
/* A wrapper around saferead_lim that maps a failure due to
exceeding the maximum size limitation to EOVERFLOW. */
int
virFileReadLimFD(int fd, int maxlen, char **buf)
{
size_t len;
char *s;
if (maxlen <= 0) {
errno = EINVAL;
return -1;
}
s = saferead_lim(fd, maxlen+1, &len);
if (s == NULL)
return -1;
if (len > maxlen || (int)len != len) {
VIR_FREE(s);
/* There was at least one byte more than MAXLEN.
Set errno accordingly. */
errno = EOVERFLOW;
return -1;
}
*buf = s;
return len;
}
int virFileReadAll(const char *path, int maxlen, char **buf)
{
int fd = open(path, O_RDONLY);
if (fd < 0) {
virReportSystemError(errno, _("Failed to open file '%s'"), path);
return -1;
}
int len = virFileReadLimFD(fd, maxlen, buf);
VIR_FORCE_CLOSE(fd);
if (len < 0) {
virReportSystemError(errno, _("Failed to read file '%s'"), path);
return -1;
}
return len;
}
/* Truncate @path and write @str to it. If @mode is 0, ensure that
@path exists; otherwise, use @mode if @path must be created.
Return 0 for success, nonzero for failure.
Be careful to preserve any errno value upon failure. */
int virFileWriteStr(const char *path, const char *str, mode_t mode)
{
int fd;
if (mode)
fd = open(path, O_WRONLY|O_TRUNC|O_CREAT, mode);
else
fd = open(path, O_WRONLY|O_TRUNC);
if (fd == -1)
return -1;
if (safewrite(fd, str, strlen(str)) < 0) {
VIR_FORCE_CLOSE(fd);
return -1;
}
/* Use errno from failed close only if there was no write error. */
if (VIR_CLOSE(fd) != 0)
return -1;
return 0;
}
int virFileMatchesNameSuffix(const char *file,
const char *name,
const char *suffix)
{
int filelen = strlen(file);
int namelen = strlen(name);
int suffixlen = strlen(suffix);
if (filelen == (namelen + suffixlen) &&
STREQLEN(file, name, namelen) &&
STREQLEN(file + namelen, suffix, suffixlen))
return 1;
else
return 0;
}
int virFileHasSuffix(const char *str,
const char *suffix)
{
int len = strlen(str);
int suffixlen = strlen(suffix);
if (len < suffixlen)
return 0;
return STRCASEEQ(str + len - suffixlen, suffix);
}
#define SAME_INODE(Stat_buf_1, Stat_buf_2) \
((Stat_buf_1).st_ino == (Stat_buf_2).st_ino \
&& (Stat_buf_1).st_dev == (Stat_buf_2).st_dev)
/* Return nonzero if checkLink and checkDest
refer to the same file. Otherwise, return 0. */
int virFileLinkPointsTo(const char *checkLink,
const char *checkDest)
{
struct stat src_sb;
struct stat dest_sb;
return (stat(checkLink, &src_sb) == 0
&& stat(checkDest, &dest_sb) == 0
&& SAME_INODE(src_sb, dest_sb));
}
static int
virFileResolveLinkHelper(const char *linkpath,
bool intermediatePaths,
char **resultpath)
{
struct stat st;
*resultpath = NULL;
/* We don't need the full canonicalization of intermediate
* directories, if linkpath is absolute and the basename is
* already a non-symlink. */
if (IS_ABSOLUTE_FILE_NAME(linkpath) && !intermediatePaths) {
if (lstat(linkpath, &st) < 0)
return -1;
if (!S_ISLNK(st.st_mode)) {
if (!(*resultpath = strdup(linkpath)))
return -1;
return 0;
}
}
*resultpath = canonicalize_file_name(linkpath);
return *resultpath == NULL ? -1 : 0;
}
/*
* Attempt to resolve a symbolic link, returning an
* absolute path where only the last component is guaranteed
* not to be a symlink.
*
* Return 0 if path was not a symbolic, or the link was
* resolved. Return -1 with errno set upon error
*/
int virFileResolveLink(const char *linkpath,
char **resultpath)
{
return virFileResolveLinkHelper(linkpath, false, resultpath);
}
/*
* Attempt to resolve a symbolic link, returning an
* absolute path where every component is guaranteed
* not to be a symlink.
*
* Return 0 if path was not a symbolic, or the link was
* resolved. Return -1 with errno set upon error
*/
int virFileResolveAllLinks(const char *linkpath,
char **resultpath)
{
return virFileResolveLinkHelper(linkpath, true, resultpath);
}
/*
* Check whether the given file is a link.
* Returns 1 in case of the file being a link, 0 in case it is not
* a link and the negative errno in all other cases.
*/
int virFileIsLink(const char *linkpath)
{
struct stat st;
if (lstat(linkpath, &st) < 0)
return -errno;
return S_ISLNK(st.st_mode) != 0;
}
/*
* Finds a requested executable file in the PATH env. e.g.:
* "kvm-img" will return "/usr/bin/kvm-img"
*
* You must free the result
*/
char *virFindFileInPath(const char *file)
{
char *path = NULL;
char *pathiter;
char *pathseg;
char *fullpath = NULL;
if (file == NULL)
return NULL;
/* if we are passed an absolute path (starting with /), return a
* copy of that path, after validating that it is executable
*/
if (IS_ABSOLUTE_FILE_NAME(file)) {
if (virFileIsExecutable(file))
return strdup(file);
else
return NULL;
}
/* If we are passed an anchored path (containing a /), then there
* is no path search - it must exist in the current directory
*/
if (strchr(file, '/')) {
if (virFileIsExecutable(file))
ignore_value(virFileAbsPath(file, &path));
return path;
}
/* copy PATH env so we can tweak it */
path = getenv("PATH");
if (path == NULL || (path = strdup(path)) == NULL)
return NULL;
/* for each path segment, append the file to search for and test for
* it. return it if found.
*/
pathiter = path;
while ((pathseg = strsep(&pathiter, ":")) != NULL) {
if (virAsprintf(&fullpath, "%s/%s", pathseg, file) < 0 ||
virFileIsExecutable(fullpath))
break;
VIR_FREE(fullpath);
}
VIR_FREE(path);
return fullpath;
}
bool virFileIsDir(const char *path)
{
struct stat s;
return (stat(path, &s) == 0) && S_ISDIR(s.st_mode);
}
bool virFileExists(const char *path)
{
return access(path, F_OK) == 0;
}
/* Check that a file is regular and has executable bits. If false is
* returned, errno is valid.
*
* Note: In the presence of ACLs, this may return true for a file that
* would actually fail with EACCES for a given user, or false for a
* file that the user could actually execute, but setups with ACLs
* that weird are unusual. */
bool
virFileIsExecutable(const char *file)
{
struct stat sb;
/* We would also want to check faccessat if we cared about ACLs,
* but we don't. */
if (stat(file, &sb) < 0)
return false;
if (S_ISREG(sb.st_mode) && (sb.st_mode & 0111) != 0)
return true;
errno = S_ISDIR(sb.st_mode) ? EISDIR : EACCES;
return false;
}
#ifndef WIN32
/* Check that a file is accessible under certain
* user & gid.
* @mode can be F_OK, or a bitwise combination of R_OK, W_OK, and X_OK.
* see 'man access' for more details.
* Returns 0 on success, -1 on fail with errno set.
*/
int
virFileAccessibleAs(const char *path, int mode,
uid_t uid, gid_t gid)
{
pid_t pid = 0;
int status, ret = 0;
int forkRet = 0;
if (uid == getuid() &&
gid == getgid())
return access(path, mode);
forkRet = virFork(&pid);
if (pid < 0) {
return -1;
}
if (pid) { /* parent */
if (virProcessWait(pid, &status) < 0) {
/* virProcessWait() already
* reported error */
return -1;
}
if (!WIFEXITED(status)) {
errno = EINTR;
return -1;
}
if (status) {
errno = WEXITSTATUS(status);
return -1;
}
return 0;
}
/* child.
* Return positive value here. Parent
* will change it to negative one. */
if (forkRet < 0) {
ret = errno;
goto childerror;
}
if (virSetUIDGID(uid, gid) < 0) {
ret = errno;
goto childerror;
}
if (access(path, mode) < 0)
ret = errno;
childerror:
if ((ret & 0xFF) != ret) {
VIR_WARN("unable to pass desired return value %d", ret);
ret = 0xFF;
}
_exit(ret);
}
/* virFileOpenForceOwnerMode() - an internal utility function called
* only by virFileOpenAs(). Sets the owner and mode of the file
* opened as "fd" if it's not correct AND the flags say it should be
* forced. */
static int
virFileOpenForceOwnerMode(const char *path, int fd, mode_t mode,
uid_t uid, gid_t gid, unsigned int flags)
{
int ret = 0;
struct stat st;
if (!(flags & (VIR_FILE_OPEN_FORCE_OWNER | VIR_FILE_OPEN_FORCE_MODE)))
return 0;
if (fstat(fd, &st) == -1) {
ret = -errno;
virReportSystemError(errno, _("stat of '%s' failed"), path);
return ret;
}
/* NB: uid:gid are never "-1" (default) at this point - the caller
* has always changed -1 to the value of get[gu]id().
*/
if ((flags & VIR_FILE_OPEN_FORCE_OWNER) &&
((st.st_uid != uid) || (st.st_gid != gid)) &&
(fchown(fd, uid, gid) < 0)) {
ret = -errno;
virReportSystemError(errno,
_("cannot chown '%s' to (%u, %u)"),
path, (unsigned int) uid,
(unsigned int) gid);
return ret;
}
if ((flags & VIR_FILE_OPEN_FORCE_MODE) &&
((mode & (S_IRWXU|S_IRWXG|S_IRWXO)) !=
(st.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO))) &&
(fchmod(fd, mode) < 0)) {
ret = -errno;
virReportSystemError(errno,
_("cannot set mode of '%s' to %04o"),
path, mode);
return ret;
}
return ret;
}
/* virFileOpenForked() - an internal utility function called only by
* virFileOpenAs(). It forks, then the child does setuid+setgid to
* given uid:gid and attempts to open the file, while the parent just
* calls recvfd to get the open fd back from the child. returns the
* fd, or -errno if there is an error. */
static int
virFileOpenForked(const char *path, int openflags, mode_t mode,
uid_t uid, gid_t gid, unsigned int flags)
{
pid_t pid;
int waitret, status, ret = 0;
int fd = -1;
int pair[2] = { -1, -1 };
int forkRet;
/* parent is running as root, but caller requested that the
* file be opened as some other user and/or group). The
* following dance avoids problems caused by root-squashing
* NFS servers. */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
ret = -errno;
virReportSystemError(errno,
_("failed to create socket needed for '%s'"),
path);
return ret;
}
forkRet = virFork(&pid);
if (pid < 0)
return -errno;
if (pid == 0) {
/* child */
VIR_FORCE_CLOSE(pair[0]); /* preserves errno */
if (forkRet < 0) {
/* error encountered and logged in virFork() after the fork. */
ret = -errno;
goto childerror;
}
/* set desired uid/gid, then attempt to create the file */
if (virSetUIDGID(uid, gid) < 0) {
ret = -errno;
goto childerror;
}
if ((fd = open(path, openflags, mode)) < 0) {
ret = -errno;
virReportSystemError(errno,
_("child process failed to create file '%s'"),
path);
goto childerror;
}
/* File is successfully open. Set permissions if requested. */
ret = virFileOpenForceOwnerMode(path, fd, mode, uid, gid, flags);
if (ret < 0)
goto childerror;
do {
ret = sendfd(pair[1], fd);
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
ret = -errno;
virReportSystemError(errno, "%s",
_("child process failed to send fd to parent"));
goto childerror;
}
childerror:
/* ret tracks -errno on failure, but exit value must be positive.
* If the child exits with EACCES, then the parent tries again. */
/* XXX This makes assumptions about errno being < 255, which is
* not true on Hurd. */
VIR_FORCE_CLOSE(pair[1]);
if (ret < 0) {
VIR_FORCE_CLOSE(fd);
}
ret = -ret;
if ((ret & 0xff) != ret) {
VIR_WARN("unable to pass desired return value %d", ret);
ret = 0xff;
}
_exit(ret);
}
/* parent */
VIR_FORCE_CLOSE(pair[1]);
do {
fd = recvfd(pair[0], 0);
} while (fd < 0 && errno == EINTR);
VIR_FORCE_CLOSE(pair[0]); /* NB: this preserves errno */
if (fd < 0 && errno != EACCES) {
ret = -errno;
while (waitpid(pid, NULL, 0) == -1 && errno == EINTR);
return ret;
}
/* wait for child to complete, and retrieve its exit code */
while ((waitret = waitpid(pid, &status, 0) == -1)
&& (errno == EINTR));
if (waitret == -1) {
ret = -errno;
virReportSystemError(errno,
_("failed to wait for child creating '%s'"),
path);
VIR_FORCE_CLOSE(fd);
return ret;
}
if (!WIFEXITED(status) || (ret = -WEXITSTATUS(status)) == -EACCES ||
fd == -1) {
/* fall back to the simpler method, which works better in
* some cases */
VIR_FORCE_CLOSE(fd);
if (flags & VIR_FILE_OPEN_NOFORK) {
/* If we had already tried opening w/o fork+setuid and
* failed, no sense trying again. Just set return the
* original errno that we got at that time (by
* definition, always either EACCES or EPERM - EACCES
* is close enough).
*/
return -EACCES;
}
if ((fd = open(path, openflags, mode)) < 0)
return -errno;
ret = virFileOpenForceOwnerMode(path, fd, mode, uid, gid, flags);
if (ret < 0) {
VIR_FORCE_CLOSE(fd);
return ret;
}
}
return fd;
}
/**
* virFileOpenAs:
* @path: file to open or create
* @openflags: flags to pass to open
* @mode: mode to use on creation or when forcing permissions
* @uid: uid that should own file on creation
* @gid: gid that should own file
* @flags: bit-wise or of VIR_FILE_OPEN_* flags
*
* Open @path, and return an fd to the open file. @openflags contains
* the flags normally passed to open(2), while those in @flags are
* used internally. If @flags includes VIR_FILE_OPEN_NOFORK, then try
* opening the file while executing with the current uid:gid
* (i.e. don't fork+setuid+setgid before the call to open()). If
* @flags includes VIR_FILE_OPEN_FORK, then try opening the file while
* the effective user id is @uid (by forking a child process); this
* allows one to bypass root-squashing NFS issues; NOFORK is always
* tried before FORK (the absence of both flags is treated identically
* to (VIR_FILE_OPEN_NOFORK | VIR_FILE_OPEN_FORK)). If @flags includes
* VIR_FILE_OPEN_FORCE_OWNER, then ensure that @path is owned by
* uid:gid before returning (even if it already existed with a
* different owner). If @flags includes VIR_FILE_OPEN_FORCE_MODE,
* ensure it has those permissions before returning (again, even if
* the file already existed with different permissions). The return
* value (if non-negative) is the file descriptor, left open. Returns
* -errno on failure. */
int
virFileOpenAs(const char *path, int openflags, mode_t mode,
uid_t uid, gid_t gid, unsigned int flags)
{
int ret = 0, fd = -1;
/* allow using -1 to mean "current value" */
if (uid == (uid_t) -1)
uid = getuid();
if (gid == (gid_t) -1)
gid = getgid();
/* treat absence of both flags as presence of both for simpler
* calling. */
if (!(flags & (VIR_FILE_OPEN_NOFORK|VIR_FILE_OPEN_FORK)))
flags |= VIR_FILE_OPEN_NOFORK|VIR_FILE_OPEN_FORK;
if ((flags & VIR_FILE_OPEN_NOFORK)
|| (getuid() != 0)
|| ((uid == 0) && (gid == 0))) {
if ((fd = open(path, openflags, mode)) < 0) {
ret = -errno;
} else {
ret = virFileOpenForceOwnerMode(path, fd, mode, uid, gid, flags);
if (ret < 0)
goto error;
}
}
/* If we either 1) didn't try opening as current user at all, or
* 2) failed, and errno/virStorageFileIsSharedFS indicate we might
* be successful if we try as a different uid, then try doing
* fork+setuid+setgid before opening.
*/
if ((fd < 0) && (flags & VIR_FILE_OPEN_FORK)) {
if (ret < 0) {
/* An open(2) that failed due to insufficient permissions
* could return one or the other of these depending on OS
* version and circumstances. Any other errno indicates a
* problem that couldn't be remedied by fork+setuid
* anyway. */
if (ret != -EACCES && ret != -EPERM)
goto error;
/* On Linux we can also verify the FS-type of the
* directory. (this is a NOP on other platforms). */
switch (virStorageFileIsSharedFS(path)) {
case 1:
/* it was on a network share, so we'll re-try */
break;
case -1:
/* failure detecting fstype */
virReportSystemError(errno, _("couldn't determine fs type "
"of mount containing '%s'"), path);
goto error;
case 0:
default:
/* file isn't on a recognized network FS */
goto error;
}
}
/* passed all prerequisites - retry the open w/fork+setuid */
if ((fd = virFileOpenForked(path, openflags, mode, uid, gid, flags)) < 0) {
ret = fd;
fd = -1;
goto error;
}
}
/* File is successfully opened */
return fd;
error:
if (fd < 0) {
/* whoever failed the open last has already set ret = -errno */
virReportSystemError(-ret, openflags & O_CREAT
? _("failed to create file '%s'")
: _("failed to open file '%s'"),
path);
} else {
/* some other failure after the open succeeded */
VIR_FORCE_CLOSE(fd);
}
return ret;
}
/* return -errno on failure, or 0 on success */
static int virDirCreateNoFork(const char *path, mode_t mode, uid_t uid, gid_t gid,
unsigned int flags) {
int ret = 0;
struct stat st;
if ((mkdir(path, mode) < 0)
&& !((errno == EEXIST) && (flags & VIR_DIR_CREATE_ALLOW_EXIST))) {
ret = -errno;
virReportSystemError(errno, _("failed to create directory '%s'"),
path);
goto error;
}
if (stat(path, &st) == -1) {
ret = -errno;
virReportSystemError(errno, _("stat of '%s' failed"), path);
goto error;
}
if (((st.st_uid != uid) || (st.st_gid != gid))
&& (chown(path, uid, gid) < 0)) {
ret = -errno;
virReportSystemError(errno, _("cannot chown '%s' to (%u, %u)"),
path, (unsigned int) uid, (unsigned int) gid);
goto error;
}
if ((flags & VIR_DIR_CREATE_FORCE_PERMS)
&& (chmod(path, mode) < 0)) {
ret = -errno;
virReportSystemError(errno,
_("cannot set mode of '%s' to %04o"),
path, mode);
goto error;
}
error:
return ret;
}
/* return -errno on failure, or 0 on success */
int virDirCreate(const char *path, mode_t mode,
uid_t uid, gid_t gid, unsigned int flags) {
struct stat st;
pid_t pid;
int waitret;
int status, ret = 0;
/* allow using -1 to mean "current value" */
if (uid == (uid_t) -1)
uid = getuid();
if (gid == (gid_t) -1)
gid = getgid();
if ((!(flags & VIR_DIR_CREATE_AS_UID))
|| (getuid() != 0)
|| ((uid == 0) && (gid == 0))
|| ((flags & VIR_DIR_CREATE_ALLOW_EXIST) && (stat(path, &st) >= 0))) {
return virDirCreateNoFork(path, mode, uid, gid, flags);
}
int forkRet = virFork(&pid);
if (pid < 0) {
ret = -errno;
return ret;
}
if (pid) { /* parent */
/* wait for child to complete, and retrieve its exit code */
while ((waitret = waitpid(pid, &status, 0) == -1) && (errno == EINTR));
if (waitret == -1) {
ret = -errno;
virReportSystemError(errno,
_("failed to wait for child creating '%s'"),
path);
goto parenterror;
}
if (!WIFEXITED(status) || (ret = -WEXITSTATUS(status)) == -EACCES) {
/* fall back to the simpler method, which works better in
* some cases */
return virDirCreateNoFork(path, mode, uid, gid, flags);
}
parenterror:
return ret;
}
/* child */
if (forkRet < 0) {
/* error encountered and logged in virFork() after the fork. */
goto childerror;
}
/* set desired uid/gid, then attempt to create the directory */
if (virSetUIDGID(uid, gid) < 0) {
ret = -errno;
goto childerror;
}
if (mkdir(path, mode) < 0) {
ret = -errno;
if (ret != -EACCES) {
/* in case of EACCES, the parent will retry */
virReportSystemError(errno, _("child failed to create directory '%s'"),
path);
}
goto childerror;
}
/* check if group was set properly by creating after
* setgid. If not, try doing it with chown */
if (stat(path, &st) == -1) {
ret = -errno;
virReportSystemError(errno,
_("stat of '%s' failed"), path);
goto childerror;
}
if ((st.st_gid != gid) && (chown(path, (uid_t) -1, gid) < 0)) {
ret = -errno;
virReportSystemError(errno,
_("cannot chown '%s' to group %u"),
path, (unsigned int) gid);
goto childerror;
}
if ((flags & VIR_DIR_CREATE_FORCE_PERMS)
&& chmod(path, mode) < 0) {
virReportSystemError(errno,
_("cannot set mode of '%s' to %04o"),
path, mode);
goto childerror;
}
childerror:
_exit(ret);
}
#else /* WIN32 */
int
virFileAccessibleAs(const char *path,
int mode,
uid_t uid ATTRIBUTE_UNUSED,
gid_t gid ATTRIBUTE_UNUSED)
{
VIR_WARN("Ignoring uid/gid due to WIN32");
return access(path, mode);
}
/* return -errno on failure, or 0 on success */
int virFileOpenAs(const char *path ATTRIBUTE_UNUSED,
int openflags ATTRIBUTE_UNUSED,
mode_t mode ATTRIBUTE_UNUSED,
uid_t uid ATTRIBUTE_UNUSED,
gid_t gid ATTRIBUTE_UNUSED,
unsigned int flags_unused ATTRIBUTE_UNUSED)
{
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("virFileOpenAs is not implemented for WIN32"));
return -ENOSYS;
}
int virDirCreate(const char *path ATTRIBUTE_UNUSED,
mode_t mode ATTRIBUTE_UNUSED,
uid_t uid ATTRIBUTE_UNUSED,
gid_t gid ATTRIBUTE_UNUSED,
unsigned int flags_unused ATTRIBUTE_UNUSED)
{
virReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("virDirCreate is not implemented for WIN32"));
return -ENOSYS;
}
#endif /* WIN32 */
static int virFileMakePathHelper(char *path, mode_t mode)
{
struct stat st;
char *p;
VIR_DEBUG("path=%s mode=0%o", path, mode);
if (stat(path, &st) >= 0) {
if (S_ISDIR(st.st_mode))
return 0;
errno = ENOTDIR;
return -1;
}
if (errno != ENOENT)
return -1;
if ((p = strrchr(path, '/')) == NULL) {
errno = EINVAL;
return -1;
}
if (p != path) {
*p = '\0';
if (virFileMakePathHelper(path, mode) < 0)
return -1;
*p = '/';
}
if (mkdir(path, mode) < 0 && errno != EEXIST)
return -1;
return 0;
}
/**
* Creates the given directory with mode 0777 if it's not already existing.
*
* Returns 0 on success, or -1 if an error occurred (in which case, errno
* is set appropriately).
*/
int virFileMakePath(const char *path)
{
return virFileMakePathWithMode(path, 0777);
}
int
virFileMakePathWithMode(const char *path,
mode_t mode)
{
int ret = -1;
char *tmp;
if ((tmp = strdup(path)) == NULL)
goto cleanup;
ret = virFileMakePathHelper(tmp, mode);
cleanup:
VIR_FREE(tmp);
return ret;
}
/* Build up a fully qualified path for a config file to be
* associated with a persistent guest or network */
char *
virFileBuildPath(const char *dir, const char *name, const char *ext)
{
char *path;
if (ext == NULL) {
if (virAsprintf(&path, "%s/%s", dir, name) < 0) {
virReportOOMError();
return NULL;
}
} else {
if (virAsprintf(&path, "%s/%s%s", dir, name, ext) < 0) {
virReportOOMError();
return NULL;
}
}
return path;
}
/* Open a non-blocking master side of a pty. If ttyName is not NULL,
* then populate it with the name of the slave. If rawmode is set,
* also put the master side into raw mode before returning. */
#ifndef WIN32
int virFileOpenTty(int *ttymaster,
char **ttyName,
int rawmode)
{
/* XXX A word of caution - on some platforms (Solaris and HP-UX),
* additional ioctl() calls are needs after opening the slave
* before it will cause isatty() to return true. Should we make
* virFileOpenTty also return the opened slave fd, so the caller
* doesn't have to worry about that mess? */
int ret = -1;
int slave = -1;
char *name = NULL;
/* Unfortunately, we can't use the name argument of openpty, since
* there is no guarantee on how large the buffer has to be.
* Likewise, we can't use the termios argument: we have to use
* read-modify-write since there is no portable way to initialize
* a struct termios without use of tcgetattr. */
if (openpty(ttymaster, &slave, NULL, NULL, NULL) < 0)
return -1;
/* What a shame that openpty cannot atomically set FD_CLOEXEC, but
* that using posix_openpt/grantpt/unlockpt/ptsname is not
* thread-safe, and that ptsname_r is not portable. */
if (virSetNonBlock(*ttymaster) < 0 ||
virSetCloseExec(*ttymaster) < 0)
goto cleanup;
/* While Linux supports tcgetattr on either the master or the
* slave, Solaris requires it to be on the slave. */
if (rawmode) {
struct termios ttyAttr;
if (tcgetattr(slave, &ttyAttr) < 0)
goto cleanup;
cfmakeraw(&ttyAttr);
if (tcsetattr(slave, TCSADRAIN, &ttyAttr) < 0)
goto cleanup;
}
/* ttyname_r on the slave is required by POSIX, while ptsname_r on
* the master is a glibc extension, and the POSIX ptsname is not
* thread-safe. Since openpty gave us both descriptors, guess
* which way we will determine the name? :) */
if (ttyName) {
/* Initial guess of 64 is generally sufficient; rely on ERANGE
* to tell us if we need to grow. */
size_t len = 64;
int rc;
if (VIR_ALLOC_N(name, len) < 0)
goto cleanup;
while ((rc = ttyname_r(slave, name, len)) == ERANGE) {
if (VIR_RESIZE_N(name, len, len, len) < 0)
goto cleanup;
}
if (rc != 0) {
errno = rc;
goto cleanup;
}
*ttyName = name;
name = NULL;
}
ret = 0;
cleanup:
if (ret != 0)
VIR_FORCE_CLOSE(*ttymaster);
VIR_FORCE_CLOSE(slave);
VIR_FREE(name);
return ret;
}
#else /* WIN32 */
int virFileOpenTty(int *ttymaster ATTRIBUTE_UNUSED,
char **ttyName ATTRIBUTE_UNUSED,
int rawmode ATTRIBUTE_UNUSED)
{
/* mingw completely lacks pseudo-terminals, and the gnulib
* replacements are not (yet) license compatible. */
errno = ENOSYS;
return -1;
}
#endif /* WIN32 */
bool virFileIsAbsPath(const char *path)
{
if (!path)
return false;
if (VIR_FILE_IS_DIR_SEPARATOR(path[0]))
return true;
#ifdef WIN32
if (c_isalpha(path[0]) &&
path[1] == ':' &&
VIR_FILE_IS_DIR_SEPARATOR(path[2]))
return true;
#endif
return false;
}
const char *virFileSkipRoot(const char *path)
{
#ifdef WIN32
/* Skip \\server\share or //server/share */
if (VIR_FILE_IS_DIR_SEPARATOR(path[0]) &&
VIR_FILE_IS_DIR_SEPARATOR(path[1]) &&
path[2] &&
!VIR_FILE_IS_DIR_SEPARATOR(path[2]))
{
const char *p = strchr(path + 2, VIR_FILE_DIR_SEPARATOR);
const char *q = strchr(path + 2, '/');
if (p == NULL || (q != NULL && q < p))
p = q;
if (p && p > path + 2 && p[1]) {
path = p + 1;
while (path[0] &&
!VIR_FILE_IS_DIR_SEPARATOR(path[0]))
path++;
/* Possibly skip a backslash after the share name */
if (VIR_FILE_IS_DIR_SEPARATOR(path[0]))
path++;
return path;
}
}
#endif
/* Skip initial slashes */
if (VIR_FILE_IS_DIR_SEPARATOR(path[0])) {
while (VIR_FILE_IS_DIR_SEPARATOR(path[0]))
path++;
return path;
}
#ifdef WIN32
/* Skip X:\ */
if (c_isalpha(path[0]) &&
path[1] == ':' &&
VIR_FILE_IS_DIR_SEPARATOR(path[2]))
return path + 3;
#endif
return path;
}
/*
* Creates an absolute path for a potentially relative path.
* Return 0 if the path was not relative, or on success.
* Return -1 on error.
*
* You must free the result.
*/
int virFileAbsPath(const char *path, char **abspath)
{
char *buf;
if (path[0] == '/') {
if (!(*abspath = strdup(path)))
return -1;
} else {
buf = getcwd(NULL, 0);
if (buf == NULL)
return -1;
if (virAsprintf(abspath, "%s/%s", buf, path) < 0) {
VIR_FREE(buf);
return -1;
}
VIR_FREE(buf);
}
return 0;
}
/* Remove spurious / characters from a path. The result must be freed */
char *
virFileSanitizePath(const char *path)
{
const char *cur = path;
char *cleanpath;
int idx = 0;
cleanpath = strdup(path);
if (!cleanpath) {
virReportOOMError();
return NULL;
}
/* Need to sanitize:
* // -> //
* /// -> /
* /../foo -> /../foo
* /foo///bar/ -> /foo/bar
*/
/* Starting with // is valid posix, but ///foo == /foo */
if (cur[0] == '/' && cur[1] == '/' && cur[2] != '/') {
idx = 2;
cur += 2;
}
/* Sanitize path in place */
while (*cur != '\0') {
if (*cur != '/') {
cleanpath[idx++] = *cur++;
continue;
}
/* Skip all extra / */
while (*++cur == '/')
continue;
/* Don't add a trailing / */
if (idx != 0 && *cur == '\0')
break;
cleanpath[idx++] = '/';
}
cleanpath[idx] = '\0';
return cleanpath;
}
/* 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.
*/
result = strdup(hostname);
goto check_and_return;
}
/* 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));
result = strdup(hostname);
goto check_and_return;
}
/* 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
*/
result = strdup(hostname);
else
/* Caller frees this string. */
result = strdup(info->ai_canonname);
freeaddrinfo(info);
check_and_return:
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;
}
if (field == VIR_USER_ENT_DIRECTORY)
ret = strdup(pw->pw_dir);
else
ret = strdup(pw->pw_name);
VIR_FREE(strbuf);
if (!ret)
virReportOOMError();
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;
}
ret = strdup(gr->gr_name);
VIR_FREE(strbuf);
if (!ret)
virReportOOMError();
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)) {
if (!(*path = strdup(buf))) {
virReportOOMError();
ret = -1;
}
}
CoTaskMemFree(pidl);
}
return ret;
}
static int
virGetWin32DirectoryRoot(char **path)
{
char windowsdir[MAX_PATH];
int ret = 0;
*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:\\");
}
if (!(*path = strdup(windowsdir))) {
virReportOOMError();
ret = -1;
}
return ret;
}
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 (dir) {
if (!(ret = strdup(dir))) {
virReportOOMError();
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 HAVE_MNTENT_H && defined HAVE_GETMNTENT_R
/* search /proc/mounts for mount point of *type; return pointer to
* malloc'ed string of the path if found, otherwise return NULL
* with errno set to an appropriate value.
*/
char *virFileFindMountPoint(const char *type)
{
FILE *f;
struct mntent mb;
char mntbuf[1024];
char *ret = NULL;
f = setmntent("/proc/mounts", "r");
if (!f)
return NULL;
while (getmntent_r(f, &mb, mntbuf, sizeof(mntbuf))) {
if (STREQ(mb.mnt_type, type)) {
ret = strdup(mb.mnt_dir);
goto cleanup;
}
}
if (!ret)
errno = ENOENT;
cleanup:
endmntent(f);
return ret;
}
#else /* defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R */
char *
virFileFindMountPoint(const char *type ATTRIBUTE_UNUSED)
{
errno = ENOSYS;
return NULL;
}
#endif /* defined HAVE_MNTENT_H && defined HAVE_GETMNTENT_R */
#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
int virBuildPathInternal(char **path, ...)
{
char *path_component = NULL;
virBuffer buf = VIR_BUFFER_INITIALIZER;
va_list ap;
int ret = 0;
va_start(ap, path);
path_component = va_arg(ap, char *);
virBufferAdd(&buf, path_component, -1);
while ((path_component = va_arg(ap, char *)) != NULL)
{
virBufferAddChar(&buf, '/');
virBufferAdd(&buf, path_component, -1);
}
va_end(ap);
*path = virBufferContentAndReset(&buf);
if (*path == NULL) {
ret = -1;
}
return ret;
}
#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 (!(*result = strndup(p, sizeof(buf)))) {
virReportOOMError();
goto cleanup;
}
ret = 0;
cleanup:
VIR_FREE(sysfs_path);
VIR_FREE(buf);
return ret;
}
int
virIsCapableFCHost(const char *sysfs_prefix,
int host)
{
char *sysfs_path = NULL;
int ret = -1;
if (virAsprintf(&sysfs_path, "%shost%d",
sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH,
host) < 0) {
virReportOOMError();
return -1;
}
if (access(sysfs_path, F_OK) == 0)
ret = 0;
VIR_FREE(sysfs_path);
return ret;
}
int
virIsCapableVport(const char *sysfs_prefix,
int host)
{
char *scsi_host_path = NULL;
char *fc_host_path = NULL;
int ret = -1;
if (virAsprintf(&fc_host_path,
"%shost%d%s",
sysfs_prefix ? sysfs_prefix : SYSFS_FC_HOST_PATH,
host,
"vport_create") < 0) {
virReportOOMError();
return -1;
}
if (virAsprintf(&scsi_host_path,
"%shost%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 = 0;
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,
"%shost%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,
"%shost%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 SCSI host name (e.g. scsi_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;
}
ret = strdup(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,.
*/
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(NULL, host))
continue;
if (virReadFCHost(NULL, 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(NULL, host, "max_npiv_vports", &max_vports) < 0) {
VIR_DEBUG("Failed to read max_npiv_vports for host%d", host);
continue;
}
if (virReadFCHost(NULL, 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)) {
ret = strdup(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;
}
int
virIsCapableFCHost(const char *sysfs_prefix ATTRIBUTE_UNUSED,
int host ATTRIBUTE_UNUSED)
{
virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
return -1;
}
int
virIsCapableVport(const char *sysfs_prefix ATTRIBUTE_UNUSED,
int host ATTRIBUTE_UNUSED)
{
virReportSystemError(ENOSYS, "%s", _("Not supported on this platform"));
return -1;
}
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;
}