libvirt/tools/virsh-console.c
Peter Krempa 654486bd57 syntax-check: sc_avoid_write: Don't use blanket file exceptions
Adding an exception for the whole file usually defeats the purpose of a
syntax check and is also likely to get forgotten once the file is
removed.

In case of the suggestion of using 'safewrite' instead of write even the
comment for safewrite states that the function needs to be used only in
certain cases.

Remove the blanket exceptions for files and use an exclude string
instead. The only instance where we keep the full file exception is for
src/libvirt-stream.c as there are multiple uses in example code in
comments where I couldn't find a nicer targetted wapproach.

Signed-off-by: Peter Krempa <pkrempa@redhat.com>
Reviewed-by: Ján Tomko <jtomko@redhat.com>
2022-02-15 09:32:23 +01:00

519 lines
14 KiB
C

/*
* virsh-console.c: A dumb serial console client
*
* Copyright (C) 2007-2008, 2010-2014 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#ifndef WIN32
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <termios.h>
# include <poll.h>
# include <unistd.h>
# include <signal.h>
# include "internal.h"
# include "virsh.h"
# include "virsh-console.h"
# include "virsh-util.h"
# include "virlog.h"
# include "virfile.h"
# include "viralloc.h"
# include "virthread.h"
# include "virerror.h"
# include "virobject.h"
VIR_LOG_INIT("tools.virsh-console");
/*
* Convert given character to control character.
* Basically, we assume ASCII, and take lower 6 bits.
*/
# define CONTROL(c) ((c) ^ 0x40)
# define VIR_FROM_THIS VIR_FROM_NONE
struct virConsoleBuffer {
size_t length;
size_t offset;
char *data;
};
typedef struct virConsole virConsole;
struct virConsole {
virObjectLockable parent;
virStreamPtr st;
bool quit;
virCond cond;
int stdinWatch;
int stdoutWatch;
struct virConsoleBuffer streamToTerminal;
struct virConsoleBuffer terminalToStream;
char escapeChar;
virError error;
};
static virClass *virConsoleClass;
static void virConsoleDispose(void *obj);
static int
virConsoleOnceInit(void)
{
if (!VIR_CLASS_NEW(virConsole, virClassForObjectLockable()))
return -1;
return 0;
}
VIR_ONCE_GLOBAL_INIT(virConsole);
static void
virConsoleHandleSignal(int sig G_GNUC_UNUSED)
{
}
static void
virConsoleShutdown(virConsole *con,
bool graceful)
{
virErrorPtr err = virGetLastError();
if (con->error.code == VIR_ERR_OK && err)
virCopyLastError(&con->error);
if (con->st) {
int rc;
virStreamEventRemoveCallback(con->st);
if (graceful)
rc = virStreamFinish(con->st);
else
rc = virStreamAbort(con->st);
if (rc < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot terminate console stream"));
}
g_clear_pointer(&con->st, virshStreamFree);
}
VIR_FREE(con->streamToTerminal.data);
VIR_FREE(con->terminalToStream.data);
if (con->stdinWatch != -1)
virEventRemoveHandle(con->stdinWatch);
if (con->stdoutWatch != -1)
virEventRemoveHandle(con->stdoutWatch);
con->stdinWatch = -1;
con->stdoutWatch = -1;
if (!con->quit) {
con->quit = true;
virCondSignal(&con->cond);
}
}
static void
virConsoleDispose(void *obj)
{
virConsole *con = obj;
virshStreamFree(con->st);
virCondDestroy(&con->cond);
virResetError(&con->error);
}
static void
virConsoleEventOnStream(virStreamPtr st,
int events, void *opaque)
{
virConsole *con = opaque;
virObjectLock(con);
/* we got late event after console was shutdown */
if (!con->st)
goto cleanup;
if (events & VIR_STREAM_EVENT_READABLE) {
size_t avail = con->streamToTerminal.length -
con->streamToTerminal.offset;
int got;
if (avail < 1024) {
VIR_REALLOC_N(con->streamToTerminal.data,
con->streamToTerminal.length + 1024);
con->streamToTerminal.length += 1024;
avail += 1024;
}
got = virStreamRecv(st,
con->streamToTerminal.data +
con->streamToTerminal.offset,
avail);
if (got == -2)
goto cleanup; /* blocking */
if (got <= 0) {
virConsoleShutdown(con, got == 0);
goto cleanup;
}
con->streamToTerminal.offset += got;
if (con->streamToTerminal.offset)
virEventUpdateHandle(con->stdoutWatch,
VIR_EVENT_HANDLE_WRITABLE);
}
if (events & VIR_STREAM_EVENT_WRITABLE &&
con->terminalToStream.offset) {
ssize_t done;
size_t avail;
done = virStreamSend(con->st,
con->terminalToStream.data,
con->terminalToStream.offset);
if (done == -2)
goto cleanup; /* blocking */
if (done < 0) {
virConsoleShutdown(con, false);
goto cleanup;
}
memmove(con->terminalToStream.data,
con->terminalToStream.data + done,
con->terminalToStream.offset - done);
con->terminalToStream.offset -= done;
avail = con->terminalToStream.length - con->terminalToStream.offset;
if (avail > 1024) {
VIR_REALLOC_N(con->terminalToStream.data,
con->terminalToStream.offset + 1024);
con->terminalToStream.length = con->terminalToStream.offset + 1024;
}
}
if (!con->terminalToStream.offset)
virStreamEventUpdateCallback(con->st,
VIR_STREAM_EVENT_READABLE);
if (events & VIR_STREAM_EVENT_ERROR ||
events & VIR_STREAM_EVENT_HANGUP) {
virConsoleShutdown(con, false);
}
cleanup:
virObjectUnlock(con);
}
static void
virConsoleEventOnStdin(int watch G_GNUC_UNUSED,
int fd G_GNUC_UNUSED,
int events,
void *opaque)
{
virConsole *con = opaque;
virObjectLock(con);
/* we got late event after console was shutdown */
if (!con->st)
goto cleanup;
if (events & VIR_EVENT_HANDLE_READABLE) {
size_t avail = con->terminalToStream.length -
con->terminalToStream.offset;
int got;
if (avail < 1024) {
VIR_REALLOC_N(con->terminalToStream.data,
con->terminalToStream.length + 1024);
con->terminalToStream.length += 1024;
avail += 1024;
}
got = read(fd,
con->terminalToStream.data +
con->terminalToStream.offset,
avail);
if (got < 0) {
if (errno != EAGAIN) {
virReportSystemError(errno, "%s", _("cannot read from stdin"));
virConsoleShutdown(con, false);
}
goto cleanup;
}
if (got == 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdin"));
virConsoleShutdown(con, false);
goto cleanup;
}
if (con->terminalToStream.data[con->terminalToStream.offset] == con->escapeChar) {
virConsoleShutdown(con, true);
goto cleanup;
}
con->terminalToStream.offset += got;
if (con->terminalToStream.offset)
virStreamEventUpdateCallback(con->st,
VIR_STREAM_EVENT_READABLE |
VIR_STREAM_EVENT_WRITABLE);
}
if (events & VIR_EVENT_HANDLE_ERROR) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error on stdin"));
virConsoleShutdown(con, false);
goto cleanup;
}
if (events & VIR_EVENT_HANDLE_HANGUP) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdin"));
virConsoleShutdown(con, false);
goto cleanup;
}
cleanup:
virObjectUnlock(con);
}
static void
virConsoleEventOnStdout(int watch G_GNUC_UNUSED,
int fd,
int events,
void *opaque)
{
virConsole *con = opaque;
virObjectLock(con);
/* we got late event after console was shutdown */
if (!con->st)
goto cleanup;
if (events & VIR_EVENT_HANDLE_WRITABLE &&
con->streamToTerminal.offset) {
ssize_t done;
size_t avail;
done = write(fd, /* sc_avoid_write */
con->streamToTerminal.data,
con->streamToTerminal.offset);
if (done < 0) {
if (errno != EAGAIN) {
virReportSystemError(errno, "%s", _("cannot write to stdout"));
virConsoleShutdown(con, false);
}
goto cleanup;
}
memmove(con->streamToTerminal.data,
con->streamToTerminal.data + done,
con->streamToTerminal.offset - done);
con->streamToTerminal.offset -= done;
avail = con->streamToTerminal.length - con->streamToTerminal.offset;
if (avail > 1024) {
VIR_REALLOC_N(con->streamToTerminal.data,
con->streamToTerminal.offset + 1024);
con->streamToTerminal.length = con->streamToTerminal.offset + 1024;
}
}
if (!con->streamToTerminal.offset)
virEventUpdateHandle(con->stdoutWatch, 0);
if (events & VIR_EVENT_HANDLE_ERROR) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("IO error stdout"));
virConsoleShutdown(con, false);
goto cleanup;
}
if (events & VIR_EVENT_HANDLE_HANGUP) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("EOF on stdout"));
virConsoleShutdown(con, false);
goto cleanup;
}
cleanup:
virObjectUnlock(con);
}
static virConsole *
virConsoleNew(void)
{
virConsole *con;
if (virConsoleInitialize() < 0)
return NULL;
if (!(con = virObjectLockableNew(virConsoleClass)))
return NULL;
if (virCondInit(&con->cond) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("cannot initialize console condition"));
goto error;
}
con->stdinWatch = -1;
con->stdoutWatch = -1;
return con;
error:
virObjectUnref(con);
return NULL;
}
static char
virshGetEscapeChar(const char *s)
{
if (*s == '^')
return CONTROL(g_ascii_toupper(s[1]));
return *s;
}
int
virshRunConsole(vshControl *ctl,
virDomainPtr dom,
const char *dev_name,
unsigned int flags)
{
virConsole *con = NULL;
virshControl *priv = ctl->privData;
int ret = -1;
struct sigaction old_sigquit;
struct sigaction old_sigterm;
struct sigaction old_sigint;
struct sigaction old_sighup;
struct sigaction old_sigpipe;
struct sigaction sighandler = {.sa_handler = virConsoleHandleSignal,
.sa_flags = SA_SIGINFO };
sigemptyset(&sighandler.sa_mask);
/* Put STDIN into raw mode so that stuff typed does not echo to the screen
* (the TTY reads will result in it being echoed back already), and also
* ensure Ctrl-C, etc is blocked, and misc other bits */
if (vshTTYMakeRaw(ctl, true) < 0)
goto resettty;
if (!(con = virConsoleNew()))
goto resettty;
virObjectLock(con);
/* Trap all common signals so that we can safely restore the original
* terminal settings on STDIN before the process exits - people don't like
* being left with a messed up terminal ! */
sigaction(SIGQUIT, &sighandler, &old_sigquit);
sigaction(SIGTERM, &sighandler, &old_sigterm);
sigaction(SIGINT, &sighandler, &old_sigint);
sigaction(SIGHUP, &sighandler, &old_sighup);
sigaction(SIGPIPE, &sighandler, &old_sigpipe);
con->escapeChar = virshGetEscapeChar(priv->escapeChar);
con->st = virStreamNew(virDomainGetConnect(dom),
VIR_STREAM_NONBLOCK);
if (!con->st)
goto cleanup;
if (virDomainOpenConsole(dom, dev_name, con->st, flags) < 0)
goto cleanup;
virObjectRef(con);
if ((con->stdinWatch = virEventAddHandle(STDIN_FILENO,
VIR_EVENT_HANDLE_READABLE,
virConsoleEventOnStdin,
con,
virObjectFreeCallback)) < 0) {
virObjectUnref(con);
goto cleanup;
}
virObjectRef(con);
if ((con->stdoutWatch = virEventAddHandle(STDOUT_FILENO,
0,
virConsoleEventOnStdout,
con,
virObjectFreeCallback)) < 0) {
virObjectUnref(con);
goto cleanup;
}
virObjectRef(con);
if (virStreamEventAddCallback(con->st,
VIR_STREAM_EVENT_READABLE,
virConsoleEventOnStream,
con,
virObjectFreeCallback) < 0) {
virObjectUnref(con);
goto cleanup;
}
while (!con->quit) {
if (virCondWait(&con->cond, &con->parent.lock) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("unable to wait on console condition"));
goto cleanup;
}
}
if (con->error.code == VIR_ERR_OK)
ret = 0;
cleanup:
virConsoleShutdown(con, ret == 0);
if (ret < 0) {
vshResetLibvirtError();
virSetError(&con->error);
vshSaveLibvirtHelperError();
}
virObjectUnlock(con);
virObjectUnref(con);
/* Restore original signal handlers */
sigaction(SIGQUIT, &old_sigquit, NULL);
sigaction(SIGTERM, &old_sigterm, NULL);
sigaction(SIGINT, &old_sigint, NULL);
sigaction(SIGHUP, &old_sighup, NULL);
sigaction(SIGPIPE, &old_sigpipe, NULL);
resettty:
/* Put STDIN back into the (sane?) state we found
it in before starting */
vshTTYRestore(ctl);
return ret;
}
#endif /* !WIN32 */