diff --git a/configure.ac b/configure.ac index 262e63b768fdc21cdf438a246aedd8a65b362cca..c9cdd7b926058a07f4876f527cc766fb95f60f0d 100644 --- a/configure.ac +++ b/configure.ac @@ -336,6 +336,12 @@ AC_ARG_WITH([remote], AC_HELP_STRING([--with-remote], [add remote driver support @<:@default=yes@:>@]),[],[with_remote=yes]) AC_ARG_WITH([libvirtd], AC_HELP_STRING([--with-libvirtd], [add libvirtd support @<:@default=yes@:>@]),[],[with_libvirtd=yes]) +AC_ARG_WITH([console-lock-files], + AC_HELP_STRING([--with-console-lock-files], + [location for UUCP style lock files for console PTYs + (use auto for default paths on some platforms) + @<:@default=auto@:>@]), + [],[with_console_lock_files=auto]) dnl dnl in case someone want to build static binaries @@ -1206,6 +1212,26 @@ AM_CONDITIONAL([HAVE_AUDIT], [test "$with_audit" = "yes"]) AC_SUBST([AUDIT_CFLAGS]) AC_SUBST([AUDIT_LIBS]) +dnl UUCP style file locks for PTY consoles +if test "$with_console_lock_files" != "no"; then + case $with_console_lock_files in + yes | auto) + dnl Default locations for platforms, or disable if unknown + if test "$with_linux" = "yes"; then + with_console_lock_files=/var/lock + elif test "$with_console_lock_files" = "auto"; then + with_console_lock_files=no + fi ;; + esac + if test "$with_console_lock_files" = "yes"; then + AC_MSG_ERROR([You must specify path for the lock files on this +platform]) + fi + AC_DEFINE_UNQUOTED([VIR_PTY_LOCK_FILE_PATH], "$with_console_lock_files", + [path to directory containing UUCP pty lock files]) +fi +AM_CONDITIONAL([VIR_PTY_LOCK_FILE_PATH], [test "$with_console_lock_files" != "no"]) + dnl SELinux AC_ARG_WITH([selinux], @@ -2742,6 +2768,7 @@ AC_MSG_NOTICE([ Python: $with_python]) AC_MSG_NOTICE([ DTrace: $with_dtrace]) AC_MSG_NOTICE([ XML Catalog: $XML_CATALOG_FILE]) AC_MSG_NOTICE([ Init script: $with_init_script]) +AC_MSG_NOTICE([Console locks: $with_console_lock_files]) AC_MSG_NOTICE([]) AC_MSG_NOTICE([Privileges]) AC_MSG_NOTICE([]) diff --git a/po/POTFILES.in b/po/POTFILES.in index 0eff13bcb1c238e0c2e14519c2568963212149b0..16a3f9e36c2c46ab380b13ddec092bb961f9ce7a 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -17,6 +17,7 @@ src/conf/nwfilter_params.c src/conf/secret_conf.c src/conf/storage_conf.c src/conf/storage_encryption_conf.c +src/conf/virconsole.c src/cpu/cpu.c src/cpu/cpu_generic.c src/cpu/cpu_map.c diff --git a/src/Makefile.am b/src/Makefile.am index 376e66517f0a563823badf02fd7c4236aa614b1b..3b29d39b797ed780d44de31e5fe26f9affc78066 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -185,6 +185,9 @@ ENCRYPTION_CONF_SOURCES = \ CPU_CONF_SOURCES = \ conf/cpu_conf.c conf/cpu_conf.h +# Safe console handling helper APIs +CONSOLE_CONF_SOURCES = \ + conf/virconsole.c conf/virconsole.h CONF_SOURCES = \ $(NETDEV_CONF_SOURCES) \ @@ -197,7 +200,8 @@ CONF_SOURCES = \ $(ENCRYPTION_CONF_SOURCES) \ $(INTERFACE_CONF_SOURCES) \ $(SECRET_CONF_SOURCES) \ - $(CPU_CONF_SOURCES) + $(CPU_CONF_SOURCES) \ + $(CONSOLE_CONF_SOURCES) # The remote RPC driver, covering domains, storage, networks, etc REMOTE_DRIVER_GENERATED = \ diff --git a/src/conf/virconsole.c b/src/conf/virconsole.c new file mode 100644 index 0000000000000000000000000000000000000000..443d80d44a840b2644c585078d16e03a789f2aba --- /dev/null +++ b/src/conf/virconsole.c @@ -0,0 +1,408 @@ +/** + * virconsole.c: api to guarantee mutually exclusive + * access to domain's consoles + * + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Peter Krempa + */ + +#include + +#include +#include +#include + +#include "virconsole.h" +#include "virhash.h" +#include "fdstream.h" +#include "internal.h" +#include "threads.h" +#include "memory.h" +#include "virpidfile.h" +#include "logging.h" +#include "virterror_internal.h" +#include "virfile.h" + +#define VIR_FROM_THIS VIR_FROM_NONE +#define virConsoleError(code, ...) \ + virReportErrorHelper(VIR_FROM_THIS, code, __FILE__, \ + __FUNCTION__, __LINE__, __VA_ARGS__) + +/* structure holding information about consoles + * open in a given domain */ +struct _virConsoles { + virMutex lock; + virHashTablePtr hash; +}; + +typedef struct _virConsoleStreamInfo virConsoleStreamInfo; +typedef virConsoleStreamInfo *virConsoleStreamInfoPtr; +struct _virConsoleStreamInfo { + virConsolesPtr cons; + const char *pty; +}; + +#ifdef VIR_PTY_LOCK_FILE_PATH +/** + * Create a full filename with path to the lock file based on + * name/path of corresponding pty + * + * @pty path of the console device + * + * Returns a modified name that the caller has to free, or NULL + * on error. + */ +static char *virConsoleLockFilePath(const char *pty) +{ + char *path = NULL; + char *sanitizedPath = NULL; + char *ptyCopy; + char *filename; + char *p; + + if (!(ptyCopy = strdup(pty))) { + virReportOOMError(); + goto cleanup; + } + + /* skip the leading "/dev/" */ + filename = STRSKIP(ptyCopy, "/dev"); + if (!filename) + filename = ptyCopy; + + /* substitute path forward slashes for underscores */ + p = filename; + while (*p) { + if (*p == '/') + *p = '_'; + ++p; + } + + if (virAsprintf(&path, "%s/LCK..%s", VIR_PTY_LOCK_FILE_PATH, filename) < 0) + goto cleanup; + + sanitizedPath = virFileSanitizePath(path); + +cleanup: + VIR_FREE(path); + VIR_FREE(ptyCopy); + + return sanitizedPath; +} + +/** + * Verify and create a lock file for a console pty + * + * @pty Path of the console device + * + * Returns 0 on success, -1 on error + */ +static int virConsoleLockFileCreate(const char *pty) +{ + char *path = NULL; + int ret = -1; + int lockfd = -1; + char *pidStr = NULL; + pid_t pid; + + /* build lock file path */ + if (!(path = virConsoleLockFilePath(pty))) + goto cleanup; + + /* check if a log file and process holding the lock still exists */ + if (virPidFileReadPathIfAlive(path, &pid, NULL) == 0 && pid >= 0) { + /* the process exists, the lockfile is valid */ + virConsoleError(VIR_ERR_OPERATION_FAILED, + _("Requested console pty '%s' is locked by " + "lock file '%s' held by process %lld"), + pty, path, (long long) pid); + goto cleanup; + } else { + /* clean up the stale/corrupted/nonexistent lockfile */ + unlink(path); + } + /* lockfile doesn't (shouldn't) exist */ + + /* ensure correct format according to filesystem hierarchy standard */ + /* http://www.pathname.com/fhs/pub/fhs-2.3.html#VARLOCKLOCKFILES */ + if (virAsprintf(&pidStr, "%10lld\n", (long long) getpid()) < 0) + goto cleanup; + + /* create the lock file */ + if ((lockfd = open(path, O_WRONLY | O_CREAT | O_EXCL, 00644)) < 0) { + /* If we run in session mode, we might have no access to the lock + * file directory. We have to check for an permission denied error + * and see if we can reach it. This should cause an error only if + * we run in daemon mode and thus privileged. + */ + if (errno == EACCES && geteuid() != 0) { + VIR_DEBUG("Skipping lock file creation for pty '%s in path '%s'.", + pty, path); + ret = 0; + goto cleanup; + } + virReportSystemError(errno, + _("Couldn't create lock file for " + "pty '%s' in path '%s'"), + pty, path); + goto cleanup; + } + + /* write the pid to the file */ + if (safewrite(lockfd, pidStr, strlen(pidStr)) < 0) { + virReportSystemError(errno, + _("Couldn't write to lock file for " + "pty '%s' in path '%s'"), + pty, path); + VIR_FORCE_CLOSE(lockfd); + unlink(path); + goto cleanup; + } + + /* we hold the lock */ + ret = 0; + +cleanup: + VIR_FORCE_CLOSE(lockfd); + VIR_FREE(path); + VIR_FREE(pidStr); + + return ret; +} + +/** + * Remove a lock file for a pty + * + * @pty Path of the pty device + */ +static void virConsoleLockFileRemove(const char *pty) +{ + char *path = virConsoleLockFilePath(pty); + if (path) + unlink(path); + VIR_FREE(path); +} +#else /* #ifdef VIR_PTY_LOCK_FILE_PATH */ +/* file locking for console devices is disabled */ +static int virConsoleLockFileCreate(const char *pty ATTRIBUTE_UNUSED) +{ + return 0; +} + +static void virConsoleLockFileRemove(const char *pty ATTRIBUTE_UNUSED) +{ + return; +} +#endif /* #ifdef VIR_PTY_LOCK_FILE_PATH */ + +/** + * Frees an entry from the hash containing domain's active consoles + * + * @data Opaque data, struct holding information about the console + * @name Path of the pty. + */ +static void virConsoleHashEntryFree(void *data, + const void *name) +{ + const char *pty = name; + virStreamPtr st = data; + + /* free stream reference */ + virStreamFree(st); + + /* delete lock file */ + virConsoleLockFileRemove(pty); +} + +/** + * Frees opaque data provided for the stream closing callback + * + * @opaque Data to be freed. + */ +static void virConsoleFDStreamCloseCbFree(void *opaque) +{ + virConsoleStreamInfoPtr priv = opaque; + + VIR_FREE(priv->pty); + VIR_FREE(priv); +} + +/** + * Callback being called if a FDstream is closed. Frees console entries + * from data structures and removes lockfiles. + * + * @st Pointer to stream being closed. + * @opaque Domain's console information structure. + */ +static void virConsoleFDStreamCloseCb(virStreamPtr st ATTRIBUTE_UNUSED, + void *opaque) +{ + virConsoleStreamInfoPtr priv = opaque; + virMutexLock(&priv->cons->lock); + + /* remove entry from hash */ + virHashRemoveEntry(priv->cons->hash, priv->pty); + + virMutexUnlock(&priv->cons->lock); +} + +/** + * Allocate structures for storing information about active console streams + * in domain's private data section. + * + * Returns pointer to the allocated structure or NULL on error + */ +virConsolesPtr virConsoleAlloc(void) +{ + virConsolesPtr cons; + if (VIR_ALLOC(cons) < 0) + return NULL; + + if (virMutexInit(&cons->lock) < 0) { + VIR_FREE(cons); + return NULL; + } + + /* there will hardly be any consoles most of the time, the hash + * does not have to be huge */ + if (!(cons->hash = virHashCreate(3, virConsoleHashEntryFree))) + goto error; + + return cons; +error: + virConsoleFree(cons); + return NULL; +} + +/** + * Free structures for handling open console streams. + * + * @cons Pointer to the private structure. + */ +void virConsoleFree(virConsolesPtr cons) +{ + if (!cons || !cons->hash) + return; + + virMutexLock(&cons->lock); + virHashFree(cons->hash); + virMutexUnlock(&cons->lock); + virMutexDestroy(&cons->lock); + + VIR_FREE(cons); +} + +/** + * Open a console stream for a domain ensuring that other streams are + * not using the console, nor any lockfiles exist. This ensures that + * the console stream does not get corrupted due to a race on reading + * same FD by two processes. + * + * @cons Pointer to private structure holding data about console streams. + * @pty Path to the pseudo tty to be opened. + * @st Stream the client wishes to use for the console connection. + * @force On true, close active console streams for the selected console pty + * before opening this connection. + * + * Returns 0 on success and st is connected to the selected pty and + * corresponding lock file is created (if configured). Returns -1 on + * error and 1 if the console stream is open and busy. + */ +int virConsoleOpen(virConsolesPtr cons, + const char *pty, + virStreamPtr st, + bool force) +{ + virConsoleStreamInfoPtr cbdata = NULL; + virStreamPtr savedStream; + int ret; + + virMutexLock(&cons->lock); + + if ((savedStream = virHashLookup(cons->hash, pty))) { + if (!force) { + /* entry found, console is busy */ + virMutexUnlock(&cons->lock); + return 1; + } else { + /* terminate existing connection */ + /* The internal close callback handler needs to lock cons->lock to + * remove the aborted stream from the hash. This would cause a + * deadlock as we would try to enter the lock twice from the very + * same thread. We need to unregister the callback and abort the + * stream manually before we create a new console connection. + */ + virFDStreamSetInternalCloseCb(savedStream, NULL, NULL, NULL); + virStreamAbort(savedStream); + virHashRemoveEntry(cons->hash, pty); + /* continue adding a new stream connection */ + } + } + + /* create the lock file */ + if ((ret = virConsoleLockFileCreate(pty)) < 0) { + virMutexUnlock(&cons->lock); + return ret; + } + + /* obtain a reference to the stream */ + if (virStreamRef(st) < 0) { + virMutexUnlock(&cons->lock); + return -1; + } + + if (VIR_ALLOC(cbdata) < 0) { + virReportOOMError(); + goto error; + } + + if (virHashAddEntry(cons->hash, pty, st) < 0) + goto error; + + cbdata->cons = cons; + if (!(cbdata->pty = strdup(pty))) { + virReportOOMError(); + goto error; + } + + /* open the console pty */ + if (virFDStreamOpenFile(st, pty, 0, 0, O_RDWR) < 0) + goto error; + + savedStream = st; + st = NULL; + + /* add cleanup callback */ + virFDStreamSetInternalCloseCb(savedStream, + virConsoleFDStreamCloseCb, + cbdata, + virConsoleFDStreamCloseCbFree); + cbdata = NULL; + + virMutexUnlock(&cons->lock); + return 0; + +error: + virStreamFree(st); + virHashRemoveEntry(cons->hash, pty); + if (cbdata) + VIR_FREE(cbdata->pty); + VIR_FREE(cbdata); + virMutexUnlock(&cons->lock); + return -1; +} diff --git a/src/conf/virconsole.h b/src/conf/virconsole.h new file mode 100644 index 0000000000000000000000000000000000000000..7cdf578c0d8addd484f7a596d26cf302f01d73e2 --- /dev/null +++ b/src/conf/virconsole.h @@ -0,0 +1,36 @@ +/** + * virconsole.h: api to guarantee mutually exclusive + * access to domain's consoles + * + * Copyright (C) 2011-2012 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Peter Krempa + */ +#ifndef __VIR_CONSOLE_H__ +# define __VIR_CONSOLE_H__ + +# include "internal.h" + +typedef struct _virConsoles virConsoles; +typedef virConsoles *virConsolesPtr; + +virConsolesPtr virConsoleAlloc(void); +void virConsoleFree(virConsolesPtr cons); + +int virConsoleOpen(virConsolesPtr cons, const char *pty, + virStreamPtr st, bool force); +#endif /*__VIR_DOMAIN_CONSOLE_LOCK_H__*/ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 310cd7d91e398dae976fb90ca1ec4161b93e9e41..3ee40dc491b0549a7c92117999c8a6bf7854dd69 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -1157,6 +1157,12 @@ virAuditOpen; virAuditSend; +# virconsole.h +virConsoleAlloc; +virConsoleFree; +virConsoleOpen; + + # virfile.h virFileClose; virFileDirectFdFlag;