/* * xend_internal.c: access to Xen though the Xen Daemon interface * * Copyright (C) 2005 * * Anthony Liguori * * This file is subject to the terms and conditions of the GNU Lesser General * Public License. See the file COPYING.LIB in the main directory of this * archive for more details. */ #ifdef WITH_XEN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "internal.h" #include "driver.h" #include "util.h" #include "sexpr.h" #include "xml.h" #include "buf.h" #include "capabilities.h" #include "uuid.h" #include "xen_unified.h" #include "xend_internal.h" #include "xen_internal.h" /* for DOM0_INTERFACE_VERSION */ #include "xs_internal.h" /* To extract VNC port & Serial console TTY */ #include "memory.h" /* required for cpumap_t */ #include #define DEBUG(fmt,...) VIR_DEBUG(__FILE__, fmt,__VA_ARGS__) #define DEBUG0(msg) VIR_DEBUG(__FILE__, "%s", msg) #ifndef PROXY /* * The number of Xen scheduler parameters */ #define XEN_SCHED_SEDF_NPARAM 6 #define XEN_SCHED_CRED_NPARAM 2 #endif /* PROXY */ /** * xend_connection_type: * * The connection to the Xen Daemon can be done either though a normal TCP * socket or a local domain direct connection. */ enum xend_connection_type { XEND_DOMAIN, XEND_TCP, }; /** * xend: * * Structure associated to a connection to a Xen daemon */ struct xend { int len; int type; struct sockaddr *addr; struct sockaddr_un addr_un; struct sockaddr_in addr_in; }; static void virXendError(virConnectPtr conn, virErrorNumber error, const char *fmt, ...) ATTRIBUTE_FORMAT(printf,3,4); #define MAX_ERROR_MESSAGE_LEN 1024 /** * virXendError: * @conn: the connection if available * @error: the error number * @fmt: format string followed by variable args * * Handle an error at the xend daemon interface */ static void virXendError(virConnectPtr conn, virErrorNumber error, const char *fmt, ...) { va_list args; char msg[MAX_ERROR_MESSAGE_LEN]; const char *msg2; if (fmt) { va_start (args, fmt); vsnprintf (msg, sizeof (msg), fmt, args); va_end (args); } msg2 = __virErrorMsg (error, fmt ? msg : NULL); __virRaiseError(conn, NULL, NULL, VIR_FROM_XEND, error, VIR_ERR_ERROR, msg2, msg, NULL, 0, 0, msg2, msg); } /** * virXendErrorInt: * @conn: the connection if available * @error: the error number * @val: extra integer information * * Handle an error at the xend daemon interface */ static void virXendErrorInt(virConnectPtr conn, virErrorNumber error, int val) { const char *errmsg; if (error == VIR_ERR_OK) return; errmsg = __virErrorMsg(error, NULL); __virRaiseError(conn, NULL, NULL, VIR_FROM_XEND, error, VIR_ERR_ERROR, errmsg, NULL, NULL, val, 0, errmsg, val); } /** * do_connect: * @xend: pointer to the Xen Daemon structure * * Internal routine to (re)connect to the daemon * * Returns the socket file descriptor or -1 in case of error */ static int do_connect(virConnectPtr xend) { int s; int serrno; int no_slow_start = 1; xenUnifiedPrivatePtr priv = (xenUnifiedPrivatePtr) xend->privateData; s = socket(priv->type, SOCK_STREAM, 0); if (s == -1) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("failed to create a socket")); return -1; } /* * try to desactivate slow-start */ setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *)&no_slow_start, sizeof(no_slow_start)); if (connect(s, priv->addr, priv->len) == -1) { serrno = errno; close(s); errno = serrno; s = -1; /* * Connecting to XenD as root is mandatory, so log this error */ if (getuid() == 0) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("failed to connect to xend")); } } return s; } /** * wr_sync: * @xend: the xend connection object * @fd: the file descriptor * @buffer: the I/O buffer * @size: the size of the I/O * @do_read: write operation if 0, read operation otherwise * * Do a synchronous read or write on the file descriptor * * Returns the number of bytes exchanged, or -1 in case of error */ static size_t wr_sync(virConnectPtr xend, int fd, void *buffer, size_t size, int do_read) { size_t offset = 0; while (offset < size) { ssize_t len; if (do_read) { len = read(fd, ((char *) buffer) + offset, size - offset); } else { len = write(fd, ((char *) buffer) + offset, size - offset); } /* recoverable error, retry */ if ((len == -1) && ((errno == EAGAIN) || (errno == EINTR))) { continue; } /* eof */ if (len == 0) { break; } /* unrecoverable error */ if (len == -1) { if (do_read) virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("failed to read from Xen Daemon")); else virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("failed to read from Xen Daemon")); return (-1); } offset += len; } return offset; } /** * sread: * @xend: the xend connection object * @fd: the file descriptor * @buffer: the I/O buffer * @size: the size of the I/O * * Internal routine to do a synchronous read * * Returns the number of bytes read, or -1 in case of error */ static ssize_t sread(virConnectPtr xend, int fd, void *buffer, size_t size) { return wr_sync(xend, fd, buffer, size, 1); } /** * swrite: * @xend: the xend connection object * @fd: the file descriptor * @buffer: the I/O buffer * @size: the size of the I/O * * Internal routine to do a synchronous write * * Returns the number of bytes written, or -1 in case of error */ static ssize_t swrite(virConnectPtr xend, int fd, const void *buffer, size_t size) { return wr_sync(xend, fd, (void *) buffer, size, 0); } /** * swrites: * @xend: the xend connection object * @fd: the file descriptor * @string: the string to write * * Internal routine to do a synchronous write of a string * * Returns the number of bytes written, or -1 in case of error */ static ssize_t swrites(virConnectPtr xend, int fd, const char *string) { return swrite(xend, fd, string, strlen(string)); } /** * sreads: * @xend: the xend connection object * @fd: the file descriptor * @buffer: the I/O buffer * @n_buffer: the size of the I/O buffer * * Internal routine to do a synchronous read of a line * * Returns the number of bytes read, or -1 in case of error */ static ssize_t sreads(virConnectPtr xend, int fd, char *buffer, size_t n_buffer) { size_t offset; if (n_buffer < 1) return (-1); for (offset = 0; offset < (n_buffer - 1); offset++) { ssize_t ret; ret = sread(xend, fd, buffer + offset, 1); if (ret == 0) break; else if (ret == -1) return ret; if (buffer[offset] == '\n') { offset++; break; } } buffer[offset] = 0; return offset; } static int istartswith(const char *haystack, const char *needle) { return STRCASEEQLEN(haystack, needle, strlen(needle)); } /** * xend_req: * @xend: the xend connection object * @fd: the file descriptor * @content: the buffer to store the content * @n_content: the size of the buffer * * Read the HTTP response from a Xen Daemon request. * * Returns the HTTP return code. */ static int xend_req(virConnectPtr xend, int fd, char *content, size_t n_content) { char buffer[4096]; int content_length = -1; int retcode = 0; while (sreads(xend, fd, buffer, sizeof(buffer)) > 0) { if (STREQ(buffer, "\r\n")) break; if (istartswith(buffer, "Content-Length: ")) content_length = atoi(buffer + 16); else if (istartswith(buffer, "HTTP/1.1 ")) retcode = atoi(buffer + 9); } if (content_length > -1) { ssize_t ret; if ((unsigned int) content_length > (n_content + 1)) content_length = n_content - 1; ret = sread(xend, fd, content, content_length); if (ret < 0) return -1; content[ret] = 0; } else { content[0] = 0; } return retcode; } /** * xend_get: * @xend: pointer to the Xen Daemon structure * @path: the path used for the HTTP request * @content: the buffer to store the content * @n_content: the size of the buffer * * Do an HTTP GET RPC with the Xen Daemon * * Returns the HTTP return code or -1 in case or error. */ static int xend_get(virConnectPtr xend, const char *path, char *content, size_t n_content) { int ret; int s = do_connect(xend); if (s == -1) return s; swrites(xend, s, "GET "); swrites(xend, s, path); swrites(xend, s, " HTTP/1.1\r\n"); swrites(xend, s, "Host: localhost:8000\r\n" "Accept-Encoding: identity\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "\r\n"); ret = xend_req(xend, s, content, n_content); close(s); if (((ret < 0) || (ret >= 300)) && ((ret != 404) || (!STRPREFIX(path, "/xend/domain/")))) { virXendError(xend, VIR_ERR_GET_FAILED, _("xend_get: error from xen daemon: %s"), content); } return ret; } #ifndef PROXY /** * xend_post: * @xend: pointer to the Xen Daemon structure * @path: the path used for the HTTP request * @ops: the information sent for the POST * @content: the buffer to store the content * @n_content: the size of the buffer * * Do an HTTP POST RPC with the Xen Daemon, this usually makes changes at the * Xen level. * * Returns the HTTP return code or -1 in case or error. */ static int xend_post(virConnectPtr xend, const char *path, const char *ops, char *content, size_t n_content) { char buffer[100]; int ret; int s = do_connect(xend); if (s == -1) return s; swrites(xend, s, "POST "); swrites(xend, s, path); swrites(xend, s, " HTTP/1.1\r\n"); swrites(xend, s, "Host: localhost:8000\r\n" "Accept-Encoding: identity\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: "); snprintf(buffer, sizeof(buffer), "%d", (int) strlen(ops)); swrites(xend ,s, buffer); swrites(xend, s, "\r\n\r\n"); swrites(xend, s, ops); ret = xend_req(xend, s, content, n_content); close(s); if ((ret < 0) || (ret >= 300)) { virXendError(xend, VIR_ERR_POST_FAILED, _("xend_post: error from xen daemon: %s"), content); } else if ((ret == 202) && (strstr(content, "failed") != NULL)) { virXendError(xend, VIR_ERR_POST_FAILED, _("xend_post: error from xen daemon: %s"), content); ret = -1; } else if (((ret >= 200) && (ret <= 202)) && (strstr(content, "xend.err") != NULL)) { /* This is to catch case of things like 'virsh dump Domain-0 foo' * which returns a success code, but the word 'xend.err' * in body to indicate error :-( */ virXendError(xend, VIR_ERR_POST_FAILED, _("xend_post: error from xen daemon: %s"), content); ret = -1; } return ret; } #endif /* ! PROXY */ /** * http2unix: * @xend: the xend connection object * @ret: the http return code * * Convert the HTTP return code to 0/-1 and set errno if needed * * Return -1 in case of error code 0 otherwise */ static int http2unix(virConnectPtr xend, int ret) { switch (ret) { case -1: break; case 200: case 201: case 202: return 0; case 404: errno = ESRCH; break; case 500: errno = EIO; break; default: virXendErrorInt(xend, VIR_ERR_HTTP_ERROR, ret); errno = EINVAL; break; } return -1; } #ifndef PROXY /** * xend_op_ext: * @xend: pointer to the Xen Daemon structure * @path: path for the object * @error: buffer for the error output * @n_error: size of @error * @key: the key for the operation * @ap: input values to pass to the operation * * internal routine to run a POST RPC operation to the Xen Daemon * * Returns 0 in case of success, -1 in case of failure. */ static int xend_op_ext(virConnectPtr xend, const char *path, char *error, size_t n_error, const char *key, va_list ap) { const char *k = key, *v; virBuffer buf = VIR_BUFFER_INITIALIZER; int ret; char *content; while (k) { v = va_arg(ap, const char *); virBufferVSprintf(&buf, "%s", k); virBufferVSprintf(&buf, "%s", "="); virBufferVSprintf(&buf, "%s", v); k = va_arg(ap, const char *); if (k) virBufferVSprintf(&buf, "%s", "&"); } if (virBufferError(&buf)) { virXendError(NULL, VIR_ERR_NO_MEMORY, _("allocate buffer")); return -1; } content = virBufferContentAndReset(&buf); ret = http2unix(xend, xend_post(xend, path, content, error, n_error)); VIR_FREE(content); return ret; } /** * xend_op: * @xend: pointer to the Xen Daemon structure * @name: the domain name target of this operation * @error: buffer for the error output * @n_error: size of @error * @key: the key for the operation * @ap: input values to pass to the operation * @...: input values to pass to the operation * * internal routine to run a POST RPC operation to the Xen Daemon targetting * a given domain. * * Returns 0 in case of success, -1 in case of failure. */ static int xend_op(virConnectPtr xend, const char *name, const char *key, ...) { char buffer[1024]; char error[1024]; va_list ap; int ret; snprintf(buffer, sizeof(buffer), "/xend/domain/%s", name); va_start(ap, key); ret = xend_op_ext(xend, buffer, error, sizeof(error), key, ap); va_end(ap); return ret; } #endif /* ! PROXY */ /** * sexpr_get: * @xend: pointer to the Xen Daemon structure * @fmt: format string for the path of the operation * @...: extra data to build the path of the operation * * Internal routine to run a simple GET RPC operation to the Xen Daemon * * Returns a parsed S-Expression in case of success, NULL in case of failure */ static struct sexpr *sexpr_get(virConnectPtr xend, const char *fmt, ...) ATTRIBUTE_FORMAT(printf,2,3); static struct sexpr * sexpr_get(virConnectPtr xend, const char *fmt, ...) { char buffer[4096]; char path[1024]; va_list ap; int ret; va_start(ap, fmt); vsnprintf(path, sizeof(path), fmt, ap); va_end(ap); ret = xend_get(xend, path, buffer, sizeof(buffer)); ret = http2unix(xend ,ret); if (ret == -1) return NULL; return string2sexpr(buffer); } /** * sexpr_int: * @sexpr: an S-Expression * @name: the name for the value * * convenience function to lookup an int value in the S-Expression * * Returns the value found or 0 if not found (but may not be an error) */ static int sexpr_int(const struct sexpr *sexpr, const char *name) { const char *value = sexpr_node(sexpr, name); if (value) { return strtol(value, NULL, 0); } return 0; } /** * sexpr_float: * @sexpr: an S-Expression * @name: the name for the value * * convenience function to lookup a float value in the S-Expression * * Returns the value found or 0 if not found (but may not be an error) */ static double sexpr_float(const struct sexpr *sexpr, const char *name) { const char *value = sexpr_node(sexpr, name); if (value) { return strtod(value, NULL); } return 0; } /** * sexpr_u64: * @sexpr: an S-Expression * @name: the name for the value * * convenience function to lookup a 64bits unsigned int value in the * S-Expression * * Returns the value found or 0 if not found (but may not be an error) */ static uint64_t sexpr_u64(const struct sexpr *sexpr, const char *name) { const char *value = sexpr_node(sexpr, name); if (value) { return strtoll(value, NULL, 0); } return 0; } /** * sexpr_uuid: * @ptr: where to store the UUID, incremented * @sexpr: an S-Expression * @name: the name for the value * * convenience function to lookup an UUID value from the S-Expression * * Returns a -1 on error, 0 on success */ static int sexpr_uuid(unsigned char *ptr, const struct sexpr *node, const char *path) { const char *r = sexpr_node(node, path); if (!r) return -1; return virUUIDParse(r, ptr); } #ifndef PROXY /** * urlencode: * @string: the input URL * * Encode an URL see RFC 2396 and following * * Returns the new string or NULL in case of error. */ static char * urlencode(const char *string) { size_t len = strlen(string); char *buffer; char *ptr; size_t i; if (VIR_ALLOC_N(buffer, len * 3 + 1) < 0) { virXendError(NULL, VIR_ERR_NO_MEMORY, _("allocate new buffer")); return (NULL); } ptr = buffer; for (i = 0; i < len; i++) { switch (string[i]) { case ' ': case '\n': snprintf(ptr, 4, "%%%02x", string[i]); ptr += 3; break; default: *ptr = string[i]; ptr++; } } *ptr = 0; return buffer; } #endif /* ! PROXY */ /* Applicable sound models */ static const char *const sound_models[] = { "sb16", "es1370" }; /** * is_sound_model_valid: * @model : model string to check against whitelist * * checks passed model string against whitelist of acceptable models * * Returns 0 if invalid, 1 otherwise */ int is_sound_model_valid(const char *model) { int i; for (i = 0; i < sizeof(sound_models)/sizeof(*sound_models); ++i) { if (STREQ(model, sound_models[i])) { return 1; } } return 0; } /** * is_sound_model_conflict: * @model : model string to look for duplicates of * @soundstr : soundhw string for the form m1,m2,m3 ... * * Returns 0 if no conflict, 1 otherwise */ int is_sound_model_conflict(const char *model, const char *soundstr) { char *dupe; char *cur = (char *) soundstr; while ((dupe = strstr(cur, model))) { if (( (dupe == cur) || // (Start of line | (*(dupe - 1) == ',') ) && // Preceded by comma) & ( (dupe[strlen(model)] == ',') || // (Ends with comma | (dupe[strlen(model)] == '\0') )) // Ends whole string) return 1; else cur = dupe + strlen(model); } return 0; } /** * sound_string_to_xml: * @soundstr : soundhw string for the form m1,m2,m3 ... * * Parses the passed string and returns a heap allocated string containing * the valid libvirt soundxml. Must be free'd by caller. * * Returns NULL on fail, xml string on success (can be the empty string). */ char *sound_string_to_xml(const char *sound) { virBuffer buf = VIR_BUFFER_INITIALIZER; char *tmp; while (sound) { int modelsize, valid, collision = 0; char *model = NULL; char *model_end = strchr(sound, ','); modelsize = (model_end ? (model_end - sound) : strlen(sound)); if(!(model = strndup(sound, modelsize))) goto error; if (!(valid = is_sound_model_valid(model))) { // Check for magic 'all' model. If found, throw out current xml // and build with all available models if (STREQ(model, "all")) { int i; if (virBufferError(&buf)) { VIR_FREE(model); goto error; } tmp = virBufferContentAndReset(&buf); VIR_FREE(tmp); for (i=0; i < sizeof(sound_models)/sizeof(*sound_models); ++i) virBufferVSprintf(&buf, " \n", sound_models[i]); VIR_FREE(model); break; } } if (valid && model_end) collision = is_sound_model_conflict(model, model_end); if (valid && !collision) virBufferVSprintf(&buf, " \n", model); sound = (model_end ? ++model_end : NULL); VIR_FREE(model); } if (virBufferError(&buf)) goto error; return virBufferContentAndReset(&buf); error: tmp = virBufferContentAndReset(&buf); VIR_FREE(tmp); return NULL; } /* PUBLIC FUNCTIONS */ /** * xenDaemonOpen_unix: * @conn: an existing virtual connection block * @path: the path for the Xen Daemon socket * * Creates a localhost Xen Daemon connection * Note: this doesn't try to check if the connection actually works * * Returns 0 in case of success, -1 in case of error. */ int xenDaemonOpen_unix(virConnectPtr conn, const char *path) { struct sockaddr_un *addr; xenUnifiedPrivatePtr priv = (xenUnifiedPrivatePtr) conn->privateData; if ((conn == NULL) || (path == NULL)) return (-1); addr = &priv->addr_un; addr->sun_family = AF_UNIX; memset(addr->sun_path, 0, sizeof(addr->sun_path)); strncpy(addr->sun_path, path, sizeof(addr->sun_path)); priv->len = sizeof(addr->sun_family) + strlen(addr->sun_path); if ((unsigned int) priv->len > sizeof(addr->sun_path)) priv->len = sizeof(addr->sun_path); priv->addr = (struct sockaddr *) addr; priv->type = PF_UNIX; return (0); } #ifndef PROXY /** * xenDaemonOpen_tcp: * @conn: an existing virtual connection block * @host: the host name for the Xen Daemon * @port: the port * * Creates a possibly remote Xen Daemon connection * Note: this doesn't try to check if the connection actually works * * Returns 0 in case of success, -1 in case of error. */ static int xenDaemonOpen_tcp(virConnectPtr conn, const char *host, int port) { struct in_addr ip; struct hostent *pent; xenUnifiedPrivatePtr priv; if ((conn == NULL) || (host == NULL) || (port <= 0)) return (-1); priv = (xenUnifiedPrivatePtr) conn->privateData; pent = gethostbyname(host); if (pent == NULL) { if (inet_aton(host, &ip) == 0) { virXendError(NULL, VIR_ERR_UNKNOWN_HOST, _("gethostbyname failed: %s"), host); errno = ESRCH; return (-1); } } else { memcpy(&ip, pent->h_addr_list[0], sizeof(ip)); } priv->len = sizeof(struct sockaddr_in); priv->addr = (struct sockaddr *) &priv->addr_in; priv->type = PF_INET; priv->addr_in.sin_family = AF_INET; priv->addr_in.sin_port = htons(port); memcpy(&priv->addr_in.sin_addr, &ip, sizeof(ip)); return (0); } /** * xend_wait_for_devices: * @xend: pointer to the Xem Daemon block * @name: name for the domain * * Block the domain until all the virtual devices are ready. This operation * is needed when creating a domain before resuming it. * * Returns 0 in case of success, -1 (with errno) in case of error. */ int xend_wait_for_devices(virConnectPtr xend, const char *name) { return xend_op(xend, name, "op", "wait_for_devices", NULL); } #endif /* PROXY */ /** * xenDaemonListDomainsOld: * @xend: pointer to the Xem Daemon block * * This method will return an array of names of currently running * domains. The memory should be released will a call to free(). * * Returns a list of names or NULL in case of error. */ char ** xenDaemonListDomainsOld(virConnectPtr xend) { size_t extra = 0; struct sexpr *root = NULL; char **ret = NULL; int count = 0; int i; char *ptr; struct sexpr *_for_i, *node; root = sexpr_get(xend, "/xend/domain"); if (root == NULL) goto error; for (_for_i = root, node = root->u.s.car; _for_i->kind == SEXPR_CONS; _for_i = _for_i->u.s.cdr, node = _for_i->u.s.car) { if (node->kind != SEXPR_VALUE) continue; extra += strlen(node->u.value) + 1; count++; } if (VIR_ALLOC_N(ptr, count + 1 + extra) < 0) goto error; ret = (char **) ptr; ptr += sizeof(char *) * (count + 1); i = 0; for (_for_i = root, node = root->u.s.car; _for_i->kind == SEXPR_CONS; _for_i = _for_i->u.s.cdr, node = _for_i->u.s.car) { if (node->kind != SEXPR_VALUE) continue; ret[i] = ptr; strcpy(ptr, node->u.value); ptr += strlen(node->u.value) + 1; i++; } ret[i] = NULL; error: sexpr_free(root); return ret; } #ifndef PROXY /** * xenDaemonDomainCreateLinux: * @xend: A xend instance * @sexpr: An S-Expr description of the domain. * * This method will create a domain based the passed in description. The * domain will be paused after creation and must be unpaused with * xenDaemonResumeDomain() to begin execution. * This method may be deprecated once switching to XML-RPC based communcations * with xend. * * Returns 0 for success, -1 (with errno) on error */ int xenDaemonDomainCreateLinux(virConnectPtr xend, const char *sexpr) { int ret, serrno; char *ptr; ptr = urlencode(sexpr); if (ptr == NULL) { /* this should be caught at the interface but ... */ virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("failed to urlencode the create S-Expr")); return (-1); } ret = xend_op(xend, "", "op", "create", "config", ptr, NULL); serrno = errno; VIR_FREE(ptr); errno = serrno; return ret; } #endif /* ! PROXY */ /** * xenDaemonDomainLookupByName_ids: * @xend: A xend instance * @domname: The name of the domain * @uuid: return value for the UUID if not NULL * * This method looks up the id of a domain * * Returns the id on success; -1 (with errno) on error */ int xenDaemonDomainLookupByName_ids(virConnectPtr xend, const char *domname, unsigned char *uuid) { struct sexpr *root; const char *value; int ret = -1; if (uuid != NULL) memset(uuid, 0, VIR_UUID_BUFLEN); root = sexpr_get(xend, "/xend/domain/%s?detail=1", domname); if (root == NULL) goto error; value = sexpr_node(root, "domain/domid"); if (value == NULL) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing domid")); goto error; } ret = strtol(value, NULL, 0); if ((ret == 0) && (value[0] != '0')) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incorrect domid not numeric")); ret = -1; } else if (uuid != NULL) { if (sexpr_uuid(uuid, root, "domain/uuid") < 0) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing uuid")); } } error: sexpr_free(root); return (ret); } /** * xenDaemonDomainLookupByID: * @xend: A xend instance * @id: The id of the domain * @name: return value for the name if not NULL * @uuid: return value for the UUID if not NULL * * This method looks up the name of a domain based on its id * * Returns the 0 on success; -1 (with errno) on error */ int xenDaemonDomainLookupByID(virConnectPtr xend, int id, char **domname, unsigned char *uuid) { const char *name = NULL; struct sexpr *root; memset(uuid, 0, VIR_UUID_BUFLEN); root = sexpr_get(xend, "/xend/domain/%d?detail=1", id); if (root == NULL) goto error; name = sexpr_node(root, "domain/name"); if (name == NULL) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing name")); goto error; } if (domname) *domname = strdup(name); if (sexpr_uuid(uuid, root, "domain/uuid") < 0) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing uuid")); goto error; } sexpr_free(root); return (0); error: sexpr_free(root); if (domname) VIR_FREE(*domname); return (-1); } #ifndef PROXY static int xend_detect_config_version(virConnectPtr conn) { struct sexpr *root; const char *value; xenUnifiedPrivatePtr priv; if (!VIR_IS_CONNECT(conn)) { virXendError(conn, VIR_ERR_INVALID_CONN, __FUNCTION__); return (-1); } priv = (xenUnifiedPrivatePtr) conn->privateData; root = sexpr_get(conn, "/xend/node/"); if (root == NULL) return (-1); value = sexpr_node(root, "node/xend_config_format"); if (value) { priv->xendConfigVersion = strtol(value, NULL, 10); } else { /* Xen prior to 3.0.3 did not have the xend_config_format field, and is implicitly version 1. */ priv->xendConfigVersion = 1; } sexpr_free(root); return (0); } #endif /* PROXY */ /***************************************************************** ****** ****** ****** ****** Needed helper code ****** ****** ****** ****** *****************************************************************/ /** * xend_parse_sexp_desc_os: * @xend: the xend connection object * @node: the root of the parsed S-Expression * @buf: output buffer object * @hvm: true or 1 if no contains HVM S-Expression * @bootloader: true or 1 if a bootloader is defined * * Parse the xend sexp for description of os and append it to buf. * * Returns 0 in case of success and -1 in case of error */ static int xend_parse_sexp_desc_os(virConnectPtr xend, struct sexpr *node, virBufferPtr buf, int hvm, int bootloader) { const char *loader = NULL; const char *kernel = NULL; const char *initrd = NULL; const char *cmdline = NULL; const char *root = NULL; if (node == NULL || buf == NULL) { return(-1); } virBufferAddLit(buf, " \n"); if (hvm) virBufferAddLit(buf, " hvm\n"); else virBufferAddLit(buf, " linux\n"); if (hvm) { loader = sexpr_node(node, "domain/image/hvm/loader"); if (loader == NULL) { loader = sexpr_node(node, "domain/image/hvm/kernel"); if (loader == NULL) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing HVM loader")); return(-1); } } else { kernel = sexpr_node(node, "domain/image/hvm/kernel"); initrd = sexpr_node(node, "domain/image/hvm/ramdisk"); cmdline = sexpr_node(node, "domain/image/hvm/args"); root = sexpr_node(node, "domain/image/hvm/root"); } } else { kernel = sexpr_node(node, "domain/image/linux/kernel"); initrd = sexpr_node(node, "domain/image/linux/ramdisk"); cmdline = sexpr_node(node, "domain/image/linux/args"); root = sexpr_node(node, "domain/image/linux/root"); } if (hvm) virBufferVSprintf(buf, " %s\n", loader); if ((kernel) && ((!loader) || (STRNEQ(kernel, loader)))) { virBufferVSprintf(buf, " %s\n", kernel); if (initrd && initrd[0]) virBufferVSprintf(buf, " %s\n", initrd); if (root && root[0]) virBufferVSprintf(buf, " %s\n", root); if (cmdline && cmdline[0]) virBufferEscapeString(buf, " %s\n", cmdline); } else { if (hvm) { const char *boot = sexpr_node(node, "domain/image/hvm/boot"); if ((boot != NULL) && (boot[0] != 0)) { while (*boot) { if (*boot == 'a') /* XXX no way to deal with boot from 2nd floppy */ virBufferAddLit(buf, " \n"); else if (*boot == 'c') /* * Don't know what to put here. Say the vm has been given 3 * disks - hda, hdb, hdc. How does one identify the boot disk? * We're going to assume that first disk is the boot disk since * this is most common practice */ virBufferAddLit(buf, " \n"); else if (*boot == 'd') virBufferAddLit(buf, " \n"); else if (*boot == 'n') virBufferAddLit(buf, " \n"); boot++; } } } else if (!bootloader) { virXendError(xend, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing kernel & bootloader")); return(-1); } } virBufferAddLit(buf, " \n"); return(0); } int xend_parse_sexp_desc_char(virConnectPtr conn, virBufferPtr buf, const char *devtype, int portNum, const char *value, const char *tty) { const char *type; int telnet = 0; char *bindPort = NULL; char *bindHost = NULL; char *connectPort = NULL; char *connectHost = NULL; char *path = NULL; int ret = -1; if (value[0] == '/') { type = "dev"; } else if (STRPREFIX(value, "null")) { type = "null"; value = NULL; } else if (STRPREFIX(value, "vc")) { type = "vc"; value = NULL; } else if (STRPREFIX(value, "pty")) { type = "pty"; value = NULL; } else if (STRPREFIX(value, "stdio")) { type = "stdio"; value = NULL; } else if (STRPREFIX(value, "file:")) { type = "file"; value += sizeof("file:")-1; } else if (STRPREFIX(value, "pipe:")) { type = "pipe"; value += sizeof("pipe:")-1; } else if (STRPREFIX(value, "tcp:")) { type = "tcp"; value += sizeof("tcp:")-1; } else if (STRPREFIX(value, "telnet:")) { type = "tcp"; value += sizeof("telnet:")-1; telnet = 1; } else if (STRPREFIX(value, "udp:")) { type = "udp"; value += sizeof("udp:")-1; } else if (STRPREFIX(value, "unix:")) { type = "unix"; value += sizeof("unix:")-1; } else { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("Unknown char device type")); return -1; } /* Compat with legacy syntax */ if (STREQ(devtype, "console") && STREQ(type, "pty") && tty != NULL) { virBufferVSprintf(buf, " <%s type='%s' tty='%s'>\n", devtype, type, tty); } else { virBufferVSprintf(buf, " <%s type='%s'>\n", devtype, type); } if (STREQ(type, "null") || STREQ(type, "vc") || STREQ(type, "stdio")) { /* no source needed */ } else if (STREQ(type, "pty")) { if (tty) virBufferVSprintf(buf, " \n", tty); } else if (STREQ(type, "file") || STREQ(type, "pipe")) { virBufferVSprintf(buf, " \n", value); } else if (STREQ(type, "tcp")) { const char *offset = strchr(value, ':'); const char *offset2; const char *mode, *protocol; if (offset == NULL) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("malformed char device string")); goto error; } if (offset != value && (bindHost = strndup(value, offset - value)) == NULL) goto no_memory; offset2 = strchr(offset, ','); if (offset2 == NULL) bindPort = strdup(offset+1); else bindPort = strndup(offset+1, offset2-(offset+1)); if (bindPort == NULL) goto no_memory; if (offset2 && strstr(offset2, ",listen")) mode = "bind"; else mode = "connect"; protocol = telnet ? "telnet":"raw"; if (bindHost) { virBufferVSprintf(buf, " \n", mode, bindHost, bindPort); } else { virBufferVSprintf(buf, " \n", mode, bindPort); } virBufferVSprintf(buf, " \n", protocol); } else if (STREQ(type, "udp")) { const char *offset = strchr(value, ':'); const char *offset2, *offset3; if (offset == NULL) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("malformed char device string")); goto error; } if (offset != value && (connectHost = strndup(value, offset - value)) == NULL) goto no_memory; offset2 = strchr(offset, '@'); if (offset2 != NULL) { if ((connectPort = strndup(offset + 1, offset2-(offset+1))) == NULL) goto no_memory; offset3 = strchr(offset2, ':'); if (offset3 == NULL) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("malformed char device string")); goto error; } if (offset3 > (offset2 + 1) && (bindHost = strndup(offset2 + 1, offset3 - (offset2+1))) == NULL) goto no_memory; if ((bindPort = strdup(offset3 + 1)) == NULL) goto no_memory; } else { if ((connectPort = strdup(offset + 1)) == NULL) goto no_memory; } if (connectPort) { if (connectHost) { virBufferVSprintf(buf, " \n", connectHost, connectPort); } else { virBufferVSprintf(buf, " \n", connectPort); } } if (bindPort) { if (bindHost) { virBufferVSprintf(buf, " \n", bindHost, bindPort); } else { virBufferVSprintf(buf, " \n", bindPort); } } } else if (STREQ(type, "unix")) { const char *offset = strchr(value, ','); int dolisten = 0; if (offset) path = strndup(value, (offset - value)); else path = strdup(value); if (path == NULL) goto no_memory; if (offset != NULL && strstr(offset, ",listen") != NULL) dolisten = 1; virBufferVSprintf(buf, " \n", dolisten ? "bind" : "connect", path); } virBufferVSprintf(buf, " \n", portNum); virBufferVSprintf(buf, " \n", devtype); ret = 0; if (ret == -1) { no_memory: virXendError(conn, VIR_ERR_NO_MEMORY, _("no memory for char device config")); } error: VIR_FREE(path); VIR_FREE(bindHost); VIR_FREE(bindPort); VIR_FREE(connectHost); VIR_FREE(connectPort); return ret; } typedef int (*sexp_blockdevs_cb) (virConnectPtr conn, void *data, int isBlock, int cdrom, int isNoSrcCdrom, int hvm, const char *drvName, const char *drvType, const char *src, const char *dst, const char *mode); /** * xend_parse_sexp_blockdevs: * @conn: connection * @root: root sexpr * @xendConfigVersion: version of xend * @fn: callback function * @data: optional data for callback function * * This parses out block devices from the domain sexpr and calls * fn (conn, data, ...) for each block device found. * * Returns 0 if successful or -1 if failed. */ static int xend_parse_sexp_blockdevs (virConnectPtr conn, struct sexpr *root, int xendConfigVersion, sexp_blockdevs_cb fn, void *data) { struct sexpr *cur, *node; int hvm; hvm = sexpr_lookup(root, "domain/image/hvm") ? 1 : 0; for (cur = root; cur->kind == SEXPR_CONS; cur = cur->u.s.cdr) { node = cur->u.s.car; /* Normally disks are in a (device (vbd ...)) block but blktap disks ended up in a differently named (device (tap ....)) block.... */ if (sexpr_lookup(node, "device/vbd") || sexpr_lookup(node, "device/tap")) { char *offset; int ret = -1; int isBlock = 0; int cdrom = 0; int isNoSrcCdrom = 0; char *drvName = NULL; char *drvType = NULL; const char *src = NULL; const char *dst = NULL; const char *mode = NULL; /* Again dealing with (vbd...) vs (tap ...) differences */ if (sexpr_lookup(node, "device/vbd")) { src = sexpr_node(node, "device/vbd/uname"); dst = sexpr_node(node, "device/vbd/dev"); mode = sexpr_node(node, "device/vbd/mode"); } else { src = sexpr_node(node, "device/tap/uname"); dst = sexpr_node(node, "device/tap/dev"); mode = sexpr_node(node, "device/tap/mode"); } if (dst == NULL) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, vbd has no dev")); goto bad_parse; } if (src == NULL) { /* There is a case without the uname to the CD-ROM device */ offset = strchr(dst, ':'); if (offset) { if (hvm && STREQ(offset, ":cdrom")) { isNoSrcCdrom = 1; } offset[0] = '\0'; } if (!isNoSrcCdrom) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, vbd has no src")); goto bad_parse; } } if (!isNoSrcCdrom) { offset = strchr(src, ':'); if (!offset) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot parse vbd filename, missing driver name")); goto bad_parse; } if (VIR_ALLOC_N(drvName, (offset-src)+1) < 0) { virXendError(conn, VIR_ERR_NO_MEMORY, _("allocate new buffer")); goto bad_parse; } strncpy(drvName, src, (offset-src)); drvName[offset-src] = '\0'; src = offset + 1; if (STREQ (drvName, "tap")) { offset = strchr(src, ':'); if (!offset) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("cannot parse vbd filename, missing driver type")); goto bad_parse; } if (VIR_ALLOC_N(drvType, (offset-src)+1)< 0) { virXendError(conn, VIR_ERR_NO_MEMORY, _("allocate new buffer")); goto bad_parse; } strncpy(drvType, src, (offset-src)); drvType[offset-src] = '\0'; src = offset + 1; /* Its possible to use blktap driver for block devs too, but kinda pointless because blkback is better, so we assume common case here. If blktap becomes omnipotent, we can revisit this, perhaps stat()'ing the src file in question */ isBlock = 0; } else if (STREQ(drvName, "phy")) { isBlock = 1; } else if (STREQ(drvName, "file")) { isBlock = 0; } } if (STREQLEN (dst, "ioemu:", 6)) dst += 6; /* New style disk config from Xen >= 3.0.3 */ if (xendConfigVersion > 1) { offset = strrchr(dst, ':'); if (offset) { if (STREQ (offset, ":cdrom")) { cdrom = 1; } else if (STREQ (offset, ":disk")) { /* The default anyway */ } else { /* Unknown, lets pretend its a disk too */ } offset[0] = '\0'; } } /* Call the callback function. */ ret = fn (conn, data, isBlock, cdrom, isNoSrcCdrom, hvm, drvName, drvType, src, dst, mode); bad_parse: VIR_FREE(drvName); VIR_FREE(drvType); if (ret == -1) return -1; } } return 0; } static int xend_parse_sexp_desc_blockdev (virConnectPtr conn ATTRIBUTE_UNUSED, void *data, int isBlock, int cdrom, int isNoSrcCdrom, int hvm, const char *drvName, const char *drvType, const char *src, const char *dst, const char *mode) { virBuffer *buf = (virBuffer *) data; const char *bus = NULL; if (!isNoSrcCdrom) { virBufferVSprintf(buf, " \n", isBlock ? "block" : "file", cdrom ? "cdrom" : "disk"); if (drvType) { virBufferVSprintf(buf, " \n", drvName, drvType); } else { virBufferVSprintf(buf, " \n", drvName); } if (isBlock) { virBufferVSprintf(buf, " \n", src); } else { virBufferVSprintf(buf, " \n", src); } } else { /* This case is the cdrom device only */ virBufferAddLit(buf, " \n"); } if (STRPREFIX(dst, "xvd") || !hvm) { bus = "xen"; } else if (STRPREFIX(dst, "sd")) { bus = "scsi"; } else { bus = "ide"; } virBufferVSprintf(buf, " \n", dst, bus); /* XXX should we force mode == r, if cdrom==1, or assume xend has already done this ? */ if ((mode != NULL) && (STREQ (mode, "r"))) virBufferAddLit(buf, " \n"); else if ((mode != NULL) && (STREQ (mode, "w!"))) virBufferAddLit(buf, " \n"); virBufferAddLit(buf, " \n"); return 0; } /** * xend_parse_sexp_desc: * @conn: the connection associated with the XML * @root: the root of the parsed S-Expression * @xendConfigVersion: version of xend * @flags: a combination of virDomainXMLFlags * @cpus: set of cpus the domain may be pinned to * * Parse the xend sexp description and turn it into the XML format similar * to the one unsed for creation. * * Returns the 0 terminated XML string or NULL in case of error. * the caller must free() the returned value. */ static char * xend_parse_sexp_desc(virConnectPtr conn, struct sexpr *root, int xendConfigVersion, int flags, const char *cpus) { struct sexpr *cur, *node; const char *tmp; char *tty, *val; virBuffer buf = VIR_BUFFER_INITIALIZER; int hvm = 0, bootloader = 0, vfb = 0; int domid = -1; int max_mem, cur_mem; unsigned char uuid[VIR_UUID_BUFLEN]; char uuidstr[VIR_UUID_STRING_BUFLEN]; int vif_index = 0; if (root == NULL) { /* ERROR */ return (NULL); } tmp = sexpr_node(root, "domain/domid"); if (tmp == NULL && xendConfigVersion < 3) { /* Old XenD, domid was mandatory */ virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing id")); goto error; } if (tmp) domid = sexpr_int(root, "domain/domid"); else domid = -1; virBufferVSprintf(&buf, "\n", domid); tmp = sexpr_node(root, "domain/name"); if (tmp == NULL) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing name")); goto error; } virBufferVSprintf(&buf, " %s\n", tmp); tmp = sexpr_node(root, "domain/uuid"); if (tmp == NULL) { virXendError(conn, VIR_ERR_INTERNAL_ERROR, _("domain information incomplete, missing name")); goto error; } virUUIDParse(tmp, uuid); virUUIDFormat(uuid, uuidstr); virBufferVSprintf(&buf, " %s\n", uuidstr); hvm = sexpr_lookup(root, "domain/image/hvm") ? 1 : 0; if (!hvm) { tmp = sexpr_node(root, "domain/bootloader"); if (tmp != NULL) { bootloader = 1; virBufferVSprintf(&buf, " %s\n", tmp); } else if (sexpr_has(root, "domain/bootloader")) { bootloader = 1; virBufferAddLit(&buf, " \n"); } tmp = sexpr_node(root, "domain/bootloader_args"); if (tmp != NULL && bootloader) { /* * Only insert bootloader_args if there is also a bootloader param */ virBufferEscapeString(&buf, " %s\n", tmp); } } if (domid != 0) { if (sexpr_lookup(root, "domain/image")) { if (xend_parse_sexp_desc_os(conn, root, &buf, hvm, bootloader) < 0) goto error; } } max_mem = (int) (sexpr_u64(root, "domain/maxmem") << 10); cur_mem = (int) (sexpr_u64(root, "domain/memory") << 10); if (cur_mem > max_mem) max_mem = cur_mem; virBufferVSprintf(&buf, " %d\n", max_mem); if ((cur_mem >= MIN_XEN_GUEST_SIZE) && (cur_mem != max_mem)) virBufferVSprintf(&buf, " %d\n", cur_mem); virBufferAddLit(&buf, " %d\n", sexpr_int(root, "domain/vcpus")); /* TODO if need to output the cpus values, * - parse the cpus values if xend exports * or * - analyze the cpus values extracted by xenDaemonDomainGetVcpus */ tmp = sexpr_node(root, "domain/on_poweroff"); if (tmp != NULL) virBufferVSprintf(&buf, " %s\n", tmp); tmp = sexpr_node(root, "domain/on_reboot"); if (tmp != NULL) virBufferVSprintf(&buf, " %s\n", tmp); tmp = sexpr_node(root, "domain/on_crash"); if (tmp != NULL) virBufferVSprintf(&buf, " %s\n", tmp); if (hvm) { int clockLocal; virBufferAddLit(&buf, " \n"); if (sexpr_int(root, "domain/image/hvm/acpi")) virBufferAddLit(&buf, " \n"); if (sexpr_int(root, "domain/image/hvm/apic")) virBufferAddLit(&buf, " \n"); if (sexpr_int(root, "domain/image/hvm/pae")) virBufferAddLit(&buf, " \n"); virBufferAddLit(&buf, " \n"); clockLocal = sexpr_int(root, "domain/image/hvm/localtime"); virBufferVSprintf(&buf, " \n", clockLocal ? "localtime" : "utc"); } virBufferAddLit(&buf, " \n"); if (hvm) tmp = sexpr_node(root, "domain/image/hvm/device_model"); else tmp = sexpr_node(root, "domain/image/linux/device_model"); if ((tmp != NULL) && (tmp[0] != 0)) virBufferVSprintf(&buf, " %s\n", tmp); /* append block devices */ if (xend_parse_sexp_blockdevs (conn, root, xendConfigVersion, xend_parse_sexp_desc_blockdev, &buf) == -1) goto error; /* append network devices and framebuffer */ for (cur = root; cur->kind == SEXPR_CONS; cur = cur->u.s.cdr) { node = cur->u.s.car; if (sexpr_lookup(node, "device/vif")) { const char *tmp2, *model; tmp2 = sexpr_node(node, "device/vif/script"); tmp = sexpr_node(node, "device/vif/bridge"); model = sexpr_node(node, "device/vif/model"); if ((tmp2 && strstr(tmp2, "bridge")) || tmp) { virBufferAddLit(&buf, " \n"); if (tmp != NULL) virBufferVSprintf(&buf, " \n", tmp); } else { virBufferAddLit(&buf, " \n"); } tmp = sexpr_node(node, "device/vif/vifname"); if (tmp) virBufferVSprintf(&buf, " \n", tmp); else virBufferVSprintf(&buf, " \n", domid, vif_index); tmp = sexpr_node(node, "device/vif/mac"); if (tmp) virBufferVSprintf(&buf, " \n", tmp); tmp = sexpr_node(node, "device/vif/ip"); if (tmp) virBufferVSprintf(&buf, " \n", tmp); if (tmp2) virBufferVSprintf(&buf, "