qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: "Denis V. Lunev" <den@openvz.org>
To: Michael Roth <mdroth@linux.vnet.ibm.com>
Cc: Olga Krishtal <okrishtal@virtuozzo.com>, qemu-devel@nongnu.org
Subject: Re: [Qemu-devel] [PATCH 06/10] qga: guest exec functionality for Windows guests
Date: Thu, 9 Jul 2015 01:47:51 +0300	[thread overview]
Message-ID: <559DA897.2030301@openvz.org> (raw)
In-Reply-To: <20150708220219.23206.20088@loki>

[-- Attachment #1: Type: text/plain, Size: 5051 bytes --]

On 09/07/15 01:02, Michael Roth wrote:
> Quoting Denis V. Lunev (2015-07-07 03:06:08)
>> On 07/07/15 04:31, Michael Roth wrote:
>>> Quoting Denis V. Lunev (2015-06-30 05:25:19)
>>>> From: Olga Krishtal <okrishtal@virtuozzo.com>
>>>>
>>>> Child process' stdin/stdout/stderr can be associated
>>>> with handles for communication via read/write interfaces.
>>>>
>>>> The workflow should be something like this:
>>>> * Open an anonymous pipe through guest-pipe-open
>>>> * Execute a binary or a script in the guest. Arbitrary arguments and
>>>>     environment to a new child process could be passed through options
>>>> * Read/pass information from/to executed process using
>>>>     guest-file-read/write
>>>> * Collect the status of a child process
>>> Have you seen anything like this in your testing?
>>>
>>> {'execute':'guest-exec','arguments':{'path':'/Windows/System32/ipconfig.exe',
>>>    'timeout':5000}}
>>> {"return": {"pid": 588}}
>>> {'execute':'guest-exec-status','arguments':{'pid':588}}
>>> {"return": {"exit": 0, "handle-stdout": -1, "handle-stderr": -1,
>>>    "handle-stdin": -1, "signal": -1}}
>>> {'execute':'guest-exec-status','arguments':{'pid':588}}
>>> {"error": {"class": "GenericError", "desc": "Invalid parameter 'pid'"}}
>>>
>>> {'execute':'guest-exec','arguments':{'path':'/Windows/System32/ipconfig.exe',
>>>    'timeout':5000}}
>>> {"error": {"class": "GenericError", "desc": "CreateProcessW() failed:
>>>    The parameter is incorrect. (error: 57)"}}
>>> {'execute':'guest-exec','arguments':{'path':'/Windows/System32/ipconfig.exe',
>>>    'timeout':5000}}
>>> {"error": {"class": "GenericError", "desc": "CreateProcessW() failed:
>>>    The parameter is incorrect. (error: 57)"}}
>>>
>>> {'execute':'guest-exec','arguments':{'path':'/Windows/System32/ipconfig.exe',
>>>    'timeout':5000}}
>>> {"return": {"pid": 1836}}
>> I'll check this later during office time. Something definitely went wrong.
>>
>>> The guest-exec-status failures are expected since the first call reaps
>>> everything, but the CreateProcessW() failures are not. Will look into it
>>> more this evening, but it doesn't look like I'll be able to apply this in
>>> it's current state.
>>>
>>> I have concerns over the schema as well. I think last time we discussed
>>> it we both seemed to agree that guest-file-open was unwieldy and
>>> unnecessary. We should just let guest-exec return a set of file handles
>>> instead of having users do all the plumbing.
>> no, the discussion was a bit different AFAIR. First of all, you have
>> proposed
>> to use unified code to perform exec. On the other hand current mechanics
>> with pipes is quite inconvenient for end-users of the feature for example
>> for interactive shell in the guest.
>>
>> We have used very simple approach for our application: pipes are not
>> used, the application creates VirtIO serial channel and forces guest through
>> this API to fork/exec the child using this serial as a stdio in/out. In this
>> case we do receive a convenient API for shell processing.
>>
>> This means that this flexibility with direct specification of the file
>> descriptors is necessary.
> But if I'm understanding things correctly, you're simply *not* using the
> guest-pipe-* interfaces in this case, and it's just a matter of having
> the option of not overriding the child's stdio? We wouldn't
> necessarilly lose that flexibility if we dropped guest-pipe-*,
> specifying whether we want to wire qemu-ga into stdin/stdout could
> still be done via options to guest-exec.
>
> Do you have an example of the sort of invocation you're doing?
>
>> There are two solutions from my point of view:
>> - keep current API, it is suitable for us
>> - switch to "pipe only" mechanics for guest exec, i.e. the command
>>      will work like "ssh" with one descriptor for read and one for write
>>      created automatically, but in this case we do need either a way
>>      to connect Unix socket in host with file descriptor in guest or
>>      make possibility to send events from QGA to client using QMP
>>
>>> I'm really sorry for chiming in right before hard freeze, very poor
>>> timing/planning on my part.
>> :( can we somehow schedule this better next time? This functionality
>> is mandatory for us and we can not afford to drop it or forget about
>> it for long. There was no pressure in winter but now I am on a hard
>> pressure. Thus can we at least agree on API terms and come to an
>> agreement?
> Yes, absolutely. Let's get the API down early and hopefully we can
> get it all merged early this time.
>
I have attached entire C program, which allows to obtain interactive (test)
shell in guest.

actually we perform
"{\"execute\": \"guest-file-open\", \"arguments\":{\"path\":\"%s\", 
\"mode\":\"r+b\"}}"
and after that
                 "{\"execute\":\"guest-exec\", 
\"arguments\":{\"path\":\"/bin/sh\","
"\"handle_stdin\":%u,\"handle_stdout\":%u,\"handle_stderr\":%u }}",
                 ctx.guest_io_handle, ctx.guest_io_handle, 
ctx.guest_io_handle);
with the handle obtained above.

Den

[-- Attachment #2: qemu-sh.c --]
[-- Type: text/x-csrc, Size: 7114 bytes --]

/* A simple shell on top of qemu-ga. */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>


static struct {
	const char *host_ga_socket;
	const char *host_io_socket;
	const char *guest_io_socket;
} conf;

static struct {
	int gafd;
	int iofd;
	int guest_io_handle;
	int guest_sh_pid;
} ctx;


static void ctx_cleanup(void);


static void usage(void)
{
	printf("Usage: qemu-sh HOST_GA_SOCKET HOST_IO_SOCKET GUEST_IO_SOCKET\n");
}

static void dbg(const char *msg)
{
	printf("qemu-sh: debug: %s\n", msg);
}

static void warn(const char *msg)
{
	fprintf(stderr, "qemu-sh: warning: %s\n", msg);
}

static void error(const char *msg)
{
	fprintf(stderr, "qemu-sh: error: %s\n", msg);
	ctx_cleanup();
	exit(1);
}

static void prepare_ga(const char *host_ga_socket)
{
	struct sockaddr_un adr;

	adr.sun_family = AF_UNIX;
	if (strlen(host_ga_socket) >= sizeof(adr.sun_path)) {
		error("too large path to unix socket");
	}
	strcpy(adr.sun_path, host_ga_socket);

	ctx.gafd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (ctx.gafd == -1) {
		error("socket() GA socket on host");
	}

	dbg("connecting to GA...");
	if (connect(ctx.gafd, (struct sockaddr *)&adr, sizeof(struct sockaddr_un)) != 0) {
		error("connect() to GA");
	}
	dbg("connected to GA");
}

static void prepare_io(const char *host_io_socket)
{
	struct sockaddr_un adr;

	adr.sun_family = AF_UNIX;
	if (strlen(host_io_socket) >= sizeof(adr.sun_path)) {
		error("too large path to unix socket");
	}
	strcpy(adr.sun_path, host_io_socket);

	ctx.iofd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (ctx.iofd == -1) {
		error("socket() IO socket on host");
	}

	dbg("connecting to IO socket...");
	if (connect(ctx.iofd, (struct sockaddr *)&adr, sizeof(struct sockaddr_un)) != 0) {
		error("connect() to GA");
	}
	dbg("connected to IO socket");

	{
	int n = 1;
	ioctl(ctx.iofd, FIONBIO, &n);
	}
}

static void prepare_guest_io(const char *guest_io_socket)
{
	char *cmd;
	int cmdlen, handle;
	char buf[4096];
	size_t nbuf = 0, r;

	cmdlen = asprintf(&cmd,
		"{\"execute\": \"guest-file-open\", \"arguments\":{\"path\":\"%s\", \"mode\":\"r+b\"}}",
		guest_io_socket);

	if (cmdlen == -1) {
		error("asprintf()");
	}

	dbg("writing to GA...");
	if (write(ctx.gafd, cmd, cmdlen) != cmdlen) {
		error("write() to GA");
	}
	free(cmd);

	/*{"return": 1000}*/

	for (;;) {

		dbg("reading from GA...");
		r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1);
		if (r == -1 || r == 0) {
			error("read() from GA");
		}

		nbuf += r;
		buf[nbuf] = '\0';

		if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) {
			error(buf);
		}

		if (sscanf(buf, "{\"return\": %u}", &handle) != EOF) {
			break;
		}

		if (nbuf == sizeof(buf) - 1) {
			error("too large response from GA");
		}
	}

	ctx.guest_io_handle = handle;
	dbg("got guest_io_handle");
}

static void close_guest_io(void)
{
	char *cmd;
	int cmdlen, handle;
	char buf[4096];
	size_t nbuf = 0, r;

	cmdlen = asprintf(&cmd,
		"{\"execute\": \"guest-file-close\", \"arguments\":{\"handle\":%u}}",
		ctx.guest_io_handle);

	if (cmdlen == -1) {
		warn("asprintf()");
		return;
	}

	dbg("writing to GA...");
	if (write(ctx.gafd, cmd, cmdlen) != cmdlen) {
		warn("write() to GA");
		return;
	}
	free(cmd);

	for (;;) {

		dbg("reading from GA...");
		r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1);
		if (r == -1 || r == 0) {
			warn("read() from GA");
			return;
		}

		nbuf += r;
		buf[nbuf] = '\0';

		if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) {
			warn(buf);
			return;
		}

		break;
	}

	ctx.guest_io_handle = -1;
	dbg("closed guest_io_handle");
}

static void close_guest_sh(void)
{
	char *cmd;
	int cmdlen, handle;
	char buf[4096];
	size_t nbuf = 0, r;

	cmdlen = asprintf(&cmd,
		"{\"execute\": \"guest-exec-status\", \"arguments\":{\"pid\":%u}}",
		ctx.guest_sh_pid);

	if (cmdlen == -1) {
		warn("asprintf()");
		return;
	}

	dbg("writing to GA...");
	if (write(ctx.gafd, cmd, cmdlen) != cmdlen) {
		warn("write() to GA");
		return;
	}
	free(cmd);

	for (;;) {

		dbg("reading from GA...");
		r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1);
		if (r == -1 || r == 0) {
			warn("read() from GA");
			return;
		}

		nbuf += r;
		buf[nbuf] = '\0';

		if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) {
			warn(buf);
			return;
		}

		break;
	}

	ctx.guest_sh_pid = 0;
	dbg("closed guest_sh_pid");
}

static void exec_shell()
{
	char *cmd;
	int cmdlen, pid;
	char buf[4096];
	size_t nbuf = 0, r;

	cmdlen = asprintf(&cmd,
		"{\"execute\":\"guest-exec\", \"arguments\":{\"path\":\"/bin/sh\","
		"\"handle_stdin\":%u,\"handle_stdout\":%u,\"handle_stderr\":%u }}",
		ctx.guest_io_handle, ctx.guest_io_handle, ctx.guest_io_handle);

	if (cmdlen == -1) {
		error("asprintf()");
	}

	dbg("writing to GA...");
	if (write(ctx.gafd, cmd, cmdlen) != cmdlen) {
		error("write() to GA");
	}
	free(cmd);

	/*{"return": 2395}*/

	for (;;) {

		dbg("reading from GA...");
		r = read(ctx.gafd, buf + nbuf, sizeof(buf) - nbuf - 1);
		if (r == -1 || r == 0) {
			error("read() from GA");
		}

		nbuf += r;
		buf[nbuf] = '\0';

		if (!strncmp(buf, "{\"error", sizeof("{\"error") - 1)) {
			error(buf);
		}

		if (sscanf(buf, "{\"return\": %u}", &pid) != EOF) {
			break;
		}

		if (nbuf == sizeof(buf) - 1) {
			error("too large response from GA");
		}
	}

	ctx.guest_sh_pid = pid;
	dbg("got guest_sh_pid");
}

static void intr_handler(int signo)
{
	dbg("signal caught");
}

static void ctx_cleanup(void)
{
	if (ctx.guest_io_handle != 0) {
		close_guest_io();
	}

	if (ctx.guest_sh_pid != 0) {
		close_guest_sh();
	}

	if (ctx.iofd != -1) {
		close(ctx.iofd);
		ctx.iofd = -1;
	}

	if (ctx.gafd != -1) {
		close(ctx.gafd);
		ctx.gafd = -1;
	}
}

static void ev_loop(void)
{
	char buf[4096];
	size_t r;
	fd_set rset;

	for (;;) {

		FD_ZERO(&rset);

		FD_SET(STDIN_FILENO, &rset);
		FD_SET(ctx.iofd, &rset);

		r = select(ctx.iofd + 1, &rset, NULL, NULL, NULL);
		if (r == -1) {

			if (errno == EINTR)
				break;

			error("select()");
		}

		if (FD_ISSET(ctx.iofd, &rset)) {
			r = read(ctx.iofd, buf, sizeof(buf));

			if (r == -1 && (errno == EAGAIN || errno == EWOULDBLOCK))
				continue;

			if (r == -1)
				error("read() from socket");

			write(STDOUT_FILENO, buf, r);
		}

		if (FD_ISSET(STDIN_FILENO, &rset)) {
			r = read(STDIN_FILENO, buf, sizeof(buf));
			if (r == -1)
				error("read() from stdin");

			write(ctx.iofd, buf, r);
		}
	}
}

int main(int argc, char **argv, char **envp)
{
	if (argc != 4) {
		usage();
		return 0;
	}

	ctx.gafd = -1;
	ctx.iofd = -1;
	ctx.guest_io_handle = -1;

	conf.host_ga_socket = argv[1];
	conf.host_io_socket = argv[2];
	conf.guest_io_socket = argv[3];

	{
	struct sigaction sa = {0};
	sa.sa_handler = &intr_handler;
	if (sigaction(SIGINT, &sa, NULL) != 0) {
		error("sigaction()");
	}
	}

	prepare_ga(conf.host_ga_socket);
	prepare_io(conf.host_io_socket);
	prepare_guest_io(conf.guest_io_socket);
	exec_shell();

	dbg("Shell's started.  Press Ctrl+C to exit.");

	ev_loop();

	ctx_cleanup();
	return 0;
}

  reply	other threads:[~2015-07-08 22:48 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-06-30 10:25 [Qemu-devel] [PATCH v6 0/10] QGA: disk and volume info for Windows & guest exec Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 01/10] util, qga: drop guest_file_toggle_flags Denis V. Lunev
2015-07-07  8:19   ` Denis V. Lunev
2015-07-08 21:26     ` Michael Roth
2015-07-08 21:16   ` Michael Roth
2015-07-08 22:40     ` Denis V. Lunev
2015-07-09  0:09       ` Michael Roth
2015-06-30 10:25 ` [Qemu-devel] [PATCH 02/10] qga: implement guest-pipe-open command Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 03/10] qga: guest exec functionality for Unix guests Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 04/10] qga: handle possible SIGPIPE in guest-file-write Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 05/10] qga: guest-pipe-open for Windows guest Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 06/10] qga: guest exec functionality for Windows guests Denis V. Lunev
2015-07-07  1:31   ` Michael Roth
2015-07-07  8:06     ` Denis V. Lunev
2015-07-07  9:12       ` Olga Krishtal
2015-07-07  9:59         ` Denis V. Lunev
2015-07-07 10:07         ` Olga Krishtal
2015-07-08 22:02       ` Michael Roth
2015-07-08 22:47         ` Denis V. Lunev [this message]
2015-07-09  1:30           ` Michael Roth
2015-07-10 13:23             ` Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 07/10] qga: added empty qmp_quest_get_fsinfo functionality Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 08/10] qga: added mountpoint and filesystem type for single volume Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 09/10] qga: added bus type and disk location path Denis V. Lunev
2015-06-30 10:25 ` [Qemu-devel] [PATCH 10/10] qga: added GuestPCIAddress information Denis V. Lunev
  -- strict thread matches above, loose matches on Subject: below --
2015-06-19 16:57 [Qemu-devel] [PATCH v5 0/10] QGA: disk and volume info for Windows & guest exec Denis V. Lunev
2015-06-19 16:57 ` [Qemu-devel] [PATCH 06/10] qga: guest exec functionality for Windows guests Denis V. Lunev
2015-06-19 16:51 [Qemu-devel] [PATCH v4 0/10] QGA: disk and volume info for Windows & guest exec Denis V. Lunev
2015-06-19 16:51 ` [Qemu-devel] [PATCH 06/10] qga: guest exec functionality for Windows guests Denis V. Lunev

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=559DA897.2030301@openvz.org \
    --to=den@openvz.org \
    --cc=mdroth@linux.vnet.ibm.com \
    --cc=okrishtal@virtuozzo.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).