[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

[Libvir] [PATCH] Remote 3/8: Client-side



Richard W.M. Jones wrote:
3 Client-side
-------------

A src/remote_internal.c
A src/remote_internal.h
M src/driver.h
M src/libvirt.c

This is the code which serialises requests on the client side. First up is a small patch against driver.h and libvirt.c which just declares and registers the new driver.

Secondly, the driver itself (remote_internal.h / remote_internal.c) which I will describe in more detail.

The header file is small and I think non-controversial. It just declares the remoteRegister function and a handful of constants like paths and port numbers.

The driver itself (remote_internal.c) consists of the following parts, arranged here in the same order that they appear in the file:

(1) remoteOpen and associated, GnuTLS initialisation

This function and associated functions deals with parsing the remote URI, deciding which transport to use, deciding the URI to forward to the remote end, creating the transport (including initialising GnuTLS, forking ssh, ...), and verifying the server's certificate.

(2) The driver functions, remoteClose, remoteType, etc. etc.
(3) The network driver functions, remoteNetworkOpen, etc. etc.

For each function we serialise the parameters as an XDR remote_foo_args object, use the generic "call" function to dispatch the call, then deserialise the XDR remote_foo_ret return value. Most of the heavy lifting for the RPC is done in the "call" function itself ...

(4) The RPC implementation (function "call"), error handling, serialisation of virDomainPtr and virNetworkPtr.

... which also deals with making error handling transparent.

There are also some functions at the bottom to handle serialising and deserialising virDomainPtr and virNetworkPtr. We do this by essentially translating them into (name, UUID) pairs.

(5) Table of functions and registration.

And finally comes the table of functions and declaration of remoteRegister.

If you're having difficulty following XDR code, it may help to read this first: http://et.redhat.com/~rjones/xdr_tests/

Rich.

--
Emerging Technologies, Red Hat  http://et.redhat.com/~rjones/
64 Baker Street, London, W1U 7DF     Mobile: +44 7866 314 421

Registered Address: Red Hat UK Ltd, Amberley Place, 107-111 Peascod
Street, Windsor, Berkshire, SL4 1TE, United Kingdom.
Registered in England and Wales under Company Registration No. 3798903
Directors: Michael Cunningham (USA), Charlie Peters (USA) and David
Owens (Ireland)
Index: src/driver.h
===================================================================
RCS file: /data/cvs/libvirt/src/driver.h,v
retrieving revision 1.26
diff -u -r1.26 driver.h
--- src/driver.h	30 Apr 2007 17:30:11 -0000	1.26
+++ src/driver.h	5 May 2007 10:57:05 -0000
@@ -20,6 +20,7 @@
     VIR_DRV_XEN_UNIFIED = 1,
     VIR_DRV_TEST = 2,
     VIR_DRV_QEMU = 3,
+    VIR_DRV_REMOTE = 4,
 } virDrvNo;
 
 
Index: src/libvirt.c
===================================================================
RCS file: /data/cvs/libvirt/src/libvirt.c,v
retrieving revision 1.71
diff -u -r1.71 libvirt.c
--- src/libvirt.c	2 May 2007 17:40:00 -0000	1.71
+++ src/libvirt.c	5 May 2007 10:57:07 -0000
@@ -28,6 +28,7 @@
 #include "test.h"
 #include "xen_unified.h"
 #include "qemu_internal.h"
+#include "remote_internal.h"
 
 /*
  * TODO:
@@ -64,6 +65,9 @@
      * Note that the order is important: the first ones have a higher
      * priority when calling virConnectOpen.
      */
+#ifdef WITH_REMOTE
+    if (remoteRegister () == -1) return -1;
+#endif
 #ifdef WITH_TEST
     if (testRegister() == -1) return -1;
 #endif
@@ -378,6 +382,9 @@
  * Get the name of the Hypervisor software used.
  *
  * Returns NULL in case of error, a static zero terminated string otherwise.
+ *
+ * See also:
+ * http://www.redhat.com/archives/libvir-list/2007-February/msg00096.html
  */
 const char *
 virConnectGetType(virConnectPtr conn)
/*
 * remote_internal.c: driver to provide access to libvirtd running
 *   on a remote machine
 *
 * Copyright (C) 2007 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: Richard Jones <rjones redhat com>
 */

#define _GNU_SOURCE /* for asprintf */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#include <rpc/xdr.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <libxml/uri.h>

#include "internal.h"
#include "driver.h"
#include "remote_internal.h"
#include "remote_protocol.h"

#define DEBUG 0                 /* Enable verbose messages on stderr. */

/* Per-connection private data. */
#define MAGIC 999               /* private_data->magic if OK */
#define DEAD 998                /* private_data->magic if dead/closed */

struct private_data {
    int magic;                  /* Should be MAGIC or DEAD. */
    int sock;                   /* Socket. */
    int uses_tls;               /* TLS enabled on socket? */
    gnutls_session_t session;   /* GnuTLS session (if uses_tls != 0). */
    char *type;                 /* Cached return from remoteType. */
};

#define GET_PRIVATE(conn,retcode)                                       \
    struct private_data *priv = (struct private_data *) (conn)->privateData; \
    assert (priv);                                                      \
    if (priv->magic == DEAD) {                                          \
        error (conn, VIR_ERR_INVALID_ARG,                               \
               "tried to use a closed or uninitialised handle");        \
        return (retcode);                                               \
    }                                                                   \
    assert (priv->magic == MAGIC)

static int call (virConnectPtr conn, struct private_data *priv, int in_open, int proc_nr, xdrproc_t args_filter, char *args, xdrproc_t ret_filter, char *ret);
static void error (virConnectPtr conn, virErrorNumber code, const char *info);
static void server_error (virConnectPtr conn, remote_error *err);
static virDomainPtr get_nonnull_domain (virConnectPtr conn, remote_nonnull_domain domain);
static virNetworkPtr get_nonnull_network (virConnectPtr conn, remote_nonnull_network network);
static void make_nonnull_domain (remote_nonnull_domain *dom_dst, virDomainPtr dom_src);
static void make_nonnull_network (remote_nonnull_network *net_dst, virNetworkPtr net_src);

/*----------------------------------------------------------------------*/

/* Helper functions for remoteOpen. */
static char *get_transport_from_scheme (char *scheme);

/* Parse query string. */
struct query_fields {
    struct query_fields *next;	/* Linked list chain. */
    char *name;			/* Field name (unescaped). */
    char *value;		/* Field value (unescaped). */
    int ignore;			/* Ignore field in query_create. */
};

static int query_parse (const char *query,
                        const char *separator,
                        struct query_fields **fields_out);
static int query_create (const struct query_fields *fields,
                         const char *separator,
                         char **query_out);
static void query_free (struct query_fields *fields);

/* GnuTLS functions used by remoteOpen. */
static int initialise_gnutls (virConnectPtr conn);
static gnutls_session_t negotiate_gnutls_on_connection (virConnectPtr conn, int sock, int no_verify, const char *hostname);

/* Supported transports. */
enum transport {
    trans_tls,
    trans_unix,
    trans_ssh,
    trans_ext,
    trans_tcp,
};

static int
remoteOpen (virConnectPtr conn, const char *uri_str, int flags)
{
    if (!uri_str) return VIR_DRV_OPEN_DECLINED;

    /* We have to parse the URL every time to discover whether
     * it contains a transport or remote server name.  There's no
     * way to get around this.
     */
    xmlURIPtr uri = xmlParseURI (uri_str);
    if (!uri || !uri->scheme)
        return VIR_DRV_OPEN_DECLINED; /* Decline - not a URL. */

    char *transport_str = get_transport_from_scheme (uri->scheme);
    if (!uri->server && !transport_str)
        return VIR_DRV_OPEN_DECLINED; /* Decline - not a remote URL. */

    /* What transport? */
    enum transport transport;
    if (!transport_str || strcasecmp (transport_str, "tls") == 0)
        transport = trans_tls;
    else if (strcasecmp (transport_str, "unix") == 0)
        transport = trans_unix;
    else if (strcasecmp (transport_str, "ssh") == 0)
        transport = trans_ssh;
    else if (strcasecmp (transport_str, "ext") == 0)
        transport = trans_ext;
    else if (strcasecmp (transport_str, "tcp") == 0)
        transport = trans_tcp;
    else {
        error (NULL, VIR_ERR_INVALID_ARG,
               "remote_open: transport in URL not recognised "
               "(should be tls|unix|ssh|ext|tcp)");
        return VIR_DRV_OPEN_ERROR;
    }

    /* Return code from this function, and the private data. */
    int retcode = VIR_DRV_OPEN_ERROR;
    struct private_data priv = { .magic = DEAD, .sock = -1 };
    char *name = 0, *command = 0, *sockname = 0, *netcat = 0, *username = 0;
    char *server, *port;
    int no_verify = 0;
    char **cmd_argv = 0;

    /* Remote server defaults to "localhost" if not specified. */
    server = strdup (uri->server ? uri->server : "localhost");
    if (!server) {
    out_of_memory:
        error (NULL, VIR_ERR_NO_MEMORY, "remote_open");
        goto failed;
    }
    if (uri->port != 0) {
        if (asprintf (&port, "%d", uri->port) == -1) goto out_of_memory;
    } else if (transport == trans_tls) {
        port = strdup (LIBVIRTD_TLS_PORT);
        if (!port) goto out_of_memory;
    } else if (transport == trans_tcp) {
        port = strdup (LIBVIRTD_TCP_PORT);
        if (!port) goto out_of_memory;
    } else if (transport == trans_ssh) {
        port = strdup ("22");
        if (!port) goto out_of_memory;
        if (uri->user) {
            username = strdup (uri->user);
            if (!username) goto out_of_memory;
        }
    } else
        port = NULL;           /* Port not used for unix, ext. */

    /* Get the variables from the query string.
     * Then we need to reconstruct the query string (because
     * feasibly it might contain variables needed by the real driver,
     * although that won't be the case for now).
     */
    struct query_fields *vars, *var;
    char *query;
#if HAVE_XMLURI_QUERY_RAW
    query = uri->query_raw;
#else
    query = uri->query;
#endif
    if (query_parse (query, NULL, &vars) != 0) goto failed;

    for (var = vars; var; var = var->next) {
        if (strcasecmp (var->name, "name") == 0) {
            name = strdup (var->value);
            if (!name) goto out_of_memory;
            var->ignore = 1;
        } else if (strcasecmp (var->name, "command") == 0) {
            command = strdup (var->value);
            if (!command) goto out_of_memory;
            var->ignore = 1;
        } else if (strcasecmp (var->name, "socket") == 0) {
            sockname = strdup (var->value);
            if (!sockname) goto out_of_memory;
            var->ignore = 1;
        } else if (strcasecmp (var->name, "netcat") == 0) {
            netcat = strdup (var->value);
            if (!netcat) goto out_of_memory;
            var->ignore = 1;
        } else if (strcasecmp (var->name, "no_verify") == 0) {
            no_verify = atoi (var->value);
            var->ignore = 1;
        }
#if DEBUG
        else
            fprintf (stderr,
                     "remoteOpen: "
                     "passing through variable '%s' to remote end\n",
                     var->name);
#endif
    }

#if HAVE_XMLURI_QUERY_RAW
    if (uri->query_raw) xmlFree (uri->query_raw);
#else
    if (uri->query) xmlFree (uri->query);
#endif

    if (query_create (vars, NULL,
#if HAVE_XMLURI_QUERY_RAW
                      &uri->query_raw
#else
                      &uri->query
#endif
                      ) != 0) goto failed;
    query_free (vars);

    /* For ext transport, command is required. */
    if (transport == trans_ext && !command) {
        error (NULL, VIR_ERR_INVALID_ARG, "remote_open: for 'ext' transport, command is required");
        goto failed;
    }

    /* Construct the original name. */
    if (!name) {
        /* Remove the transport (if any) from the scheme. */
        if (transport_str) {
            assert (transport_str[-1] == '+');
            transport_str[-1] = '\0';
        }
        /* Remove the username, server name and port number. */
        if (uri->user) xmlFree (uri->user);
        uri->user = 0;

        if (uri->server) xmlFree (uri->server);
        uri->server = 0;

        uri->port = 0;

        name = (char *) xmlSaveUri (uri);
    }

    assert (name);
#if DEBUG
    fprintf (stderr, "remoteOpen: proceeding with name = %s\n", name);
#endif

    /* Connect to the remote service. */
    switch (transport) {
    case trans_tls:
        if (initialise_gnutls (conn) == -1) goto failed;
        priv.uses_tls = 1;

        /*FALLTHROUGH*/
    case trans_tcp: {
        // http://people.redhat.com/drepper/userapi-ipv6.html
        struct addrinfo *res, *r;
        struct addrinfo hints;
        memset (&hints, 0, sizeof hints);
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_ADDRCONFIG;
        int e = getaddrinfo (server, port, &hints, &res);
        if (e != 0) {
            error (NULL, VIR_ERR_INVALID_ARG, gai_strerror (e));
            goto failed;
        }

        /* Try to connect to each returned address in turn. */
        /* XXX This loop contains a subtle problem.  In the case
         * where a host is accessible over IPv4 and IPv6, it will
         * try the IPv4 and IPv6 addresses in turn.  However it
         * should be able to present different client certificates
         * (because the commonName field in a client cert contains
         * the client IP address, which is different for IPv4 and
         * IPv6).  At the moment we only have a single client
         * certificate, and no way to specify what address family
         * that certificate belongs to.
         */
        for (r = res; r; r = r->ai_next) {
            priv.sock = socket (r->ai_family, SOCK_STREAM, 0);
            if (priv.sock == -1) {
                error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
                continue;
            }
            if (connect (priv.sock, r->ai_addr, r->ai_addrlen) == -1) {
                error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
                close (priv.sock);
                continue;
            }
            if (priv.uses_tls) {
                priv.session =
                    negotiate_gnutls_on_connection
                      (conn, priv.sock, no_verify, server);
                if (!priv.session) {
                    close (priv.sock);
                    priv.sock = -1;
                    continue;
                }
                goto tcp_connected;
            }
        }

        freeaddrinfo (res);
        goto failed;

       tcp_connected:
        freeaddrinfo (res);

        // NB. All versioning is done by the RPC headers, so we don't
        // need to worry (at this point anyway) about versioning.
        break;
    }

    case trans_unix: {
        if (!sockname) {
            if (flags & VIR_CONNECT_RO)
                sockname = strdup (LIBVIRTD_UNIX_SOCKET_RO);
            else
                sockname = strdup (LIBVIRTD_UNIX_SOCKET);
        }

#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX(addr) (sizeof (addr).sun_path)
#endif
        struct sockaddr_un addr;
        memset (&addr, 0, sizeof addr);
        addr.sun_family = AF_UNIX;
        strncpy (addr.sun_path, sockname, UNIX_PATH_MAX (addr));

        priv.sock = socket (AF_UNIX, SOCK_STREAM, 0);
        if (priv.sock == -1) {
            error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
            goto failed;
        }
        if (connect (priv.sock, (struct sockaddr *) &addr, sizeof addr) == -1){
            error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
            goto failed;
        }

        break;
    }

    case trans_ssh: {
        int j, nr_args = username ? 10 : 8;

        command = command ? : strdup ("ssh");

        // Generate the final command argv[] array.
        //   ssh -p $port [-l $username] $hostname $netcat -U $sockname [NULL]
        cmd_argv = malloc (nr_args * sizeof (char *));
        j = 0;
        cmd_argv[j++] = strdup (command);
        cmd_argv[j++] = strdup ("-p");
        cmd_argv[j++] = strdup (port);
        if (username) {
            cmd_argv[j++] = strdup ("-l");
            cmd_argv[j++] = strdup (username);
        }
        cmd_argv[j++] = strdup (server);
        cmd_argv[j++] = strdup (netcat ? netcat : "nc");
        cmd_argv[j++] = strdup ("-U");
        cmd_argv[j++] = strdup (sockname ? sockname : LIBVIRTD_UNIX_SOCKET);
        cmd_argv[j++] = 0;
        assert (j == nr_args);
    }

        /*FALLTHROUGH*/
    case trans_ext: {
        int pid;
        int sv[2];

        /* Fork off the external process.  Use socketpair to create a private
         * (unnamed) Unix domain socket to the child process so we don't have
         * to faff around with two file descriptors (a la 'pipe(2)').
         */
        if (socketpair (PF_UNIX, SOCK_STREAM, 0, sv) == -1) {
            error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
            goto failed;
        }

        pid = fork ();
        if (pid == -1) {
            error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
            goto failed;
        } else if (pid == 0) { /* Child. */
            close (sv[0]);
            // Connect socket (sv[1]) to stdin/stdout.
            close (0);
            if (dup (sv[1]) == -1) perror ("dup");
            close (1);
            if (dup (sv[1]) == -1) perror ("dup");
            close (sv[1]);

            // Run the external process.
            if (!cmd_argv) {
                cmd_argv = malloc (2 * sizeof (char *));
                cmd_argv[0] = command;
                cmd_argv[1] = 0;
            }
            execvp (command, cmd_argv);
            perror (command);
            _exit (1);
        }

        /* Parent continues here. */
        close (sv[1]);
        priv.sock = sv[0];
    }
    } /* switch (transport) */

    /* Finally we can call the remote side's open function. */
    remote_open_args args = { &name, flags };

    if (call (conn, &priv, 1, REMOTE_PROC_OPEN,
              (xdrproc_t) xdr_remote_open_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        goto failed;

    /* Finally allocate private data. */
    conn->privateData = malloc (sizeof priv);
    if (!conn->privateData) {
        error (NULL, VIR_ERR_NO_MEMORY, "malloc");
        goto failed;
    }
    priv.magic = MAGIC;
    memcpy (conn->privateData, &priv, sizeof priv);

    /* Successful. */
    retcode = VIR_DRV_OPEN_SUCCESS;

    /*FALLTHROUGH*/
 failed:
    /* Close the socket if we failed. */
    if (retcode != VIR_DRV_OPEN_SUCCESS && priv.sock >= 0) {
        if (priv.uses_tls) gnutls_bye (priv.session, GNUTLS_SHUT_RDWR);
        close (priv.sock);
    }

    /* Free up the URL and strings. */
    xmlFreeURI (uri);
    if (name) free (name);
    if (command) free (command);
    if (sockname) free (sockname);
    if (netcat) free (netcat);
    if (username) free (username);
    if (server) free (server);
    if (port) free (port);
    if (cmd_argv) {
        char **cmd_argv_ptr = cmd_argv;
        while (*cmd_argv_ptr) {
            free (*cmd_argv_ptr);
            cmd_argv_ptr++;
        }
        free (cmd_argv);
    }

    return retcode;
}

/* In a string "driver+transport" return a pointer to "transport". */
static char *
get_transport_from_scheme (char *scheme)
{
    char *p = strchr (scheme, '+');
    return p ? p+1 : 0;
}

static int
query_create (const struct query_fields *fields,
              const char *separator,
              char **query_out)
{
    /* List of characters which are safe inside names or values,
     * apart from '@', IS_MARK and IS_ALPHANUM.  Best to escape
     * as much as possible.  Certainly '=', '&' and '#' must NEVER
     * be added to this list.
     */
    static const xmlChar *special_chars = BAD_CAST "";

    int append_sep = 0, sep_len;
    xmlBufferPtr buf;
    xmlChar *str;
    int rv;

    if (query_out) *query_out = NULL;
    if (!fields) return 0;

    if (separator == NULL) {
	separator = "&";
	sep_len = 1;
    } else
	sep_len = xmlStrlen (BAD_CAST separator);

    buf = xmlBufferCreate ();
    if (!buf) return -1;

    rv = 0;
    while (fields) {
	if (!fields->ignore) {
	    if (append_sep) {
		rv = xmlBufferAdd (buf, BAD_CAST separator, sep_len);
		if (rv != 0) goto error;
	    }
	    append_sep = 1;

	    str = xmlURIEscapeStr (BAD_CAST fields->name, special_chars);
	    if (!str) { rv = XML_ERR_NO_MEMORY; goto error; }
	    rv = xmlBufferAdd (buf, str, xmlStrlen (str));
	    xmlFree (str);
	    if (rv != 0) goto error;

	    rv = xmlBufferAdd (buf, BAD_CAST "=", 1);
	    if (rv != 0) goto error;
	    str = xmlURIEscapeStr (BAD_CAST fields->value, special_chars);
	    if (!str) { rv = XML_ERR_NO_MEMORY; goto error; }
	    rv = xmlBufferAdd (buf, str, xmlStrlen (str));
	    xmlFree (str);
	    if (rv != 0) goto error;
	}

	fields = fields->next;
    }

    if (query_out && buf->content) {
	*query_out = (char *) xmlStrdup (buf->content);
	if (!*query_out) {
	    rv = XML_ERR_NO_MEMORY;
	    goto error;
	}
    }

 error:
    if (buf)
	xmlBufferFree (buf);
    return rv;
}

static int
query_parse (const char *query_,
             const char *separator,
             struct query_fields **fields_out)
{
    struct query_fields *fields, *field, **prev;
    int sep_len;
    const xmlChar *query = BAD_CAST query_, *end, *eq;
    char *name, *value;

    if (fields_out) *fields_out = NULL;
    if (!query || query[0] == '\0') return 0;

    if (separator == NULL) {
	separator = "&";
	sep_len = 1;
    } else
	sep_len = xmlStrlen (BAD_CAST separator);

    fields = NULL;
    prev = &fields;

    while (*query) {
	/* Find the next separator, or end of the string. */
	end = xmlStrstr (query, BAD_CAST separator);
	if (!end) end = query + xmlStrlen (query);

	/* Find the first '=' character between here and end. */
	eq = xmlStrchr (query, '=');
	if (eq && eq >= end) eq = NULL;

	/* Empty section (eg. "?&"). */
	if (end == query)
	    goto next;
	/* If there is no '=' character, then we have just "name"
	 * and consistent with CGI.pm we assume value is "".
	 */
	else if (!eq) {
	    name = xmlURIUnescapeString ((const char *) query,
					 end - query, NULL);
	    value = (char *) xmlStrdup (BAD_CAST "");
	    if (!name || !value) goto out_of_memory;
	}
	/* Or if we have "name=" here (works around annoying
	 * problem when calling xmlURIUnescapeString with len = 0).
	 */
	else if (eq+1 == end) {
	    name = xmlURIUnescapeString ((const char *) query,
					 eq - query, NULL);
	    value = (char *) xmlStrdup (BAD_CAST "");
	    if (!name || !value) goto out_of_memory;
	}
	/* If the '=' character is at the beginning then we have
	 * "=value" and consistent with CGI.pm we _ignore_ this.
	 */
	else if (query == eq)
	    goto next;
	/* Otherwise it's "name=value". */
	else {
	    name = xmlURIUnescapeString ((const char *) query,
					 eq - query, NULL);
	    value = xmlURIUnescapeString ((const char *) eq+1,
					  end - (eq+1), NULL);
	    if (!name || !value) goto out_of_memory;
	}

	/* Allocate this field and append to the list. */
	field = xmlMalloc (sizeof *field);
	if (!field) goto out_of_memory;
	field->next = NULL;
	field->name = name;
	field->value = value;
	field->ignore = 0;
	*prev = field;
	prev = &field->next;

    next:
	query = end;
	if (*query) query += sep_len; /* skip separator */
    }

    if (fields_out) *fields_out = fields;
    return 0;

 out_of_memory:
    query_free (fields);
    return XML_ERR_NO_MEMORY;
}

static void
query_free (struct query_fields *fields)
{
    struct query_fields *t;

    while (fields) {
        if (fields->name) xmlFree (fields->name);
        if (fields->value) xmlFree (fields->value);
        t = fields;
        fields = fields->next;
        xmlFree (t);
    }
}

/* GnuTLS functions used by remoteOpen. */
#define CAFILE "demoCA/cacert.pem" /* XXX */
#define KEY_FILE "127001key.pem" /* XXX */
#define CERT_FILE "127001cert.pem" /* XXX */

static gnutls_certificate_credentials_t x509_cred;

static int
initialise_gnutls (virConnectPtr conn ATTRIBUTE_UNUSED)
{
    static int initialised = 0;
    int err;

    if (initialised) return 0;

    gnutls_global_init ();

    /* X509 stuff */
    err = gnutls_certificate_allocate_credentials (&x509_cred);
    if (err) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return -1;
    }

    /* Set the trusted CA cert. */
#if DEBUG
    fprintf (stderr, "loading CA file %s\n", CAFILE);
#endif
    err =
        gnutls_certificate_set_x509_trust_file (x509_cred, CAFILE,
                                                GNUTLS_X509_FMT_PEM);
    if (err < 0) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return -1;
    }

    /* Set the client certificate and private key. */
#if DEBUG
    fprintf (stderr, "loading client cert and key from files %s and %s\n",
             CERT_FILE, KEY_FILE);
#endif
    err =
        gnutls_certificate_set_x509_key_file (x509_cred,
                                              CERT_FILE, KEY_FILE,
                                              GNUTLS_X509_FMT_PEM);
    if (err < 0) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return -1;
    }

    initialised = 1;
    return 0;
}

static int verify_certificate (virConnectPtr conn, gnutls_session_t session, const char *hostname);

static gnutls_session_t
negotiate_gnutls_on_connection (virConnectPtr conn,
                                int sock, int no_verify, const char *hostname)
{
    const int cert_type_priority[3] = {
        GNUTLS_CRT_X509,
        GNUTLS_CRT_OPENPGP,
        0
    };
    int err;
    gnutls_session_t session;

    /* Initialize TLS session 
     */
    err = gnutls_init (&session, GNUTLS_CLIENT);
    if (err) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return NULL;
    }

    /* Use default priorities */
    err = gnutls_set_default_priority (session);
    if (err) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return NULL;
    }
    err =
        gnutls_certificate_type_set_priority (session,
                                              cert_type_priority);
    if (err) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return NULL;
    }

    /* put the x509 credentials to the current session
     */
    err = gnutls_credentials_set (session, GNUTLS_CRD_CERTIFICATE, x509_cred);
    if (err) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return NULL;
    }

    gnutls_transport_set_ptr (session,
                              (gnutls_transport_ptr_t) (long) sock);

    /* Perform the TLS handshake. */
 again:
    err = gnutls_handshake (session);
    if (err < 0) {
        if (err == GNUTLS_E_AGAIN || err == GNUTLS_E_INTERRUPTED)
            goto again;
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
        return NULL;
    }

    /* Verify certificate. */
    if (verify_certificate (conn, session, hostname) == -1) {
            fprintf (stderr,
                     "remote_internal: failed to verify peer's certificate\n");
            if (!no_verify) return NULL;
        }

    /* At this point, the server is verifying _our_ certificate, IP address,
     * etc.  If we make the grade, it will send us a '\1' byte.
     */
    char buf[1];
    int len;
 again_2:
    len = gnutls_record_recv (session, buf, 1);
    if (len < 0 && len != GNUTLS_E_UNEXPECTED_PACKET_LENGTH) {
        if (len == GNUTLS_E_AGAIN || len == GNUTLS_E_INTERRUPTED)
            goto again_2;
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (len));
        return NULL;
    }
    if (len != 1 || buf[0] != '\1') {
        error (NULL, VIR_ERR_RPC,
               "server verification (of our certificate or IP address) failed\n");
        return NULL;
    }

#if 0
    /* Print session info. */
    print_info (session);
#endif

    return session;
}

static int
verify_certificate (virConnectPtr conn ATTRIBUTE_UNUSED,
                    gnutls_session_t session,
                    const char *hostname)
{
    int ret;
    unsigned int status;
    const gnutls_datum_t *certs;
    unsigned int nCerts, i;
    time_t now;

    if ((ret = gnutls_certificate_verify_peers2 (session, &status)) < 0) {
        error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (ret));
        return -1;
    }
  
    if ((now = time(NULL)) == ((time_t)-1)) {
        error (NULL, VIR_ERR_SYSTEM_ERROR, strerror (errno));
        return -1;
    }

    if (status != 0) {
        const char *reason = "Invalid certificate";

        if (status & GNUTLS_CERT_INVALID)
            reason = "The certificate is not trusted.";
        
        if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
            reason = "The certificate hasn't got a known issuer.";
    
        if (status & GNUTLS_CERT_REVOKED)
            reason = "The certificate has been revoked.";
    
        if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
            reason = "The certificate uses an insecure algorithm";
    
        error (NULL, VIR_ERR_RPC, reason);
        return -1;
    }

    if (gnutls_certificate_type_get(session) != GNUTLS_CRT_X509) {
        error (NULL, VIR_ERR_RPC, "Certificate type is not X.509");
        return -1;
    }
  
    if (!(certs = gnutls_certificate_get_peers(session, &nCerts))) {
        error (NULL, VIR_ERR_RPC, "gnutls_certificate_get_peers failed");
        return -1;
    }
  
    for (i = 0 ; i < nCerts ; i++) {
        gnutls_x509_crt_t cert;

        ret = gnutls_x509_crt_init (&cert);
        if (ret < 0) {
            error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (ret));
            return -1;
        }
        
        ret = gnutls_x509_crt_import (cert, &certs[i], GNUTLS_X509_FMT_DER);
        if (ret < 0) {
            error (NULL, VIR_ERR_GNUTLS_ERROR, gnutls_strerror (ret));
            gnutls_x509_crt_deinit (cert);
            return -1;
        }
    
        if (gnutls_x509_crt_get_expiration_time (cert) < now) {
            error (NULL, VIR_ERR_RPC, "The certificate has expired");
            gnutls_x509_crt_deinit (cert);
            return -1;
        }
    
        if (gnutls_x509_crt_get_activation_time (cert) > now) {
            error (NULL, VIR_ERR_RPC, "The certificate is not yet activated");
            gnutls_x509_crt_deinit (cert);
            return -1;
        }
    
        if (i == 0) {
            if (!gnutls_x509_crt_check_hostname (cert, hostname)) {
                __virRaiseError
                    (NULL, NULL, NULL,
                     VIR_FROM_REMOTE, VIR_ERR_RPC,
                     VIR_ERR_ERROR, hostname, NULL, NULL,
                     0, 0,
                     "Certificate's owner does not match the hostname (%s)",
                     hostname);
                gnutls_x509_crt_deinit (cert);
                return -1;
            }
        }
    }

    return 0;
}

/*----------------------------------------------------------------------*/

static int
remoteClose (virConnectPtr conn)
{
    GET_PRIVATE (conn, -1);

    if (call (conn, priv, 0, REMOTE_PROC_CLOSE,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    /* Close socket. */
    if (priv->uses_tls) gnutls_bye (priv->session, GNUTLS_SHUT_RDWR);
    close (priv->sock);

    /* See comment for remoteType. */
    if (priv->type) free (priv->type);

    return 0;
}

/* Unfortunately this function is defined to return a static string.
 * Since the remote end always answers with the same type (for a
 * single connection anyway) we cache the type in the connection's
 * private data, and free it when we close the connection.
 *
 * See also:
 * http://www.redhat.com/archives/libvir-list/2007-February/msg00096.html
 */
static const char *
remoteType (virConnectPtr conn)
{
    remote_get_type_ret ret;
    GET_PRIVATE (conn, NULL);

    /* Cached? */
    if (priv->type) return priv->type;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_GET_TYPE,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_get_type_ret, (char *) &ret) == -1)
        return NULL;

    /* Stash. */
    return priv->type = ret.type;
}

static int
remoteVersion (virConnectPtr conn, unsigned long *hvVer)
{
    remote_get_version_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_GET_VERSION,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_get_version_ret, (char *) &ret) == -1)
        return -1;

    if (hvVer) *hvVer = ret.hv_ver;
    return 0;
}

static int
remoteGetMaxVcpus (virConnectPtr conn, const char *type)
{
    remote_get_max_vcpus_args args;
    remote_get_max_vcpus_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    args.type = (char **) &type;
    if (call (conn, priv, 0, REMOTE_PROC_GET_MAX_VCPUS,
              (xdrproc_t) xdr_remote_get_max_vcpus_args, (char *) &args,
              (xdrproc_t) xdr_remote_get_max_vcpus_ret, (char *) &ret) == -1)
        return -1;

    return ret.max_vcpus;
}

static int
remoteNodeGetInfo (virConnectPtr conn, virNodeInfoPtr info)
{
    remote_node_get_info_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NODE_GET_INFO,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_node_get_info_ret, (char *) &ret) == -1)
        return -1;

    strncpy (info->model, ret.model, 32);
    info->model[31] = '\0';
    info->memory = ret.memory;
    info->cpus = ret.cpus;
    info->mhz = ret.mhz;
    info->nodes = ret.nodes;
    info->sockets = ret.sockets;
    info->cores = ret.cores;
    info->threads = ret.threads;
    return 0;
}

static char *
remoteGetCapabilities (virConnectPtr conn)
{
    remote_get_capabilities_ret ret;
    GET_PRIVATE (conn, NULL);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_GET_CAPABILITIES,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_get_capabilities_ret, (char *)&ret) == -1)
        return NULL;

    /* Caller frees this. */
    return ret.capabilities;
}

static int
remoteListDomains (virConnectPtr conn, int *ids, int maxids)
{
    int i;
    remote_list_domains_args args;
    remote_list_domains_ret ret;
    GET_PRIVATE (conn, -1);

    if (maxids > REMOTE_DOMAIN_ID_LIST_MAX) {
        error (conn, VIR_ERR_RPC, "maxids > REMOTE_DOMAIN_ID_LIST_MAX");
        return -1;
    }
    args.maxids = maxids;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_LIST_DOMAINS,
              (xdrproc_t) xdr_remote_list_domains_args, (char *) &args,
              (xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret) == -1)
        return -1;

    if (ret.ids.ids_len > maxids) {
        error (conn, VIR_ERR_RPC, "ret.ids.ids_len > maxids");
        xdr_free ((xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret);
        return -1;
    }

    for (i = 0; i < ret.ids.ids_len; ++i)
        ids[i] = ret.ids.ids_val[i];

    xdr_free ((xdrproc_t) xdr_remote_list_domains_ret, (char *) &ret);

    return ret.ids.ids_len;
}

static int
remoteNumOfDomains (virConnectPtr conn)
{
    remote_num_of_domains_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NUM_OF_DOMAINS,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_num_of_domains_ret, (char *) &ret) == -1)
        return -1;

    return ret.num;
}

static virDomainPtr
remoteDomainCreateLinux (virConnectPtr conn,
                         const char *xmlDesc,
                         unsigned int flags)
{
    virDomainPtr dom;
    remote_domain_create_linux_args args;
    remote_domain_create_linux_ret ret;
    GET_PRIVATE (conn, NULL);

    args.xml_desc = (char *) xmlDesc;
    args.flags = flags;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_DOMAIN_CREATE_LINUX,
              (xdrproc_t) xdr_remote_domain_create_linux_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_create_linux_ret, (char *) &ret) == -1)
        return NULL;

    dom = get_nonnull_domain (conn, ret.dom);
    if (dom == NULL)
        error (conn, VIR_ERR_RPC, "remoteDomainCreateLinux: domain not found");

    xdr_free ((xdrproc_t) &xdr_remote_domain_create_linux_ret, (char *) &ret);

    return dom;
}

static virDomainPtr
remoteDomainLookupByID (virConnectPtr conn, int id)
{
    virDomainPtr dom;
    remote_domain_lookup_by_id_args args;
    remote_domain_lookup_by_id_ret ret;
    GET_PRIVATE (conn, NULL);

    args.id = id;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_DOMAIN_LOOKUP_BY_ID,
              (xdrproc_t) xdr_remote_domain_lookup_by_id_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_lookup_by_id_ret, (char *) &ret) == -1)
        return NULL;

    dom = get_nonnull_domain (conn, ret.dom);
    if (dom == NULL)
        error (conn, VIR_ERR_RPC, "remoteDomainLookupByID: domain not found");

    xdr_free ((xdrproc_t) &xdr_remote_domain_lookup_by_id_ret, (char *) &ret);

    return dom;
}

static virDomainPtr
remoteDomainLookupByUUID (virConnectPtr conn, const unsigned char *uuid)
{
    virDomainPtr dom;
    remote_domain_lookup_by_uuid_args args;
    remote_domain_lookup_by_uuid_ret ret;
    GET_PRIVATE (conn, NULL);

    memcpy (args.uuid, uuid, VIR_UUID_BUFLEN);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_DOMAIN_LOOKUP_BY_UUID,
              (xdrproc_t) xdr_remote_domain_lookup_by_uuid_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_lookup_by_uuid_ret, (char *) &ret) == -1)
        return NULL;

    dom = get_nonnull_domain (conn, ret.dom);
    if (dom == NULL)
        error (conn, VIR_ERR_RPC, "remoteDomainLookupByUUID: domain not found");

    xdr_free ((xdrproc_t) &xdr_remote_domain_lookup_by_uuid_ret, (char *) &ret);
    return dom;
}

static virDomainPtr
remoteDomainLookupByName (virConnectPtr conn, const char *name)
{
    virDomainPtr dom;
    remote_domain_lookup_by_name_args args;
    remote_domain_lookup_by_name_ret ret;
    GET_PRIVATE (conn, NULL);

    args.name = (char *) name;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_DOMAIN_LOOKUP_BY_NAME,
              (xdrproc_t) xdr_remote_domain_lookup_by_name_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_lookup_by_name_ret, (char *) &ret) == -1)
        return NULL;

    dom = get_nonnull_domain (conn, ret.dom);
    if (dom == NULL)
        error (conn, VIR_ERR_RPC, "remoteDomainLookupByName: domain not found");

    xdr_free ((xdrproc_t) &xdr_remote_domain_lookup_by_name_ret, (char *) &ret);

    return dom;
}

static int
remoteDomainSuspend (virDomainPtr domain)
{
    remote_domain_suspend_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SUSPEND,
              (xdrproc_t) xdr_remote_domain_suspend_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainResume (virDomainPtr domain)
{
    remote_domain_resume_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_RESUME,
              (xdrproc_t) xdr_remote_domain_resume_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainShutdown (virDomainPtr domain)
{
    remote_domain_shutdown_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SHUTDOWN,
              (xdrproc_t) xdr_remote_domain_shutdown_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainReboot (virDomainPtr domain, unsigned int flags)
{
    remote_domain_reboot_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.flags = flags;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_REBOOT,
              (xdrproc_t) xdr_remote_domain_reboot_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainDestroy (virDomainPtr domain)
{
    remote_domain_destroy_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_DESTROY,
              (xdrproc_t) xdr_remote_domain_destroy_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static char *
remoteDomainGetOSType (virDomainPtr domain)
{
    remote_domain_get_os_type_args args;
    remote_domain_get_os_type_ret ret;
    GET_PRIVATE (domain->conn, NULL);

    make_nonnull_domain (&args.dom, domain);

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_OS_TYPE,
              (xdrproc_t) xdr_remote_domain_get_os_type_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_get_os_type_ret, (char *) &ret) == -1)
        return NULL;

    /* Caller frees. */
    return ret.type;
}

static unsigned long
remoteDomainGetMaxMemory (virDomainPtr domain)
{
    remote_domain_get_max_memory_args args;
    remote_domain_get_max_memory_ret ret;
    GET_PRIVATE (domain->conn, 0);

    make_nonnull_domain (&args.dom, domain);

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_MAX_MEMORY,
              (xdrproc_t) xdr_remote_domain_get_max_memory_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_get_max_memory_ret, (char *) &ret) == -1)
        return 0;

    return ret.memory;
}

static int
remoteDomainSetMaxMemory (virDomainPtr domain, unsigned long memory)
{
    remote_domain_set_max_memory_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.memory = memory;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SET_MAX_MEMORY,
              (xdrproc_t) xdr_remote_domain_set_max_memory_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainSetMemory (virDomainPtr domain, unsigned long memory)
{
    remote_domain_set_memory_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.memory = memory;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SET_MEMORY,
              (xdrproc_t) xdr_remote_domain_set_memory_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainGetInfo (virDomainPtr domain, virDomainInfoPtr info)
{
    remote_domain_get_info_args args;
    remote_domain_get_info_ret ret;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_INFO,
              (xdrproc_t) xdr_remote_domain_get_info_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_get_info_ret, (char *) &ret) == -1)
        return -1;

    info->state = ret.state;
    info->maxMem = ret.max_mem;
    info->memory = ret.memory;
    info->nrVirtCpu = ret.nr_virt_cpu;
    info->cpuTime = ret.cpu_time;
    return 0;
}

static int
remoteDomainSave (virDomainPtr domain, const char *to)
{
    remote_domain_save_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.to = (char *) to;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SAVE,
              (xdrproc_t) xdr_remote_domain_save_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainRestore (virConnectPtr conn, const char *from)
{
    remote_domain_restore_args args;
    GET_PRIVATE (conn, -1);

    args.from = (char *) from;

    if (call (conn, priv, 0, REMOTE_PROC_DOMAIN_RESTORE,
              (xdrproc_t) xdr_remote_domain_restore_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainCoreDump (virDomainPtr domain, const char *to, int flags)
{
    remote_domain_core_dump_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.to = (char *) to;
    args.flags = flags;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_CORE_DUMP,
              (xdrproc_t) xdr_remote_domain_core_dump_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainSetVcpus (virDomainPtr domain, unsigned int nvcpus)
{
    remote_domain_set_vcpus_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.nvcpus = nvcpus;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SET_VCPUS,
              (xdrproc_t) xdr_remote_domain_set_vcpus_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainPinVcpu (virDomainPtr domain,
                     unsigned int vcpu,
                     unsigned char *cpumap,
                     int maplen)
{
    remote_domain_pin_vcpu_args args;
    GET_PRIVATE (domain->conn, -1);

    if (maplen > REMOTE_CPUMAP_MAX) {
        error (domain->conn, VIR_ERR_RPC, "maplen > REMOTE_CPUMAP_MAX");
        return -1;
    }

    make_nonnull_domain (&args.dom, domain);
    args.vcpu = vcpu;
    args.cpumap.cpumap_len = maplen;
    args.cpumap.cpumap_val = (char *) cpumap;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_PIN_VCPU,
              (xdrproc_t) xdr_remote_domain_pin_vcpu_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainGetVcpus (virDomainPtr domain,
                      virVcpuInfoPtr info,
                      int maxinfo,
                      unsigned char *cpumaps,
                      int maplen)
{
    int i;
    remote_domain_get_vcpus_args args;
    remote_domain_get_vcpus_ret ret;
    GET_PRIVATE (domain->conn, -1);

    if (maxinfo > REMOTE_VCPUINFO_MAX) {
        error (domain->conn, VIR_ERR_RPC, "maxinfo > REMOTE_VCPUINFO_MAX");
        return -1;
    }
    if (maplen > REMOTE_CPUMAPS_MAX) {
        error (domain->conn, VIR_ERR_RPC, "maplen > REMOTE_CPUMAPS_MAX");
        return -1;
    }

    make_nonnull_domain (&args.dom, domain);
    args.maxinfo = maxinfo;
    args.maplen = maplen;

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_VCPUS,
              (xdrproc_t) xdr_remote_domain_get_vcpus_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_get_vcpus_ret, (char *) &ret) == -1)
        return -1;

    if (ret.info.info_len > maxinfo) {
        error (domain->conn, VIR_ERR_RPC, "ret.info.info_len > maxinfo");
        xdr_free ((xdrproc_t) xdr_remote_domain_get_vcpus_ret, (char *) &ret);
        return -1;
    }
    if (ret.cpumaps.cpumaps_len > maplen) {
        error (domain->conn, VIR_ERR_RPC, "ret.cpumaps.cpumaps_len > maplen");
        xdr_free ((xdrproc_t) xdr_remote_domain_get_vcpus_ret, (char *) &ret);
        return -1;
    }

    for (i = 0; i < ret.info.info_len; ++i) {
        info[i].number = ret.info.info_val[i].number;
        info[i].state = ret.info.info_val[i].state;
        info[i].cpuTime = ret.info.info_val[i].cpu_time;
        info[i].cpu = ret.info.info_val[i].cpu;
    }

    for (i = 0; i < ret.cpumaps.cpumaps_len; ++i)
        cpumaps[i] = ret.cpumaps.cpumaps_val[i];

    xdr_free ((xdrproc_t) xdr_remote_domain_get_vcpus_ret, (char *) &ret);
    return ret.info.info_len;
}

static int
remoteDomainGetMaxVcpus (virDomainPtr domain)
{
    remote_domain_get_max_vcpus_args args;
    remote_domain_get_max_vcpus_ret ret;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_GET_MAX_VCPUS,
              (xdrproc_t) xdr_remote_domain_get_max_vcpus_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_get_max_vcpus_ret, (char *) &ret) == -1)
        return -1;

    return ret.num;
}

static char *
remoteDomainDumpXML (virDomainPtr domain, int flags)
{
    remote_domain_dump_xml_args args;
    remote_domain_dump_xml_ret ret;
    GET_PRIVATE (domain->conn, NULL);

    make_nonnull_domain (&args.dom, domain);
    args.flags = flags;

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_DUMP_XML,
              (xdrproc_t) xdr_remote_domain_dump_xml_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_dump_xml_ret, (char *) &ret) == -1)
        return NULL;

    /* Caller frees. */
    return ret.xml;
}

static int
remoteListDefinedDomains (virConnectPtr conn, char **const names, int maxnames)
{
    int i;
    remote_list_defined_domains_args args;
    remote_list_defined_domains_ret ret;
    GET_PRIVATE (conn, -1);

    if (maxnames > REMOTE_DOMAIN_NAME_LIST_MAX) {
        error (conn, VIR_ERR_RPC, "maxnames > REMOTE_DOMAIN_NAME_LIST_MAX");
        return -1;
    }
    args.maxnames = maxnames;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_LIST_DEFINED_DOMAINS,
              (xdrproc_t) xdr_remote_list_defined_domains_args, (char *) &args,
              (xdrproc_t) xdr_remote_list_defined_domains_ret, (char *) &ret) == -1)
        return -1;

    if (ret.names.names_len > maxnames) {
        error (conn, VIR_ERR_RPC, "ret.names.names_len > maxnames");
        xdr_free ((xdrproc_t) xdr_remote_list_defined_domains_ret, (char *) &ret);
        return -1;
    }

    /* This call is caller-frees (although that isn't clear from
     * the documentation).  However xdr_free will free up both the
     * names and the list of pointers, so we have to strdup the
     * names here.
     */
    for (i = 0; i < ret.names.names_len; ++i)
        names[i] = strdup (ret.names.names_val[i]);

    xdr_free ((xdrproc_t) xdr_remote_list_defined_domains_ret, (char *) &ret);

    return ret.names.names_len;
}

static int
remoteNumOfDefinedDomains (virConnectPtr conn)
{
    remote_num_of_defined_domains_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NUM_OF_DEFINED_DOMAINS,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_num_of_defined_domains_ret, (char *) &ret) == -1)
        return -1;

    return ret.num;
}

static int
remoteDomainCreate (virDomainPtr domain)
{
    remote_domain_create_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_CREATE,
              (xdrproc_t) xdr_remote_domain_create_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static virDomainPtr
remoteDomainDefineXML (virConnectPtr conn, const char *xml)
{
    virDomainPtr dom;
    remote_domain_define_xml_args args;
    remote_domain_define_xml_ret ret;
    GET_PRIVATE (conn, NULL);

    args.xml = (char *) xml;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_DOMAIN_DEFINE_XML,
              (xdrproc_t) xdr_remote_domain_define_xml_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_define_xml_ret, (char *) &ret) == -1)
        return NULL;

    dom = get_nonnull_domain (conn, ret.dom);
    if (dom == NULL)
        error (conn, VIR_ERR_RPC, "remoteDomainDefineXML: domain not found");

    xdr_free ((xdrproc_t) xdr_remote_domain_define_xml_ret, (char *) &ret);

    return dom;
}

static int
remoteDomainUndefine (virDomainPtr domain)
{
    remote_domain_undefine_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_UNDEFINE,
              (xdrproc_t) xdr_remote_domain_undefine_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainAttachDevice (virDomainPtr domain, char *xml)
{
    remote_domain_attach_device_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.xml = xml;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_ATTACH_DEVICE,
              (xdrproc_t) xdr_remote_domain_attach_device_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainDetachDevice (virDomainPtr domain, char *xml)
{
    remote_domain_detach_device_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.xml = xml;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_DETACH_DEVICE,
              (xdrproc_t) xdr_remote_domain_detach_device_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteDomainGetAutostart (virDomainPtr domain, int *autostart)
{
    remote_domain_get_autostart_args args;
    remote_domain_get_autostart_ret ret;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);

    memset (&ret, 0, sizeof ret);
    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_GET_AUTOSTART,
              (xdrproc_t) xdr_remote_domain_get_autostart_args, (char *) &args,
              (xdrproc_t) xdr_remote_domain_get_autostart_ret, (char *) &ret) == -1)
        return -1;

    if (autostart) *autostart = ret.autostart;
    return 0;
}

static int
remoteDomainSetAutostart (virDomainPtr domain, int autostart)
{
    remote_domain_set_autostart_args args;
    GET_PRIVATE (domain->conn, -1);

    make_nonnull_domain (&args.dom, domain);
    args.autostart = autostart;

    if (call (domain->conn, priv, 0, REMOTE_PROC_DOMAIN_SET_AUTOSTART,
              (xdrproc_t) xdr_remote_domain_set_autostart_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

/*----------------------------------------------------------------------*/

static int
remoteNetworkOpen (virConnectPtr conn,
                   const char *uri_str ATTRIBUTE_UNUSED,
                   int flags ATTRIBUTE_UNUSED)
{
    /* If the main connection is a remote, then just catch the
     * network open too.  Nothing is forwarded because the
     * main remoteOpen call above will have already opened
     * network on the remote side.
     */
    if (conn &&
        conn->driver &&
        strcmp (conn->driver->name, "remote") == 0)
        return VIR_DRV_OPEN_SUCCESS;
    else
        return VIR_DRV_OPEN_DECLINED;
}

static int
remoteNetworkClose (virConnectPtr conn ATTRIBUTE_UNUSED)
{
    /* No need to pass this to the remote side, because
     * libvirt.c will soon call remoteClose.
     */
    return 0;
}

static int
remoteNumOfNetworks (virConnectPtr conn)
{
    remote_num_of_networks_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NUM_OF_NETWORKS,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_num_of_networks_ret, (char *) &ret) == -1)
        return -1;

    return ret.num;
}

static int
remoteListNetworks (virConnectPtr conn, char **const names, int maxnames)
{
    int i;
    remote_list_networks_args args;
    remote_list_networks_ret ret;
    GET_PRIVATE (conn, -1);

    if (maxnames > REMOTE_NETWORK_NAME_LIST_MAX) {
        error (conn, VIR_ERR_RPC, "maxnames > REMOTE_NETWORK_NAME_LIST_MAX");
        return -1;
    }
    args.maxnames = maxnames;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_LIST_NETWORKS,
              (xdrproc_t) xdr_remote_list_networks_args, (char *) &args,
              (xdrproc_t) xdr_remote_list_networks_ret, (char *) &ret) == -1)
        return -1;

    if (ret.names.names_len > maxnames) {
        error (conn, VIR_ERR_RPC, "ret.names.names_len > maxnames");
        xdr_free ((xdrproc_t) xdr_remote_list_networks_ret, (char *) &ret);
        return -1;
    }

    /* This call is caller-frees (although that isn't clear from
     * the documentation).  However xdr_free will free up both the
     * names and the list of pointers, so we have to strdup the
     * names here.
     */
    for (i = 0; i < ret.names.names_len; ++i)
        names[i] = strdup (ret.names.names_val[i]);

    xdr_free ((xdrproc_t) xdr_remote_list_networks_ret, (char *) &ret);

    return ret.names.names_len;
}

static int
remoteNumOfDefinedNetworks (virConnectPtr conn)
{
    remote_num_of_defined_networks_ret ret;
    GET_PRIVATE (conn, -1);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NUM_OF_DEFINED_NETWORKS,
              (xdrproc_t) xdr_void, (char *) NULL,
              (xdrproc_t) xdr_remote_num_of_defined_networks_ret, (char *) &ret) == -1)
        return -1;

    return ret.num;
}

static int
remoteListDefinedNetworks (virConnectPtr conn,
                           char **const names, int maxnames)
{
    int i;
    remote_list_defined_networks_args args;
    remote_list_defined_networks_ret ret;
    GET_PRIVATE (conn, -1);

    if (maxnames > REMOTE_NETWORK_NAME_LIST_MAX) {
        error (conn, VIR_ERR_RPC, "maxnames > REMOTE_NETWORK_NAME_LIST_MAX");
        return -1;
    }
    args.maxnames = maxnames;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_LIST_DEFINED_NETWORKS,
              (xdrproc_t) xdr_remote_list_defined_networks_args, (char *) &args,
              (xdrproc_t) xdr_remote_list_defined_networks_ret, (char *) &ret) == -1)
        return -1;

    if (ret.names.names_len > maxnames) {
        error (conn, VIR_ERR_RPC, "ret.names.names_len > maxnames");
        xdr_free ((xdrproc_t) xdr_remote_list_defined_networks_ret, (char *) &ret);
        return -1;
    }

    /* This call is caller-frees (although that isn't clear from
     * the documentation).  However xdr_free will free up both the
     * names and the list of pointers, so we have to strdup the
     * names here.
     */
    for (i = 0; i < ret.names.names_len; ++i)
        names[i] = strdup (ret.names.names_val[i]);

    xdr_free ((xdrproc_t) xdr_remote_list_defined_networks_ret, (char *) &ret);

    return ret.names.names_len;
}

static virNetworkPtr
remoteNetworkLookupByUUID (virConnectPtr conn,
                           const unsigned char *uuid)
{
    virNetworkPtr net;
    remote_network_lookup_by_uuid_args args;
    remote_network_lookup_by_uuid_ret ret;
    GET_PRIVATE (conn, NULL);

    memcpy (args.uuid, uuid, VIR_UUID_BUFLEN);

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NETWORK_LOOKUP_BY_UUID,
              (xdrproc_t) xdr_remote_network_lookup_by_uuid_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_lookup_by_uuid_ret, (char *) &ret) == -1)
        return NULL;

    net = get_nonnull_network (conn, ret.net);
    if (net == NULL)
        error (conn, VIR_ERR_RPC, "remoteNetworkLookupByUUID: network not found");

    xdr_free ((xdrproc_t) &xdr_remote_network_lookup_by_uuid_ret, (char *) &ret);

    return net;
}

static virNetworkPtr
remoteNetworkLookupByName (virConnectPtr conn,
                           const char *name)
{
    virNetworkPtr net;
    remote_network_lookup_by_name_args args;
    remote_network_lookup_by_name_ret ret;
    GET_PRIVATE (conn, NULL);

    args.name = (char *) name;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NETWORK_LOOKUP_BY_NAME,
              (xdrproc_t) xdr_remote_network_lookup_by_name_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_lookup_by_name_ret, (char *) &ret) == -1)
        return NULL;

    net = get_nonnull_network (conn, ret.net);
    if (net == NULL)
        error (conn, VIR_ERR_RPC, "remoteNetworkLookupByName: network not found");

    xdr_free ((xdrproc_t) &xdr_remote_network_lookup_by_name_ret, (char *) &ret);

    return net;
}

static virNetworkPtr
remoteNetworkCreateXML (virConnectPtr conn, const char *xmlDesc)
{
    virNetworkPtr net;
    remote_network_create_xml_args args;
    remote_network_create_xml_ret ret;
    GET_PRIVATE (conn, NULL);

    args.xml = (char *) xmlDesc;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NETWORK_CREATE_XML,
              (xdrproc_t) xdr_remote_network_create_xml_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_create_xml_ret, (char *) &ret) == -1)
        return NULL;

    net = get_nonnull_network (conn, ret.net);
    if (net == NULL)
        error (conn, VIR_ERR_RPC, "remoteNetworkCreateXML: network not found");

    xdr_free ((xdrproc_t) &xdr_remote_network_create_xml_ret, (char *) &ret);

    return net;
}

static virNetworkPtr
remoteNetworkDefineXML (virConnectPtr conn, const char *xml)
{
    virNetworkPtr net;
    remote_network_define_xml_args args;
    remote_network_define_xml_ret ret;
    GET_PRIVATE (conn, NULL);

    args.xml = (char *) xml;

    memset (&ret, 0, sizeof ret);
    if (call (conn, priv, 0, REMOTE_PROC_NETWORK_DEFINE_XML,
              (xdrproc_t) xdr_remote_network_define_xml_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_define_xml_ret, (char *) &ret) == -1)
        return NULL;

    net = get_nonnull_network (conn, ret.net);
    if (net == NULL)
        error (conn, VIR_ERR_RPC, "remoteNetworkDefineXML: network not found");

    xdr_free ((xdrproc_t) &xdr_remote_network_define_xml_ret, (char *) &ret);

    return net;
}

static int
remoteNetworkUndefine (virNetworkPtr network)
{
    remote_network_undefine_args args;
    GET_PRIVATE (network->conn, -1);

    make_nonnull_network (&args.net, network);

    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_UNDEFINE,
              (xdrproc_t) xdr_remote_network_undefine_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteNetworkCreate (virNetworkPtr network)
{
    remote_network_create_args args;
    GET_PRIVATE (network->conn, -1);

    make_nonnull_network (&args.net, network);

    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_CREATE,
              (xdrproc_t) xdr_remote_network_create_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static int
remoteNetworkDestroy (virNetworkPtr network)
{
    remote_network_destroy_args args;
    GET_PRIVATE (network->conn, -1);

    make_nonnull_network (&args.net, network);

    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_DESTROY,
              (xdrproc_t) xdr_remote_network_destroy_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

static char *
remoteNetworkDumpXML (virNetworkPtr network, int flags)
{
    remote_network_dump_xml_args args;
    remote_network_dump_xml_ret ret;
    GET_PRIVATE (network->conn, NULL);

    make_nonnull_network (&args.net, network);
    args.flags = flags;

    memset (&ret, 0, sizeof ret);
    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_DUMP_XML,
              (xdrproc_t) xdr_remote_network_dump_xml_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_dump_xml_ret, (char *) &ret) == -1)
        return NULL;

    /* Caller frees. */
    return ret.xml;
}

static char *
remoteNetworkGetBridgeName (virNetworkPtr network)
{
    remote_network_get_bridge_name_args args;
    remote_network_get_bridge_name_ret ret;
    GET_PRIVATE (network->conn, NULL);

    make_nonnull_network (&args.net, network);

    memset (&ret, 0, sizeof ret);
    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_GET_BRIDGE_NAME,
              (xdrproc_t) xdr_remote_network_get_bridge_name_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_get_bridge_name_ret, (char *) &ret) == -1)
        return NULL;

    /* Caller frees. */
    return ret.name;
}

static int
remoteNetworkGetAutostart (virNetworkPtr network, int *autostart)
{
    remote_network_get_autostart_args args;
    remote_network_get_autostart_ret ret;
    GET_PRIVATE (network->conn, -1);

    make_nonnull_network (&args.net, network);

    memset (&ret, 0, sizeof ret);
    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_GET_AUTOSTART,
              (xdrproc_t) xdr_remote_network_get_autostart_args, (char *) &args,
              (xdrproc_t) xdr_remote_network_get_autostart_ret, (char *) &ret) == -1)
        return -1;

    if (autostart) *autostart = ret.autostart;

    return 0;
}

static int
remoteNetworkSetAutostart (virNetworkPtr network, int autostart)
{
    remote_network_set_autostart_args args;
    GET_PRIVATE (network->conn, -1);

    make_nonnull_network (&args.net, network);
    args.autostart = autostart;

    if (call (network->conn, priv, 0, REMOTE_PROC_NETWORK_SET_AUTOSTART,
              (xdrproc_t) xdr_remote_network_set_autostart_args, (char *) &args,
              (xdrproc_t) xdr_void, (char *) NULL) == -1)
        return -1;

    return 0;
}

/*----------------------------------------------------------------------*/

static int really_write (virConnectPtr conn, struct private_data *priv,
                         int in_open, char *bytes, int len);
static int really_read (virConnectPtr conn, struct private_data *priv,
                        int in_open, char *bytes, int len);

/* This function performs a remote procedure call to procedure PROC_NR.
 *
 * NB. This does not free the args structure (not desirable, since you
 * often want this allocated on the stack or else it contains strings
 * which come from the user).  It does however free any intermediate
 * results, eg. the error structure if there is one.
 *
 * NB(2). Make sure to memset (&ret, 0, sizeof ret) before calling,
 * else Bad Things will happen in the XDR code.
 */
static int
call (virConnectPtr conn, struct private_data *priv,
      int in_open /* if we are in virConnectOpen */,
      int proc_nr,
      xdrproc_t args_filter, char *args,
      xdrproc_t ret_filter, char *ret)
{
    char buffer[REMOTE_MESSAGE_MAX];
    char buffer2[4];
    struct remote_message_header hdr;
    XDR xdr;
    int len;
    struct remote_error rerror;

    /* Get a unique serial number for this message. */
    /* XXX This is supposed to be atomic over threads, but I don't know
     * if it is.
     */
    static sig_atomic_t counter = 1;
    sig_atomic_t serial = counter++;

    hdr.prog = REMOTE_PROGRAM;
    hdr.vers = REMOTE_PROTOCOL_VERSION;
    hdr.proc = proc_nr;
    hdr.direction = REMOTE_CALL;
    hdr.serial = serial;
    hdr.status = REMOTE_OK;

    /* Serialise header followed by args. */
    xdrmem_create (&xdr, buffer, sizeof buffer, XDR_ENCODE);
    if (!xdr_remote_message_header (&xdr, &hdr)) {
        error (in_open ? NULL : conn,
               VIR_ERR_RPC, "xdr_remote_message_header");
        return -1;
    }

    if (!(*args_filter) (&xdr, args)) {
        error (in_open ? NULL : conn, VIR_ERR_RPC, "marshalling args");
        return -1;
    }

    /* Get the length stored in buffer. */
    len = xdr_getpos (&xdr);
    xdr_destroy (&xdr);

    /* Length must include the length word itself (always encoded in
     * 4 bytes as per RFC 4506).
     */
    len += 4;

    /* Encode the length word. */
    xdrmem_create (&xdr, buffer2, sizeof buffer2, XDR_ENCODE);
    if (!xdr_int (&xdr, &len)) {
        error (in_open ? NULL : conn, VIR_ERR_RPC, "xdr_int (length word)");
        return -1;
    }
    xdr_destroy (&xdr);

    /* Send length word followed by header+args. */
    if (really_write (conn, priv, in_open, buffer2, sizeof buffer2) == -1 ||
        really_write (conn, priv, in_open, buffer, len-4) == -1)
        return -1;

    /* Read and deserialise length word. */
    if (really_read (conn, priv, in_open, buffer2, sizeof buffer2) == -1)
        return -1;

    xdrmem_create (&xdr, buffer2, sizeof buffer2, XDR_DECODE);
    if (!xdr_int (&xdr, &len)) {
        error (in_open ? NULL : conn,
               VIR_ERR_RPC, "xdr_int (length word, reply)");
        return -1;
    }
    xdr_destroy (&xdr);

    /* Length includes length word - adjust to real length to read. */
    len -= 4;

    if (len < 0 || len > REMOTE_MESSAGE_MAX) {
        error (in_open ? NULL : conn,
               VIR_ERR_RPC, "packet received from server too large");
        return -1;
    }

    /* Read reply header and what follows (either a ret or an error). */
    if (really_read (conn, priv, in_open, buffer, len) == -1)
        return -1;

    /* Deserialise reply header. */
    xdrmem_create (&xdr, buffer, len, XDR_DECODE);
    if (!xdr_remote_message_header (&xdr, &hdr)) {
        error (in_open ? NULL : conn,
               VIR_ERR_RPC, "xdr_remote_message_header (reply)");
        return -1;
    }

    /* Check program, version, etc. are what we expect. */
    if (hdr.prog != REMOTE_PROGRAM) {
        __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE,
                         VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0,
                         "unknown program (received %x, expected %x)",
                         hdr.prog, REMOTE_PROGRAM);
        return -1;
    }
    if (hdr.vers != REMOTE_PROTOCOL_VERSION) {
        __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE,
                         VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0,
                         "unknown protocol version (received %x, expected %x)",
                         hdr.vers, REMOTE_PROTOCOL_VERSION);
        return -1;
    }

    /* If we extend the server to actually send asynchronous messages, then
     * we'll need to change this so that it can recognise an asynch
     * message being received at this point.
     */
    if (hdr.proc != proc_nr) {
        __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE,
                         VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0,
                         "unknown procedure (received %x, expected %x)",
                         hdr.proc, proc_nr);
        return -1;
    }
    if (hdr.direction != REMOTE_REPLY) {
        __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE,
                         VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0,
                         "unknown direction (received %x, expected %x)",
                         hdr.direction, REMOTE_REPLY);
        return -1;
    }
    if (hdr.serial != serial) {
        __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE,
                         VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0,
                         "unknown serial (received %x, expected %x)",
                         hdr.serial, serial);
        return -1;
    }

    /* Status is either REMOTE_OK (meaning that what follows is a ret
     * structure), or REMOTE_ERROR (and what follows is a remote_error
     * structure).
     */
    switch (hdr.status) {
    case REMOTE_OK:
        if (!(*ret_filter) (&xdr, ret)) {
            error (in_open ? NULL : conn, VIR_ERR_RPC, "unmarshalling ret");
            return -1;
        }
        xdr_destroy (&xdr);
        return 0;

    case REMOTE_ERROR:
        memset (&rerror, 0, sizeof rerror);
        if (!xdr_remote_error (&xdr, &rerror)) {
            error (in_open ? NULL : conn,
                   VIR_ERR_RPC, "unmarshalling remote_error");
            return -1;
        }
        xdr_destroy (&xdr);
        server_error (in_open ? NULL : conn, &rerror);
        xdr_free ((xdrproc_t) xdr_remote_error, (char *) &rerror);
        return -1;

    default:
        __virRaiseError (in_open ? NULL : conn, NULL, NULL, VIR_FROM_REMOTE,
                         VIR_ERR_RPC, VIR_ERR_ERROR, NULL, NULL, NULL, 0, 0,
                         "unknown status (received %x)",
                         hdr.status);
        xdr_destroy (&xdr);
        return -1;
    }
}

static int
really_write (virConnectPtr conn, struct private_data *priv,
              int in_open /* if we are in virConnectOpen */,
              char *bytes, int len)
{
    char *p;
    int err;

    p = bytes;
    if (priv->uses_tls) {
        do {
            err = gnutls_record_send (priv->session, p, len);
            if (err < 0) {
                if (err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN)
                    continue;
                error (in_open ? NULL : conn,
                       VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
                return -1;
            }
            len -= err;
            p += err;
        }
        while (len > 0);
    } else {
        do {
            err = write (priv->sock, p, len);
            if (err == -1) {
                if (errno == EINTR || errno == EAGAIN)
                    continue;
                error (in_open ? NULL : conn,
                       VIR_ERR_SYSTEM_ERROR, strerror (errno));
                return -1;
            }
            len -= err;
            p += err;
        }
        while (len > 0);
    }

    return 0;
}

static int
really_read (virConnectPtr conn, struct private_data *priv,
             int in_open /* if we are in virConnectOpen */,
             char *bytes, int len)
{
    char *p;
    int err;

    p = bytes;
    if (priv->uses_tls) {
        do {
            err = gnutls_record_recv (priv->session, p, len);
            if (err < 0) {
                if (err == GNUTLS_E_INTERRUPTED || err == GNUTLS_E_AGAIN)
                    continue;
                error (in_open ? NULL : conn,
                       VIR_ERR_GNUTLS_ERROR, gnutls_strerror (err));
                return -1;
            }
            if (err == 0) {
                error (in_open ? NULL : conn,
                       VIR_ERR_RPC, "socket closed unexpectedly");
                return -1;
            }
            len -= err;
            p += err;
        }
        while (len > 0);
    } else {
        do {
            err = read (priv->sock, p, len);
            if (err == -1) {
                if (errno == EINTR || errno == EAGAIN)
                    continue;
                error (in_open ? NULL : conn,
                       VIR_ERR_SYSTEM_ERROR, strerror (errno));
                return -1;
            }
            if (err == 0) {
                error (in_open ? NULL : conn,
                       VIR_ERR_RPC, "socket closed unexpectedly");
                return -1;
            }
            len -= err;
            p += err;
        }
        while (len > 0);
    }

    return 0;
}

/* For errors internal to this library. */
static void
error (virConnectPtr conn, virErrorNumber code, const char *info)
{
    const char *errmsg;

    /* XXX I don't think this is correct use of virErrorMsg. */
    errmsg = __virErrorMsg (code, info);
    __virRaiseError (conn, NULL, NULL, VIR_FROM_REMOTE,
                     code, VIR_ERR_ERROR, errmsg, info, NULL, 0, 0,
                     errmsg, info);
}

/* For errors generated on the server side and sent back to us. */
static void
server_error (virConnectPtr conn, remote_error *err)
{
    virDomainPtr dom;
    virNetworkPtr net;

    /* Get the domain and network, if set.  OK to ignore the return
     * value of get_nonnull_* since these are informational.
     */
    dom = err->dom ? get_nonnull_domain (conn, *err->dom) : NULL;
    net = err->net ? get_nonnull_network (conn, *err->net) : NULL;

    /* These strings are nullable.  OK to ignore the return value
     * of strdup since these strings are informational.
     */
    char *str1 = err->str1 ? strdup (*err->str1) : NULL;
    char *str2 = err->str2 ? strdup (*err->str2) : NULL;
    char *str3 = err->str3 ? strdup (*err->str3) : NULL;

    char *message = err->message ? strdup (*err->message) : NULL;

    __virRaiseError (conn, dom, net,
                     err->domain, err->code, err->level,
                     str1, str2, str3,
                     err->int1, err->int2,
                     message);
}

/* get_nonnull_domain and get_nonnull_network turn an on-wire
 * (name, uuid) pair into virDomainPtr or virNetworkPtr object.
 * virDomainPtr or virNetworkPtr cannot be NULL.
 *
 * NB. If these return NULL then the caller must return an error.
 */
static virDomainPtr
get_nonnull_domain (virConnectPtr conn, remote_nonnull_domain domain)
{
    virDomainPtr dom;
    dom = virGetDomain (conn, domain.name, BAD_CAST domain.uuid);
    if (dom) dom->id = domain.id;
    return dom;
}

static virNetworkPtr
get_nonnull_network (virConnectPtr conn, remote_nonnull_network network)
{
    return virGetNetwork (conn, network.name, BAD_CAST network.uuid);
}

/* Make remote_nonnull_domain and remote_nonnull_network. */
static void
make_nonnull_domain (remote_nonnull_domain *dom_dst, virDomainPtr dom_src)
{
    dom_dst->name = dom_src->name;
    memcpy (dom_dst->uuid, dom_src->uuid, VIR_UUID_BUFLEN);
}

static void
make_nonnull_network (remote_nonnull_network *net_dst, virNetworkPtr net_src)
{
    net_dst->name = net_src->name;
    memcpy (net_dst->uuid, net_src->uuid, VIR_UUID_BUFLEN);
}

/*----------------------------------------------------------------------*/

static virDriver driver = {
    .no = VIR_DRV_REMOTE,
    .name = "remote",
    .ver = REMOTE_PROTOCOL_VERSION,
    .open = remoteOpen,
    .close = remoteClose,
	.type = remoteType,
	.version = remoteVersion,
	.getMaxVcpus = remoteGetMaxVcpus,
	.nodeGetInfo = remoteNodeGetInfo,
    .getCapabilities = remoteGetCapabilities,
    .listDomains = remoteListDomains,
    .numOfDomains = remoteNumOfDomains,
    .domainCreateLinux = remoteDomainCreateLinux,
    .domainLookupByID = remoteDomainLookupByID,
    .domainLookupByUUID = remoteDomainLookupByUUID,
    .domainLookupByName = remoteDomainLookupByName,
    .domainSuspend = remoteDomainSuspend,
    .domainResume = remoteDomainResume,
    .domainShutdown = remoteDomainShutdown,
    .domainReboot = remoteDomainReboot,
    .domainDestroy = remoteDomainDestroy,
    .domainGetOSType = remoteDomainGetOSType,
    .domainGetMaxMemory = remoteDomainGetMaxMemory,
    .domainSetMaxMemory = remoteDomainSetMaxMemory,
    .domainSetMemory = remoteDomainSetMemory,
    .domainGetInfo = remoteDomainGetInfo,
    .domainSave = remoteDomainSave,
    .domainRestore = remoteDomainRestore,
    .domainCoreDump = remoteDomainCoreDump,
    .domainSetVcpus = remoteDomainSetVcpus,
    .domainPinVcpu = remoteDomainPinVcpu,
    .domainGetVcpus = remoteDomainGetVcpus,
    .domainGetMaxVcpus = remoteDomainGetMaxVcpus,
    .domainDumpXML = remoteDomainDumpXML,
    .listDefinedDomains = remoteListDefinedDomains,
    .numOfDefinedDomains = remoteNumOfDefinedDomains,
    .domainCreate = remoteDomainCreate,
    .domainDefineXML = remoteDomainDefineXML,
    .domainUndefine = remoteDomainUndefine,
    .domainAttachDevice = remoteDomainAttachDevice,
    .domainDetachDevice = remoteDomainDetachDevice,
    .domainGetAutostart = remoteDomainGetAutostart,
    .domainSetAutostart = remoteDomainSetAutostart,
};

static virNetworkDriver network_driver = {
    .open = remoteNetworkOpen,
    .close = remoteNetworkClose,
    .numOfNetworks = remoteNumOfNetworks,
    .listNetworks = remoteListNetworks,
    .numOfDefinedNetworks = remoteNumOfDefinedNetworks,
    .listDefinedNetworks = remoteListDefinedNetworks,
    .networkLookupByUUID = remoteNetworkLookupByUUID,
    .networkLookupByName = remoteNetworkLookupByName,
    .networkCreateXML = remoteNetworkCreateXML,
    .networkDefineXML = remoteNetworkDefineXML,
    .networkUndefine = remoteNetworkUndefine,
    .networkCreate = remoteNetworkCreate,
    .networkDestroy = remoteNetworkDestroy,
    .networkDumpXML = remoteNetworkDumpXML,
    .networkGetBridgeName = remoteNetworkGetBridgeName,
    .networkGetAutostart = remoteNetworkGetAutostart,
    .networkSetAutostart = remoteNetworkSetAutostart,
};

/* remoteRegister:
 *
 * Register driver with libvirt driver system.
 */
int
remoteRegister (void)
{
    if (virRegisterDriver (&driver) == -1) return -1;
    if (virRegisterNetworkDriver (&network_driver) == -1) return -1;

    return 0;
}

/*
 * vim: set tabstop=4:
 * vim: set shiftwidth=4:
 * vim: set expandtab:
 */
/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */
/*
 * remote_internal.h: driver to provide access to libvirtd running
 *   on a remote machine
 *
 * Copyright (C) 2006-2007 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: Richard Jones <rjones redhat com>
 */

#ifndef __VIR_REMOTE_INTERNAL_H__
#define __VIR_REMOTE_INTERNAL_H__

#include <libvirt/virterror.h>

#ifdef __cplusplus
extern "C" {
#endif

int remoteRegister (void);

#define LIBVIRTD_TLS_PORT "16514"
#define LIBVIRTD_TCP_PORT "16509"
#define LIBVIRTD_UNIX_SOCKET (LOCAL_STATE_DIR "/run/libvirt/libvirt-sock")
#define LIBVIRTD_UNIX_SOCKET_RO (LOCAL_STATE_DIR "/run/libvirt/libvirt-sock-ro")
#define LIBVIRTD_CONFIGURATION_FILE (SYSCONFDIR "/libvirtd.conf")

#ifdef __cplusplus
}
#endif
#endif /* __VIR_REMOTE_INTERNAL_H__ */

/*
 * vim: set tabstop=4:
 * vim: set shiftwidth=4:
 * vim: set expandtab:
 */
/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]