From: Daniel Veillard <veillard@redhat.com>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] [PATCH] Remote console access though socket
Date: Wed, 8 Mar 2006 09:52:11 -0500 [thread overview]
Message-ID: <20060308145211.GG346@redhat.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 3793 bytes --]
Hi,
enclosed is a first version of a patch to allow remote access and control
for QEmu instances, I'm not suggesting to apply it as is (though it seems
to work in my limited testing) but would rather like to get comments back
for choices I'm facing.
First a bit of context, I'm working on libvirt (see
http://libvirt.org/) a library providing access to virtualization. I would
like to be able to handle QEmu instance like we do for Xen, the simpler
would be to be able to list and connect to the console of running QEmu.
In an early mail last month Fabrice expressed interest in exporting the
console though TCP, so I hope I'm not too much off-track.
Enclosed are:
- qemu-tcpctrl.patch: the patch to the existing code, it add tcpctrl
as a configure option (on by default except on Windows), it plugs
in main() of vl.c, add one entry point in console.c, and add a new
module in the Makefile
- tcpctrl.c: the C file, it currently use an unix socket
/tmp/qemu-$pid-socket add a routine to start and stop the access
and plug in the I/O handler of QEmu to wait for connections on it.
When connections are accepted they are also plugged in the I/O
handling, and receive commands. The commands are passed to console.c
along with a minimal CharDriverState whose grite goes back to
the associated connection.
- client.c: a minimal client to send a command and get the output.
use as './client /tmp/qemu-*-socket quit'
There is a number of open questions which would need to be resolved before
applying any such patch:
- First one is the unix socket, we could as easilly start normal port
based access but:
+ I would really like to be able to list the current running instance
without checking all process on the OS, and mapping in the file
system seems the easiest way
+ the permission over the filesystem allows to set up the access policy
without any special mechanism
+ in a port based access how do you guess the port numbers used by the
QEmu instances
the drawback of the file sockets are:
+ security: one need a directory where any user (since qemu isn't
priviledged) can create a new file, and ensure safety over those
accesses
+ cleanup: when qemu exits one need to clean up the socket, and
currently qemu has no central exit routine for this (and doesn't
seems to trap signals leading to exit)
- what about asynchronous events CharDriverState has structures in place
for those, in general is that okay to keep some of the CharDriverState
as NULL or should it trap every possible entries not just write()
- the plug of the socket in the console framework is a bit crude, it just
replace the global variable monitor_hd during the processing of the
request (c.f. monitor_process_command()).
- the console -> socket code should try to coalesce multiple writes
from the console into a single packet flushed at the end of the routine,
and a marker for the end of the output from the command will help
parsing at the client level.
- ease of error handling on the client if possible, to generalize a
bit go from a human targetted interaction to a machine one.
- command line configurations, for enabling/disabling the remote access
and being able to specify the port number if using port, left open
I didn't tried to address that
So there is a bit too many open issues but IMHO nothing really hard, I would
just like to get feedback from other interested people.
Daniel
--
Daniel Veillard | Red Hat http://redhat.com/
veillard@redhat.com | libxml GNOME XML XSLT toolkit http://xmlsoft.org/
http://veillard.com/ | Rpmfind RPM search engine http://rpmfind.net/
[-- Attachment #2: qemu-tcpctrl.patch --]
[-- Type: text/plain, Size: 5114 bytes --]
Index: qemu/Makefile.target
===================================================================
RCS file: /sources/qemu/qemu/Makefile.target,v
retrieving revision 1.93
diff -u -r1.93 Makefile.target
--- qemu/Makefile.target 6 Feb 2006 04:11:15 -0000 1.93
+++ qemu/Makefile.target 8 Mar 2006 13:19:14 -0000
@@ -275,6 +275,9 @@
ifdef CONFIG_WIN32
VL_OBJS+=tap-win32.o
endif
+ifdef CONFIG_TCPCTRL
+VL_OBJS+=tcpctrl.o
+endif
SOUND_HW = sb16.o es1370.o
AUDIODRV = audio.o noaudio.o wavaudio.o
Index: qemu/configure
===================================================================
RCS file: /sources/qemu/qemu/configure,v
retrieving revision 1.83
diff -u -r1.83 configure
--- qemu/configure 2 Mar 2006 21:52:18 -0000 1.83
+++ qemu/configure 8 Mar 2006 13:19:14 -0000
@@ -74,6 +74,7 @@
mingw32="no"
EXESUF=""
gdbstub="yes"
+tcpctrl="yes"
slirp="yes"
adlib="no"
oss="no"
@@ -208,6 +209,10 @@
;;
--disable-gcc-check) check_gcc="no"
;;
+ --enable-tcpctrl) tcpctrl="yes"
+ ;;
+ --disable-tcpctrl) tcpctrl="no"
+ ;;
esac
done
@@ -224,6 +229,7 @@
linux="no"
EXESUF=".exe"
gdbstub="no"
+ tcpctrl="no"
oss="no"
if [ "$cpu" = "i386" ] ; then
kqemu="yes"
@@ -385,6 +391,7 @@
echo " --enable-alsa enable ALSA audio driver"
echo " --enable-fmod enable FMOD audio driver"
echo " --enabled-dsound enable DirectSound audio driver"
+echo " --disable-tcpctrl disable TCP control connections"
echo " --fmod-lib path to FMOD library"
echo " --fmod-inc path to FMOD includes"
echo ""
@@ -451,6 +458,7 @@
echo -n " (lib='$fmod_lib' include='$fmod_inc')"
fi
echo ""
+echo "TCP control $tcpctrl"
echo "kqemu support $kqemu"
if test $sdl_too_old = "yes"; then
@@ -545,6 +553,10 @@
echo "CONFIG_GDBSTUB=yes" >> $config_mak
echo "#define CONFIG_GDBSTUB 1" >> $config_h
fi
+if test "$tcpctrl" = "yes" ; then
+ echo "CONFIG_TCPCTRL=yes" >> $config_mak
+ echo "#define CONFIG_TCPCTRL 1" >> $config_h
+fi
if test "$gprof" = "yes" ; then
echo "TARGET_GPROF=yes" >> $config_mak
echo "#define HAVE_GPROF 1" >> $config_h
Index: qemu/monitor.c
===================================================================
RCS file: /sources/qemu/qemu/monitor.c,v
retrieving revision 1.46
diff -u -r1.46 monitor.c
--- qemu/monitor.c 8 Feb 2006 22:40:15 -0000 1.46
+++ qemu/monitor.c 8 Mar 2006 13:19:14 -0000
@@ -296,6 +296,9 @@
static void do_quit(void)
{
+#ifdef CONFIG_TCPCTRL
+ tcpctrl_stop();
+#endif
exit(0);
}
@@ -2026,6 +2029,30 @@
return;
}
+/**
+ * monitor_process_command:
+ * @output: the console used to send the output
+ * @cmdline: the line of the command to process
+ *
+ * Process a command coming from a different requester than the usual console.
+ */
+
+void monitor_process_command(CharDriverState *output, const char *cmdline)
+{
+ CharDriverState *old_output = monitor_hd;
+
+ if ((output == NULL) || (cmdline == NULL))
+ return;
+
+ /*
+ * TODO: maybe it would be cleaner to inherit the current driver
+ * in the calls than relying on a global variable ?
+ */
+ monitor_hd = output;
+ monitor_handle_command(cmdline);
+ monitor_hd = old_output;
+}
+
static void cmd_completion(const char *name, const char *list)
{
const char *p, *pstart;
Index: qemu/vl.c
===================================================================
RCS file: /sources/qemu/qemu/vl.c,v
retrieving revision 1.165
diff -u -r1.165 vl.c
--- qemu/vl.c 20 Feb 2006 00:33:36 -0000 1.165
+++ qemu/vl.c 8 Mar 2006 13:19:15 -0000
@@ -4550,6 +4550,9 @@
int main(int argc, char **argv)
{
+#ifdef CONFIG_TCPCTRL
+ int use_tcpctrl = 1;
+#endif
#ifdef CONFIG_GDBSTUB
int use_gdbstub, gdbstub_port;
#endif
@@ -5210,6 +5213,14 @@
gui_timer = qemu_new_timer(rt_clock, gui_update, NULL);
qemu_mod_timer(gui_timer, qemu_get_clock(rt_clock));
+#ifdef CONFIG_TCPCTRL
+ if (use_tcpctrl) {
+ if (tcpctrl_start() < 0) {
+ fprintf(stderr, "Could not open TCP control socket\n");
+ exit(1);
+ }
+ }
+#endif
#ifdef CONFIG_GDBSTUB
if (use_gdbstub) {
if (gdbserver_start(gdbstub_port) < 0) {
@@ -5232,6 +5243,11 @@
}
}
main_loop();
+#ifdef CONFIG_TCPCTRL
+ if (use_tcpctrl) {
+ tcpctrl_stop();
+ }
+#endif
quit_timers();
return 0;
}
Index: qemu/vl.h
===================================================================
RCS file: /sources/qemu/qemu/vl.h,v
retrieving revision 1.105
diff -u -r1.105 vl.h
--- qemu/vl.h 20 Feb 2006 00:33:36 -0000 1.105
+++ qemu/vl.h 8 Mar 2006 13:19:15 -0000
@@ -991,6 +991,14 @@
void term_print_help(void);
void monitor_readline(const char *prompt, int is_password,
char *buf, int buf_size);
+void monitor_process_command(CharDriverState *output, const char *cmdline);
+
+#ifdef CONFIG_TCPCTRL
+/* tcpctrl.c */
+int tcpctrl_start(void);
+void tcpctrl_stop(void);
+
+#endif /* CONFIG_TCPCTRL */
/* readline.c */
typedef void ReadLineFunc(void *opaque, const char *str);
[-- Attachment #3: tcpctrl.c --]
[-- Type: text/plain, Size: 5971 bytes --]
/*
* remote TCP control connection
*
* Copyright (c) 2006 Daniel Veillard <daniel@veillard.com>
*
* 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 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
*/
#include "vl.h"
#ifdef CONFIG_TCPCTRL
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
/* #define DEBUG_TCPCTRL */
static int tcpctrl_fd = -1;
static char tcpctrl_socket[150] = "";
typedef struct _tcp_client {
int fd;
CharDriverState drv;
} tcp_client;
static int
chrctr_write(struct CharDriverState *s, const uint8_t *buf, int len) {
tcp_client *info;
int written, sent = 0;
if ((s == NULL) || (buf == NULL) || (len < 0) || (s->opaque == NULL))
return(-1);
if (tcpctrl_fd == -1)
return(-1);
info = s->opaque;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "chrctr_write(fd = %d, len = %d)\n", info->fd, len);
#endif
retry_write:
written = write(info->fd, buf + sent, len - sent);
if (written < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
goto retry_write;
close(info->fd);
qemu_set_fd_handler(info->fd, NULL, NULL, NULL);
info->fd = -1;
return(-1);
}
if (written + sent < len) {
sent += written;
goto retry_write;
}
return(written);
}
static void
tcpctrl_disconnect(tcp_client *info)
{
if ((info == NULL) || (info->fd < 0))
return;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_disconnect(fd = %d)\n", info->fd);
#endif
/*
* this seems the best way to unregister this should work
* even in the loop over iov in the main loop
*/
if (info->fd > 0) {
qemu_set_fd_handler(info->fd, NULL, NULL, NULL);
close(info->fd);
}
qemu_free(info);
}
static int tcpctrl_process(tcp_client *info, const char *command) {
if (tcpctrl_fd == -1)
return(-1);
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_process(command = '%s')\n", command);
#endif
monitor_process_command(&info->drv, command);
/* writing back to tcp connection may have failed */
if (info->fd < 0)
tcpctrl_disconnect(info);
}
static void tcpctrl_read(void *opaque)
{
char buffer[4096 + 1];
int ret;
tcp_client *info = opaque;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_read(fd = %d)\n", info->fd);
#endif
if (tcpctrl_fd == -1) {
tcpctrl_disconnect(info);
return;
}
retry_read:
ret = read(info->fd, &buffer[0], sizeof(buffer) - 1);
if (ret < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
goto retry_read;
tcpctrl_disconnect(info);
return;
}
if (ret == 0) {
tcpctrl_disconnect(info);
return;
}
buffer[ret] = 0;
tcpctrl_process(info, &buffer[0]);
}
static void
tcpctrl_accept(void *opaque)
{
struct sockaddr_in sockaddr;
socklen_t len;
int val, fd;
tcp_client *info;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_accept()\n");
#endif
for(;;) {
len = sizeof(sockaddr);
fd = accept(tcpctrl_fd, (struct sockaddr *)&sockaddr, &len);
if (fd < 0 && errno != EINTR) {
perror("accept");
return;
} else if (fd >= 0) {
break;
}
}
/* set short latency */
val = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));
info = qemu_mallocz(sizeof(tcp_client));
if (!info) {
close(fd);
return;
}
info->fd = fd;
info->drv.chr_write = chrctr_write;
info->drv.opaque = info;
fcntl(fd, F_SETFL, O_NONBLOCK);
qemu_set_fd_handler(fd, tcpctrl_read, NULL, info);
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_accept(): fd = %d\n", fd);
#endif
}
static int tcpctrl_open()
{
int fd = -1;
int pid = getpid();
mode_t oldmask;
struct sockaddr_un addr;
snprintf(tcpctrl_socket, sizeof(tcpctrl_socket) - 1,
"%s/qemu-%d-socket", "/tmp", pid);
tcpctrl_socket[sizeof(tcpctrl_socket) - 1] = 0;
unlink(tcpctrl_socket);
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
fprintf(stderr, "Failed to create unix socket");
return(-1);
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(&addr.sun_path[0], tcpctrl_socket, (sizeof(addr) - 4) - 1);
oldmask = umask(0077);
if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
fprintf(stderr, "Failed to bind to socket %s\n", tcpctrl_socket);
close(fd);
umask(oldmask);
return(-1);
}
if (listen(fd, 3) < 0) {
fprintf(stderr, "Failed to listen to socket %s\n", tcpctrl_socket);
close(fd);
umask(oldmask);
return(-1);
}
umask(oldmask);
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "Listening on TCP control socket %s\n", tcpctrl_socket);
#endif
return fd;
}
int tcpctrl_start()
{
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_start()\n");
#endif
tcpctrl_fd = tcpctrl_open();
if (tcpctrl_fd < 0)
return -1;
qemu_set_fd_handler(tcpctrl_fd, tcpctrl_accept, NULL, NULL);
return 0;
}
void tcpctrl_stop()
{
if (tcpctrl_fd < 0)
return;
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "tcpctrl_stop()\n");
#endif
unlink(tcpctrl_socket);
qemu_set_fd_handler(tcpctrl_fd, NULL, NULL, NULL);
close(tcpctrl_fd);
tcpctrl_fd = -1;
return;
}
#endif /* CONFIG_TCPCTRL */
[-- Attachment #4: client.c --]
[-- Type: text/plain, Size: 2087 bytes --]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#define DEBUG_TCPCTRL
static int
qemu_connect(const char *path) {
int fd;
struct sockaddr_un addr;
fd = socket(PF_UNIX, SOCK_STREAM, 0);
if (fd < 0) {
fprintf(stderr, "Failed to create unix socket\n");
return (-1);
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(&addr.sun_path[0], path, (sizeof(addr) - 4) - 1);
while (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
if (errno != EINTR) {
fprintf(stderr, "Failed to connect to unix socket %s\n",
path);
close(fd);
unlink(path);
return (-1);
}
}
#ifdef DEBUG_TCPCTRL
fprintf(stderr, "Connected to socket %s : %d\n", path, fd);
#endif
return(fd);
}
static int
qemu_command(int fd, const char *command) {
int ret, written, sent = 0, len;
char answer[200];
len = strlen(command);
retry_write:
written = write(fd, &command[sent], len - sent);
if (written < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
goto retry_write;
fprintf(stderr, "Failed to write message to socket %d\n", fd);
return (-1);
}
if (written + sent < len) {
sent += written;
goto retry_write;
}
retry_read:
ret = read(fd, &answer[0], sizeof(answer) - 1);
if (ret < 0) {
if ((errno == EINTR) || (errno == EAGAIN))
goto retry_read;
return (-1);
}
answer[ret] = 0;
printf("%s\n", answer);
return(0);
}
static void usage(const char *progname) {
printf("Usage %s socket_path [command]\n", progname);
exit(1);
}
int main(int argc, char **argv) {
const char *path, *command;
int fd;
int ret;
if ((argc < 2) || (argc > 3))
usage(argv[0]);
path = argv[1];
if (argc == 3)
command = argv[2];
else
command = "help";
fd = qemu_connect(path);
if (fd < 0)
exit(1);
ret = qemu_command(fd, command);
return(ret);
}
next reply other threads:[~2006-03-08 14:52 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2006-03-08 14:52 Daniel Veillard [this message]
[not found] ` <16af12af0603081035h5f61d947y6c24b7a7675eeb14@mail.gmail.com>
2006-03-08 20:35 ` [Qemu-devel] [PATCH] Remote console access though socket Ed Swierk
2006-03-11 20:53 ` Daniel Veillard
2006-03-11 16:24 ` Oliver Gerlich
2006-03-11 20:59 ` Daniel Veillard
2006-03-11 22:22 ` Oliver Gerlich
2006-03-12 11:03 ` Daniel Veillard
2006-03-11 21:07 ` Daniel Veillard
-- strict thread matches above, loose matches on Subject: below --
2006-03-09 12:47 wanderer
2006-03-09 12:55 ` Daniel Veillard
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20060308145211.GG346@redhat.com \
--to=veillard@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).