Doug Goldstein wrote:
The following patch stubs out virDomainRename() as an available API
function. I'm just making sure I'm doing this right. I know there were
some plans to include documentation about adding API to the website
but I don't see it. The included patch does not include my example
driver for qemu and test.
Just looking for a thumbs up/down whether I should continue or not and
whether I'm adding it right.
Doug,
I haven't looked at your patch, so I can't give an opinion on it, but
the attached patch contains the most recent version of the API
documentation that I did. If you apply the patch to your development
tree it'll create the directory:
libvirt/docs/api_extension
with 8 example patch files in it and a single HTML doc describing the
API addition process. It's still a pretty new doc, but it should cover
the basics. I'd much appreciate any feedback you have as well, and I'll
incorporate it.
Dave
From be8c9e3d1dc89ade9680fa61e4392cc9b523f12b Mon Sep 17 00:00:00 2001
From: David Allan <dallan(a)redhat.com>
Date: Wed, 27 May 2009 12:50:57 -0400
Subject: [PATCH 1/1] Document how to extend the libvirt API
---
.../0001-Step-1-of-8-Define-the-public-API.patch | 45 +
...tep-2-of-8-Define-the-internal-driver-API.patch | 37 +
...0003-Step-3-of-8-Implement-the-public-API.patch | 120 ++
...ep-4-of-8-Define-the-wire-protocol-format.patch | 48 +
...0005-Step-5-of-8-Implement-the-RPC-client.patch | 85 ++
...of-8-Implement-the-server-side-dispatcher.patch | 71 ++
...-Step-7-of-8-Implement-the-driver-methods.patch | 1172 ++++++++++++++++++++
.../0008-Step-8-of-8-Add-virsh-support.patch | 133 +++
docs/api_extension/api_extension.html | 264 +++++
docs/sitemap.html.in | 4 +
10 files changed, 1979 insertions(+), 0 deletions(-)
create mode 100644 docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
create mode 100644
docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
create mode 100644 docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
create mode 100644
docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
create mode 100644 docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
create mode 100644
docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
create mode 100644
docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
create mode 100644 docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
create mode 100644 docs/api_extension/api_extension.html
diff --git a/docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
b/docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
new file mode 100644
index 0000000..6d0cd68
--- /dev/null
+++ b/docs/api_extension/0001-Step-1-of-8-Define-the-public-API.patch
@@ -0,0 +1,45 @@
+From 2ae8fd62a1e5e085b7902da9bc207b806d84fd91 Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:16:11 -0400
+Subject: [PATCH] Step 1 of 8 Define the public API
+
+---
+ include/libvirt/libvirt.h.in | 6 ++++++
+ src/libvirt_public.syms | 6 ++++++
+ 2 files changed, 12 insertions(+), 0 deletions(-)
+
+diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
+index a028b21..2f7076f 100644
+--- a/include/libvirt/libvirt.h.in
++++ b/include/libvirt/libvirt.h.in
+@@ -1124,6 +1124,12 @@ int virNodeDeviceDettach (virNodeDevicePtr
dev);
+ int virNodeDeviceReAttach (virNodeDevicePtr dev);
+ int virNodeDeviceReset (virNodeDevicePtr dev);
+
++virNodeDevicePtr virNodeDeviceCreateXML (virConnectPtr conn,
++ const char *xmlDesc,
++ unsigned int flags);
++
++int virNodeDeviceDestroy (virNodeDevicePtr dev);
++
+ /*
+ * Domain Event Notification
+ */
+diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms
+index f7ebbc3..b8f9128 100644
+--- a/src/libvirt_public.syms
++++ b/src/libvirt_public.syms
+@@ -258,4 +258,10 @@ LIBVIRT_0.6.1 {
+ virNodeGetSecurityModel;
+ } LIBVIRT_0.6.0;
+
++LIBVIRT_0.6.3 {
++ global:
++ virNodeDeviceCreateXML;
++ virNodeDeviceDestroy;
++} LIBVIRT_0.6.1;
++
+ # .... define new API here using predicted next version number ....
+--
+1.6.0.6
+
diff --git a/docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
b/docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
new file mode 100644
index 0000000..231cbdf
--- /dev/null
+++ b/docs/api_extension/0002-Step-2-of-8-Define-the-internal-driver-API.patch
@@ -0,0 +1,37 @@
+From b26d7fc2d64e7e6e4d3ea2b43361015d3620d7a6 Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:19:14 -0400
+Subject: [PATCH] Step 2 of 8 Define the internal driver API
+
+---
+ src/driver.h | 7 +++++++
+ 1 files changed, 7 insertions(+), 0 deletions(-)
+
+diff --git a/src/driver.h b/src/driver.h
+index 39dc413..c357b76 100644
+--- a/src/driver.h
++++ b/src/driver.h
+@@ -684,6 +684,11 @@ typedef int (*virDevMonDeviceListCaps)(virNodeDevicePtr dev,
+ char **const names,
+ int maxnames);
+
++typedef virNodeDevicePtr (*virDrvNodeDeviceCreateXML)(virConnectPtr conn,
++ const char *xmlDesc,
++ unsigned int flags);
++typedef int (*virDrvNodeDeviceDestroy)(virNodeDevicePtr dev);
++
+ /**
+ * _virDeviceMonitor:
+ *
+@@ -702,6 +707,8 @@ struct _virDeviceMonitor {
+ virDevMonDeviceGetParent deviceGetParent;
+ virDevMonDeviceNumOfCaps deviceNumOfCaps;
+ virDevMonDeviceListCaps deviceListCaps;
++ virDrvNodeDeviceCreateXML deviceCreateXML;
++ virDrvNodeDeviceDestroy deviceDestroy;
+ };
+
+ /*
+--
+1.6.0.6
+
diff --git a/docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
b/docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
new file mode 100644
index 0000000..079bd06
--- /dev/null
+++ b/docs/api_extension/0003-Step-3-of-8-Implement-the-public-API.patch
@@ -0,0 +1,120 @@
+From fc585594a207dfb9149e7d3d01c9eb1c79b6d52d Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:22:23 -0400
+Subject: [PATCH] Step 3 of 8 Implement the public API
+
+---
+ src/libvirt.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 97 insertions(+), 0 deletions(-)
+
+diff --git a/src/libvirt.c b/src/libvirt.c
+index f3d4484..ded18a7 100644
+--- a/src/libvirt.c
++++ b/src/libvirt.c
+@@ -7509,6 +7509,103 @@ error:
+ }
+
+
++/**
++ * virNodeDeviceCreateXML:
++ * @conn: pointer to the hypervisor connection
++ * @xmlDesc: string containing an XML description of the device to be created
++ * @flags: callers should always pass 0
++ *
++ * Create a new device on the VM host machine, for example, virtual
++ * HBAs created using vport_create.
++ *
++ * Returns a node device object if successful, NULL in case of failure
++ */
++virNodeDevicePtr
++virNodeDeviceCreateXML(virConnectPtr conn,
++ const char *xmlDesc,
++ unsigned int flags)
++{
++ VIR_DEBUG("conn=%p, xmlDesc=%s, flags=%d", conn, xmlDesc, flags);
++
++ virResetLastError();
++
++ if (!VIR_IS_CONNECT(conn)) {
++ virLibConnError(NULL, VIR_ERR_INVALID_CONN, __FUNCTION__);
++ return NULL;
++ }
++
++ if (conn->flags & VIR_CONNECT_RO) {
++ virLibConnError(conn, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
++ goto error;
++ }
++
++ if (xmlDesc == NULL) {
++ virLibConnError(conn, VIR_ERR_INVALID_ARG, __FUNCTION__);
++ goto error;
++ }
++
++ if (conn->deviceMonitor &&
++ conn->deviceMonitor->deviceCreateXML) {
++ virNodeDevicePtr dev = conn->deviceMonitor->deviceCreateXML(conn, xmlDesc,
flags);
++ if (dev == NULL)
++ goto error;
++ return dev;
++ }
++
++ virLibConnError (conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
++
++error:
++ /* Copy to connection error object for back compatability */
++ virSetConnError(conn);
++ return NULL;
++}
++
++
++/**
++ * virNodeDeviceDestroy:
++ * @dev: a device object
++ *
++ * Destroy the device object. The virtual device is removed from the host operating
system.
++ * This function may require privileged access
++ *
++ * Returns 0 in case of success and -1 in case of failure.
++ */
++int
++virNodeDeviceDestroy(virNodeDevicePtr dev)
++{
++ DEBUG("dev=%p", dev);
++
++ virResetLastError();
++
++ if (!VIR_IS_CONNECTED_NODE_DEVICE(dev)) {
++ virLibNodeDeviceError(NULL, VIR_ERR_INVALID_NODE_DEVICE, __FUNCTION__);
++ return (-1);
++ }
++
++ if (dev->conn->flags & VIR_CONNECT_RO) {
++ virLibConnError(dev->conn, VIR_ERR_OPERATION_DENIED, __FUNCTION__);
++ goto error;
++ }
++
++ if (dev->conn->deviceMonitor &&
++ dev->conn->deviceMonitor->deviceDestroy) {
++ int retval = dev->conn->deviceMonitor->deviceDestroy(dev);
++ if (retval < 0) {
++ goto error;
++ }
++
++ return 0;
++ }
++
++ virLibConnError (dev->conn, VIR_ERR_NO_SUPPORT, __FUNCTION__);
++
++error:
++ /* Copy to connection error object for back compatability */
++ virSetConnError(dev->conn);
++ return -1;
++}
++
++
+ /*
+ * Domain Event Notification
+ */
+--
+1.6.0.6
+
diff --git a/docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
b/docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
new file mode 100644
index 0000000..8990263
--- /dev/null
+++ b/docs/api_extension/0004-Step-4-of-8-Define-the-wire-protocol-format.patch
@@ -0,0 +1,48 @@
+From bce8f1243b0454c0d70e3db832a039d22faab09a Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Wed, 20 May 2009 13:58:58 -0400
+Subject: [PATCH] Step 4 of 8 Define the wire protocol format
+
+---
+ qemud/remote_protocol.x | 18 +++++++++++++++++-
+ 1 files changed, 17 insertions(+), 1 deletions(-)
+
+diff --git a/qemud/remote_protocol.x b/qemud/remote_protocol.x
+index 2d8e6a2..2c79949 100644
+--- a/qemud/remote_protocol.x
++++ b/qemud/remote_protocol.x
+@@ -1109,6 +1109,19 @@ struct remote_node_device_reset_args {
+ remote_nonnull_string name;
+ };
+
++struct remote_node_device_create_xml_args {
++ remote_nonnull_string xml_desc;
++ int flags;
++};
++
++struct remote_node_device_create_xml_ret {
++ remote_nonnull_node_device dev;
++};
++
++struct remote_node_device_destroy_args {
++ remote_nonnull_string name;
++};
++
+
+ /**
+ * Events Register/Deregister:
+@@ -1270,7 +1283,10 @@ enum remote_procedure {
+ REMOTE_PROC_NODE_DEVICE_RESET = 120,
+
+ REMOTE_PROC_DOMAIN_GET_SECURITY_LABEL = 121,
+- REMOTE_PROC_NODE_GET_SECURITY_MODEL = 122
++ REMOTE_PROC_NODE_GET_SECURITY_MODEL = 122,
++
++ REMOTE_PROC_NODE_DEVICE_CREATE_XML = 123,
++ REMOTE_PROC_NODE_DEVICE_DESTROY = 124
+ };
+
+ /* Custom RPC structure. */
+--
+1.6.0.6
+
diff --git a/docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
b/docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
new file mode 100644
index 0000000..6f87dea
--- /dev/null
+++ b/docs/api_extension/0005-Step-5-of-8-Implement-the-RPC-client.patch
@@ -0,0 +1,85 @@
+From ff272552c297966ace3492aefe91fc830152251a Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:26:12 -0400
+Subject: [PATCH] Step 5 of 8 Implement the RPC client
+
+---
+ src/remote_internal.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 55 insertions(+), 0 deletions(-)
+
+diff --git a/src/remote_internal.c b/src/remote_internal.c
+index 4b3afb0..e665ef8 100644
+--- a/src/remote_internal.c
++++ b/src/remote_internal.c
+@@ -4978,6 +4978,59 @@ done:
+ }
+
+
++static virNodeDevicePtr
++remoteNodeDeviceCreateXML(virConnectPtr conn,
++ const char *xmlDesc,
++ unsigned int flags)
++{
++ remote_node_device_create_xml_args args;
++ remote_node_device_create_xml_ret ret;
++ virNodeDevicePtr dev = NULL;
++ struct private_data *priv = conn->privateData;
++
++ remoteDriverLock(priv);
++
++ memset(&ret, 0, sizeof ret);
++ args.xml_desc = (char *)xmlDesc;
++ args.flags = flags;
++
++ if (call(conn, priv, 0, REMOTE_PROC_NODE_DEVICE_CREATE_XML,
++ (xdrproc_t) xdr_remote_node_device_create_xml_args, (char *) &args,
++ (xdrproc_t) xdr_remote_node_device_create_xml_ret, (char *) &ret) ==
-1)
++ goto done;
++
++ dev = get_nonnull_node_device(conn, ret.dev);
++ xdr_free ((xdrproc_t) xdr_remote_node_device_create_xml_ret, (char *) &ret);
++
++done:
++ remoteDriverUnlock(priv);
++ return dev;
++}
++
++static int
++remoteNodeDeviceDestroy(virNodeDevicePtr dev)
++{
++ int rv = -1;
++ remote_node_device_destroy_args args;
++ struct private_data *priv = dev->conn->privateData;
++
++ remoteDriverLock(priv);
++
++ args.name = dev->name;
++
++ if (call(dev->conn, priv, 0, REMOTE_PROC_NODE_DEVICE_DESTROY,
++ (xdrproc_t) xdr_remote_node_device_destroy_args, (char *) &args,
++ (xdrproc_t) xdr_void, (char *) NULL) == -1)
++ goto done;
++
++ rv = 0;
++
++done:
++ remoteDriverUnlock(priv);
++ return rv;
++}
++
++
+ /*----------------------------------------------------------------------*/
+
+ static int
+@@ -6982,6 +7035,8 @@ static virDeviceMonitor dev_monitor = {
+ .deviceGetParent = remoteNodeDeviceGetParent,
+ .deviceNumOfCaps = remoteNodeDeviceNumOfCaps,
+ .deviceListCaps = remoteNodeDeviceListCaps,
++ .deviceCreateXML = remoteNodeDeviceCreateXML,
++ .deviceDestroy = remoteNodeDeviceDestroy
+ };
+
+
+--
+1.6.0.6
+
diff --git
a/docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
b/docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
new file mode 100644
index 0000000..96df453
--- /dev/null
+++ b/docs/api_extension/0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch
@@ -0,0 +1,71 @@
+From 4c5166df583459574526841234d61d6ae5be19a0 Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:26:55 -0400
+Subject: [PATCH] Step 6 of 8 Implement the server side dispatcher
+
+---
+ qemud/remote.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 48 insertions(+), 0 deletions(-)
+
+diff --git a/qemud/remote.c b/qemud/remote.c
+index e27820f..8d24a3a 100644
+--- a/qemud/remote.c
++++ b/qemud/remote.c
+@@ -4323,6 +4323,54 @@ remoteDispatchNodeDeviceReset (struct qemud_server *server
ATTRIBUTE_UNUSED,
+ }
+
+
++static int
++remoteDispatchNodeDeviceCreateXml(struct qemud_server *server ATTRIBUTE_UNUSED,
++ struct qemud_client *client ATTRIBUTE_UNUSED,
++ virConnectPtr conn,
++ remote_error *rerr,
++ remote_node_device_create_xml_args *args,
++ remote_node_device_create_xml_ret *ret)
++{
++ virNodeDevicePtr dev;
++
++ dev = virNodeDeviceCreateXML (conn, args->xml_desc, args->flags);
++ if (dev == NULL) {
++ remoteDispatchConnError(rerr, conn);
++ return -1;
++ }
++
++ make_nonnull_node_device (&ret->dev, dev);
++ virNodeDeviceFree(dev);
++
++ return 0;
++}
++
++
++static int
++remoteDispatchNodeDeviceDestroy(struct qemud_server *server ATTRIBUTE_UNUSED,
++ struct qemud_client *client ATTRIBUTE_UNUSED,
++ virConnectPtr conn,
++ remote_error *rerr,
++ remote_node_device_destroy_args *args,
++ void *ret ATTRIBUTE_UNUSED)
++{
++ virNodeDevicePtr dev;
++
++ dev = virNodeDeviceLookupByName(conn, args->name);
++ if (dev == NULL) {
++ remoteDispatchFormatError(rerr, "%s", _("node_device not
found"));
++ return -1;
++ }
++
++ if (virNodeDeviceDestroy(dev) == -1) {
++ remoteDispatchConnError(rerr, conn);
++ return -1;
++ }
++
++ return 0;
++}
++
++
+ /**************************
+ * Async Events
+ **************************/
+--
+1.6.0.6
+
diff --git a/docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
b/docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
new file mode 100644
index 0000000..ddd0a41
--- /dev/null
+++ b/docs/api_extension/0007-Step-7-of-8-Implement-the-driver-methods.patch
@@ -0,0 +1,1172 @@
+From 04d20a662109de6727232eb1213627877bb9662f Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:35:15 -0400
+Subject: [PATCH] Step 7 of 8 Implement the driver methods
+
+---
+ src/Makefile.am | 4 +-
+ src/node_device.c | 430 +++++++++++++++++++++++++++++++++++++++++++
+ src/node_device.h | 13 ++
+ src/node_device_conf.c | 136 ++++++++++++--
+ src/node_device_conf.h | 22 ++-
+ src/node_device_hal.c | 5 +
+ src/node_device_hal.h | 40 ++++
+ src/node_device_hal_linux.c | 170 +++++++++++++++++
+ src/qemu_driver.c | 2 +-
+ src/storage_backend.c | 24 +--
+ src/xen_unified.c | 2 +-
+ tests/nodedevxml2xmltest.c | 2 +-
+ 12 files changed, 810 insertions(+), 40 deletions(-)
+ create mode 100644 src/node_device_hal.h
+ create mode 100644 src/node_device_hal_linux.c
+
+diff --git a/src/Makefile.am b/src/Makefile.am
+index fd692b4..39fabce 100644
+--- a/src/Makefile.am
++++ b/src/Makefile.am
+@@ -188,7 +188,9 @@ NODE_DEVICE_DRIVER_SOURCES = \
+ node_device.c node_device.h
+
+ NODE_DEVICE_DRIVER_HAL_SOURCES = \
+- node_device_hal.c
++ node_device_hal.c \
++ node_device_hal_linux.c
++
+ NODE_DEVICE_DRIVER_DEVKIT_SOURCES = \
+ node_device_devkit.c
+
+diff --git a/src/node_device.c b/src/node_device.c
+index b84729f..4f73baf 100644
+--- a/src/node_device.c
++++ b/src/node_device.c
+@@ -25,6 +25,8 @@
+
+ #include <unistd.h>
+ #include <errno.h>
++#include <fcntl.h>
++#include <time.h>
+
+ #include "virterror_internal.h"
+ #include "datatypes.h"
+@@ -133,6 +135,53 @@ cleanup:
+ return ret;
+ }
+
++
++static virNodeDevicePtr
++nodeDeviceLookupByWWN(virConnectPtr conn,
++ const char *wwnn,
++ const char *wwpn)
++{
++ unsigned int i;
++ virDeviceMonitorStatePtr driver = conn->devMonPrivateData;
++ virNodeDeviceObjListPtr devs = &driver->devs;
++ virNodeDevCapsDefPtr cap = NULL;
++ virNodeDeviceObjPtr obj = NULL;
++ virNodeDevicePtr dev = NULL;
++
++ nodeDeviceLock(driver);
++
++ for (i = 0; i < devs->count; i++) {
++
++ obj = devs->objs[i];
++ virNodeDeviceObjLock(obj);
++ cap = obj->def->caps;
++
++ while (cap) {
++
++ if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST) {
++ if (cap->data.scsi_host.flags &
++ VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
++
++ if (STREQ(cap->data.scsi_host.wwnn, wwnn) &&
++ STREQ(cap->data.scsi_host.wwpn, wwpn)) {
++ dev = virGetNodeDevice(conn, obj->def->name);
++ virNodeDeviceObjUnlock(obj);
++ goto out;
++ }
++ }
++ }
++ cap = cap->next;
++ }
++
++ virNodeDeviceObjUnlock(obj);
++ }
++
++out:
++ nodeDeviceUnlock(driver);
++ return dev;
++}
++
++
+ static char *nodeDeviceDumpXML(virNodeDevicePtr dev,
+ unsigned int flags ATTRIBUTE_UNUSED)
+ {
+@@ -258,6 +307,385 @@ cleanup:
+ }
+
+
++static int
++nodeDeviceVportCreateDelete(virConnectPtr conn,
++ const int parent_host,
++ const char *wwpn,
++ const char *wwnn,
++ int operation)
++{
++ int fd = -1;
++ int retval = 0;
++ char *operation_path = NULL, *vport_name = NULL;
++ const char *operation_file = NULL;
++ size_t towrite = 0;
++ unsigned int written = 0;
++
++ switch (operation) {
++ case VPORT_CREATE:
++ operation_file = LINUX_SYSFS_VPORT_CREATE_POSTFIX;
++ break;
++ case VPORT_DELETE:
++ operation_file = LINUX_SYSFS_VPORT_DELETE_POSTFIX;
++ break;
++ default:
++ virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++ _("Invalid vport operation (%d)"),
operation);
++ retval = -1;
++ goto cleanup;
++ break;
++ }
++
++ if (virAsprintf(&operation_path,
++ "%shost%d%s",
++ LINUX_SYSFS_FC_HOST_PREFIX,
++ parent_host,
++ operation_file) < 0) {
++
++ virReportOOMError(conn);
++ retval = -1;
++ goto cleanup;
++ }
++
++ VIR_DEBUG(_("Vport operation path is '%s'"), operation_path);
++
++ fd = open(operation_path, O_WRONLY);
++
++ if (fd < 0) {
++ virReportSystemError(conn, errno,
++ _("Could not open '%s' for vport
operation"),
++ operation_path);
++ retval = -1;
++ goto cleanup;
++ }
++
++ if (virAsprintf(&vport_name,
++ "%s:%s",
++ wwpn,
++ wwnn) < 0) {
++
++ virReportOOMError(conn);
++ retval = -1;
++ goto cleanup;
++ }
++
++ towrite = strlen(vport_name);
++ written = safewrite(fd, vport_name, towrite);
++ if (written != towrite) {
++ virReportSystemError(conn, errno,
++ _("Write of '%s' to '%s' during
"
++ "vport create/delete failed "
++ "(towrite: %lu written: %d)"),
++ vport_name, operation_path,
++ towrite, written);
++ retval = -1;
++ }
++
++cleanup:
++ if (fd != -1) {
++ close(fd);
++ }
++ VIR_FREE(vport_name);
++ VIR_FREE(operation_path);
++ VIR_DEBUG("%s", _("Vport operation complete"));
++ return retval;
++}
++
++
++static int
++get_wwns(virConnectPtr conn,
++ virNodeDeviceDefPtr def,
++ char **wwnn,
++ char **wwpn)
++{
++ virNodeDevCapsDefPtr cap = NULL;
++ int ret = 0;
++
++ cap = def->caps;
++ while (cap != NULL) {
++ if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST &&
++ cap->data.scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
++ *wwnn = strdup(cap->data.scsi_host.wwnn);
++ *wwpn = strdup(cap->data.scsi_host.wwpn);
++ break;
++ }
++
++ cap = cap->next;
++ }
++
++ if (cap == NULL) {
++ virNodeDeviceReportError(conn, VIR_ERR_NO_SUPPORT,
++ "%s", _("Device is not a fibre channel
HBA"));
++ ret = -1;
++ }
++
++ if (*wwnn == NULL || *wwpn == NULL) {
++ /* Free the other one, if allocated... */
++ VIR_FREE(wwnn);
++ VIR_FREE(wwpn);
++ ret = -1;
++ virReportOOMError(conn);
++ }
++
++ return ret;
++}
++
++
++static int
++get_parent_host(virConnectPtr conn,
++ virDeviceMonitorStatePtr driver,
++ const char *dev_name,
++ const char *parent_name,
++ int *parent_host)
++{
++ virNodeDeviceObjPtr parent = NULL;
++ virNodeDevCapsDefPtr cap = NULL;
++ int ret = 0;
++
++ parent = virNodeDeviceFindByName(&driver->devs, parent_name);
++ if (parent == NULL) {
++ virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE,
++ _("Could not find parent device for
'%s'"),
++ dev_name);
++ ret = -1;
++ goto out;
++ }
++
++ cap = parent->def->caps;
++ while (cap != NULL) {
++ if (cap->type == VIR_NODE_DEV_CAP_SCSI_HOST &&
++ (cap->data.scsi_host.flags &
++ VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS)) {
++ *parent_host = cap->data.scsi_host.host;
++ break;
++ }
++
++ cap = cap->next;
++ }
++
++ if (cap == NULL) {
++ virNodeDeviceReportError(conn, VIR_ERR_INVALID_NODE_DEVICE,
++ _("Device %s is not capable of vport
operations"),
++ parent->def->name);
++ ret = -1;
++ }
++
++ virNodeDeviceObjUnlock(parent);
++
++out:
++ return ret;
++}
++
++
++static int
++get_time(virConnectPtr conn, time_t *t)
++{
++ int ret = 0;
++
++ *t = time(NULL);
++ if (*t == (time_t)-1) {
++ virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++ "%s", _("Could not get current
time"));
++
++ *t = 0;
++ ret = -1;
++ }
++
++ return ret;
++}
++
++
++/* When large numbers of devices are present on the host, it's
++ * possible for udev not to realize that it has work to do before we
++ * get here. We thus keep trying to find the new device we just
++ * created for up to LINUX_NEW_DEVICE_WAIT_TIME. Note that udev's
++ * default settle time is 180 seconds, so once udev realizes that it
++ * has work to do, it might take that long for the udev wait to
++ * return. Thus the total maximum time for this function to return is
++ * the udev settle time plus LINUX_NEW_DEVICE_WAIT_TIME.
++ *
++ * This whole area is a race, but if we retry the udev wait for
++ * LINUX_NEW_DEVICE_WAIT_TIME seconds and there's still no device,
++ * it's probably safe to assume it's not going to appear.
++ */
++static virNodeDevicePtr
++find_new_device(virConnectPtr conn, const char *wwnn, const char *wwpn)
++{
++ virDeviceMonitorStatePtr driver = conn->devMonPrivateData;
++ virNodeDevicePtr dev = NULL;
++ time_t start = 0, now = 0;
++
++ /* The thread that creates the device takes the driver lock, so we
++ * must release it in order to allow the device to be created.
++ * We're not doing anything with the driver pointer at this point,
++ * so it's safe to release it, assuming that the pointer itself
++ * doesn't become invalid. */
++ nodeDeviceUnlock(driver);
++
++ get_time(conn, &start);
++
++ while ((now - start) < LINUX_NEW_DEVICE_WAIT_TIME) {
++
++ virNodeDeviceWaitForDevices(conn);
++
++ dev = nodeDeviceLookupByWWN(conn, wwnn, wwpn);
++
++ if (dev != NULL) {
++ break;
++ }
++
++ sleep(5);
++ if (get_time(conn, &now) == -1) {
++ break;
++ }
++ }
++
++ nodeDeviceLock(driver);
++
++ return dev;
++}
++
++static virNodeDevicePtr
++nodeDeviceCreateXML(virConnectPtr conn,
++ const char *xmlDesc,
++ unsigned int flags ATTRIBUTE_UNUSED)
++{
++ virDeviceMonitorStatePtr driver = conn->devMonPrivateData;
++ virNodeDeviceDefPtr def = NULL;
++ char *wwnn = NULL, *wwpn = NULL;
++ int parent_host = -1;
++ virNodeDevicePtr dev = NULL;
++
++ nodeDeviceLock(driver);
++
++ def = virNodeDeviceDefParseString(conn, xmlDesc, CREATE_DEVICE);
++ if (def == NULL) {
++ goto cleanup;
++ }
++
++ if (get_wwns(conn, def, &wwnn, &wwpn) == -1) {
++ goto cleanup;
++ }
++
++ if (get_parent_host(conn,
++ driver,
++ def->name,
++ def->parent,
++ &parent_host) == -1) {
++ goto cleanup;
++ }
++
++ if (nodeDeviceVportCreateDelete(conn,
++ parent_host,
++ wwpn,
++ wwnn,
++ VPORT_CREATE) == -1) {
++ goto cleanup;
++ }
++
++ dev = find_new_device(conn, wwnn, wwpn);
++ /* We don't check the return value, because one way or another,
++ * we're returning what we get... */
++
++ if (dev == NULL) {
++ virNodeDeviceReportError(conn, VIR_ERR_NO_NODE_DEVICE, NULL);
++ }
++
++cleanup:
++ nodeDeviceUnlock(driver);
++ virNodeDeviceDefFree(def);
++ VIR_FREE(wwnn);
++ VIR_FREE(wwpn);
++ return dev;
++}
++
++
++static int
++nodeDeviceDestroy(virNodeDevicePtr dev)
++{
++ int ret = 0;
++ virDeviceMonitorStatePtr driver = dev->conn->devMonPrivateData;
++ virNodeDeviceObjPtr obj = NULL;
++ char *parent_name = NULL, *wwnn = NULL, *wwpn = NULL;
++ int parent_host = -1;
++
++ nodeDeviceLock(driver);
++ obj = virNodeDeviceFindByName(&driver->devs, dev->name);
++ nodeDeviceUnlock(driver);
++
++ if (!obj) {
++ virNodeDeviceReportError(dev->conn, VIR_ERR_NO_NODE_DEVICE, NULL);
++ goto out;
++ }
++
++ if (get_wwns(dev->conn, obj->def, &wwnn, &wwpn) == -1) {
++ goto out;
++ }
++
++ parent_name = strdup(obj->def->parent);
++
++ /* get_parent_host will cause the device object's lock to be
++ * taken, so we have to dup the parent's name and drop the lock
++ * before calling it. We don't need the reference to the object
++ * any more once we have the parent's name. */
++ virNodeDeviceObjUnlock(obj);
++ obj = NULL;
++
++ if (parent_name == NULL) {
++ virReportOOMError(dev->conn);
++ goto out;
++ }
++
++ if (get_parent_host(dev->conn,
++ driver,
++ dev->name,
++ parent_name,
++ &parent_host) == -1) {
++ goto out;
++ }
++
++ if (nodeDeviceVportCreateDelete(dev->conn,
++ parent_host,
++ wwpn,
++ wwnn,
++ VPORT_DELETE) == -1) {
++ goto out;
++ }
++
++out:
++ VIR_FREE(parent_name);
++ VIR_FREE(wwnn);
++ VIR_FREE(wwpn);
++ return ret;
++}
++
++
++#if defined(UDEVADM) || defined(UDEVSETTLE)
++void virNodeDeviceWaitForDevices(virConnectPtr conn)
++{
++#ifdef UDEVADM
++ const char *const settleprog[] = { UDEVADM, "settle", NULL };
++#else
++ const char *const settleprog[] = { UDEVSETTLE, NULL };
++#endif
++ int exitstatus;
++
++ if (access(settleprog[0], X_OK) != 0)
++ return;
++
++ /*
++ * NOTE: we ignore errors here; this is just to make sure that any device
++ * nodes that are being created finish before we try to scan them.
++ * If this fails for any reason, we still have the backup of polling for
++ * 5 seconds for device nodes.
++ */
++ virRun(conn, settleprog, &exitstatus);
++}
++#else
++void virNodeDeviceWaitForDevices(virConnectPtr conn ATTRIBUTE_UNUSED) {}
++#endif
++
++
+ void registerCommonNodeFuncs(virDeviceMonitorPtr driver)
+ {
+ driver->numOfDevices = nodeNumOfDevices;
+@@ -267,6 +695,8 @@ void registerCommonNodeFuncs(virDeviceMonitorPtr driver)
+ driver->deviceGetParent = nodeDeviceGetParent;
+ driver->deviceNumOfCaps = nodeDeviceNumOfCaps;
+ driver->deviceListCaps = nodeDeviceListCaps;
++ driver->deviceCreateXML = nodeDeviceCreateXML;
++ driver->deviceDestroy = nodeDeviceDestroy;
+ }
+
+
+diff --git a/src/node_device.h b/src/node_device.h
+index 9496120..882ba0f 100644
+--- a/src/node_device.h
++++ b/src/node_device.h
+@@ -28,6 +28,17 @@
+ #include "driver.h"
+ #include "node_device_conf.h"
+
++#define LINUX_SYSFS_SCSI_HOST_PREFIX "/sys/class/scsi_host"
++#define LINUX_SYSFS_SCSI_HOST_POSTFIX "device"
++#define LINUX_SYSFS_FC_HOST_PREFIX "/sys/class/fc_host/"
++
++#define VPORT_CREATE 0
++#define VPORT_DELETE 1
++#define LINUX_SYSFS_VPORT_CREATE_POSTFIX "/vport_create"
++#define LINUX_SYSFS_VPORT_DELETE_POSTFIX "/vport_delete"
++
++#define LINUX_NEW_DEVICE_WAIT_TIME 60
++
+ #ifdef HAVE_HAL
+ int halNodeRegister(void);
+ #endif
+@@ -42,4 +53,6 @@ void registerCommonNodeFuncs(virDeviceMonitorPtr mon);
+
+ int nodedevRegister(void);
+
++void virNodeDeviceWaitForDevices(virConnectPtr conn);
++
+ #endif /* __VIR_NODE_DEVICE_H__ */
+diff --git a/src/node_device_conf.c b/src/node_device_conf.c
+index 6e04112..5b35b60 100644
+--- a/src/node_device_conf.c
++++ b/src/node_device_conf.c
+@@ -53,9 +53,34 @@ VIR_ENUM_IMPL(virNodeDevNetCap, VIR_NODE_DEV_CAP_NET_LAST,
+ "80203",
+ "80211")
+
++VIR_ENUM_IMPL(virNodeDevHBACap, VIR_NODE_DEV_CAP_HBA_LAST,
++ "fc_host",
++ "vport_ops")
+
+ #define virNodeDeviceLog(msg...) fprintf(stderr, msg)
+
++static int
++virNodeDevCapsDefParseString(virConnectPtr conn,
++ const char *xpath,
++ xmlXPathContextPtr ctxt,
++ char **string,
++ virNodeDeviceDefPtr def,
++ const char *missing_error_fmt)
++{
++ char *s;
++
++ s = virXPathString(conn, xpath, ctxt);
++ if (s == NULL) {
++ virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++ missing_error_fmt,
++ def->name);
++ return -1;
++ }
++
++ *string = s;
++ return 0;
++}
++
+ virNodeDeviceObjPtr virNodeDeviceFindByName(const virNodeDeviceObjListPtr devs,
+ const char *name)
+ {
+@@ -302,6 +327,18 @@ char *virNodeDeviceDefFormat(virConnectPtr conn,
+ case VIR_NODE_DEV_CAP_SCSI_HOST:
+ virBufferVSprintf(&buf, " <host>%d</host>\n",
+ data->scsi_host.host);
++ if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST) {
++ virBufferAddLit(&buf, " <capability
type='fc_host'>\n");
++ virBufferVSprintf(&buf,
++ " <wwnn>%s</wwnn>\n",
data->scsi_host.wwnn);
++ virBufferVSprintf(&buf,
++ " <wwpn>%s</wwpn>\n",
data->scsi_host.wwpn);
++ virBufferAddLit(&buf, " </capability>\n");
++ }
++ if (data->scsi_host.flags & VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS) {
++ virBufferAddLit(&buf, " <capability
type='vport_ops' />\n");
++ }
++
+ break;
+ case VIR_NODE_DEV_CAP_SCSI:
+ virBufferVSprintf(&buf, " <host>%d</host>\n",
data->scsi.host);
+@@ -561,26 +598,91 @@ virNodeDevCapScsiHostParseXML(virConnectPtr conn,
+ xmlXPathContextPtr ctxt,
+ virNodeDeviceDefPtr def,
+ xmlNodePtr node,
+- union _virNodeDevCapData *data)
++ union _virNodeDevCapData *data,
++ int create)
+ {
+- xmlNodePtr orignode;
+- int ret = -1;
++ xmlNodePtr orignode, *nodes = NULL;
++ int ret = -1, n = 0, i;
++ char *type = NULL;
+
+ orignode = ctxt->node;
+ ctxt->node = node;
+
+- if (virNodeDevCapsDefParseULong(conn, "number(./host[1])", ctxt,
++ if (create == EXISTING_DEVICE &&
++ virNodeDevCapsDefParseULong(conn, "number(./host[1])", ctxt,
+ &data->scsi_host.host, def,
+ _("no SCSI host ID supplied for
'%s'"),
+- _("invalid SCSI host ID supplied for
'%s'")) < 0)
++ _("invalid SCSI host ID supplied for
'%s'")) < 0) {
+ goto out;
++ }
++
++ if ((n = virXPathNodeSet(conn, "./capability", ctxt, &nodes)) < 0)
{
++ virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++ _("error parsing SCSI host capabilities for
'%s'"),
++ def->name);
++ goto out;
++ }
++
++ for (i = 0 ; i < n ; i++) {
++ type = virXMLPropString(nodes[i], "type");
++
++ if (!type) {
++ virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++ _("missing SCSI host capability type for
'%s'"),
++ def->name);
++ goto out;
++ }
++
++ if (STREQ(type, "vport_ops")) {
++
++ data->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS;
++
++ } else if (STREQ(type, "fc_host")) {
++
++ xmlNodePtr orignode2;
++
++ data->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST;
++
++ orignode2 = ctxt->node;
++ ctxt->node = nodes[i];
++
++ if (virNodeDevCapsDefParseString(conn, "string(./wwnn[1])",
++ ctxt,
++ &data->scsi_host.wwnn,
++ def,
++ _("no WWNN supplied for
'%s'")) < 0) {
++ goto out;
++ }
++
++ if (virNodeDevCapsDefParseString(conn, "string(./wwpn[1])",
++ ctxt,
++ &data->scsi_host.wwpn,
++ def,
++ _("no WWPN supplied for
'%s'")) < 0) {
++ goto out;
++ }
++
++ ctxt->node = orignode2;
++
++ } else {
++ virNodeDeviceReportError(conn, VIR_ERR_INTERNAL_ERROR,
++ _("unknown SCSI host capability type
'%s' for '%s'"),
++ type, def->name);
++ goto out;
++ }
++
++ VIR_FREE(type);
++ }
+
+ ret = 0;
++
+ out:
++ VIR_FREE(type);
+ ctxt->node = orignode;
+ return ret;
+ }
+
++
+ static int
+ virNodeDevCapNetParseXML(virConnectPtr conn,
+ xmlXPathContextPtr ctxt,
+@@ -848,7 +950,8 @@ static virNodeDevCapsDefPtr
+ virNodeDevCapsDefParseXML(virConnectPtr conn,
+ xmlXPathContextPtr ctxt,
+ virNodeDeviceDefPtr def,
+- xmlNodePtr node)
++ xmlNodePtr node,
++ int create)
+ {
+ virNodeDevCapsDefPtr caps;
+ char *tmp;
+@@ -892,7 +995,7 @@ virNodeDevCapsDefParseXML(virConnectPtr conn,
+ ret = virNodeDevCapNetParseXML(conn, ctxt, def, node, &caps->data);
+ break;
+ case VIR_NODE_DEV_CAP_SCSI_HOST:
+- ret = virNodeDevCapScsiHostParseXML(conn, ctxt, def, node, &caps->data);
++ ret = virNodeDevCapScsiHostParseXML(conn, ctxt, def, node, &caps->data,
create);
+ break;
+ case VIR_NODE_DEV_CAP_SCSI:
+ ret = virNodeDevCapScsiParseXML(conn, ctxt, def, node, &caps->data);
+@@ -918,7 +1021,7 @@ error:
+ }
+
+ static virNodeDeviceDefPtr
+-virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt)
++virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr ctxt, int create)
+ {
+ virNodeDeviceDefPtr def;
+ virNodeDevCapsDefPtr *next_cap;
+@@ -931,7 +1034,12 @@ virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr
ctxt)
+ }
+
+ /* Extract device name */
+- def->name = virXPathString(conn, "string(./name[1])", ctxt);
++ if (create == EXISTING_DEVICE) {
++ def->name = virXPathString(conn, "string(./name[1])", ctxt);
++ } else {
++ def->name = strdup("new device");
++ }
++
+ if (!def->name) {
+ virNodeDeviceReportError(conn, VIR_ERR_NO_NAME, NULL);
+ goto error;
+@@ -951,7 +1059,7 @@ virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr
ctxt)
+
+ next_cap = &def->caps;
+ for (i = 0 ; i < n ; i++) {
+- *next_cap = virNodeDevCapsDefParseXML(conn, ctxt, def, nodes[i]);
++ *next_cap = virNodeDevCapsDefParseXML(conn, ctxt, def, nodes[i], create);
+ if (!*next_cap) {
+ VIR_FREE(nodes);
+ goto error;
+@@ -969,7 +1077,7 @@ virNodeDeviceDefParseXML(virConnectPtr conn, xmlXPathContextPtr
ctxt)
+ }
+
+ static virNodeDeviceDefPtr
+-virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root)
++virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml, xmlNodePtr root, int
create)
+ {
+ xmlXPathContextPtr ctxt = NULL;
+ virNodeDeviceDefPtr def = NULL;
+@@ -987,7 +1095,7 @@ virNodeDeviceDefParseNode(virConnectPtr conn, xmlDocPtr xml,
xmlNodePtr root)
+ }
+
+ ctxt->node = root;
+- def = virNodeDeviceDefParseXML(conn, ctxt);
++ def = virNodeDeviceDefParseXML(conn, ctxt, create);
+
+ cleanup:
+ xmlXPathFreeContext(ctxt);
+@@ -1015,7 +1123,7 @@ catchXMLError(void *ctx, const char *msg ATTRIBUTE_UNUSED, ...)
+ }
+
+ virNodeDeviceDefPtr
+-virNodeDeviceDefParseString(virConnectPtr conn, const char *str)
++virNodeDeviceDefParseString(virConnectPtr conn, const char *str, int create)
+ {
+ xmlParserCtxtPtr pctxt;
+ xmlDocPtr xml = NULL;
+@@ -1046,7 +1154,7 @@ virNodeDeviceDefParseString(virConnectPtr conn, const char *str)
+ goto cleanup;
+ }
+
+- def = virNodeDeviceDefParseNode(conn, xml, root);
++ def = virNodeDeviceDefParseNode(conn, xml, root, create);
+
+ cleanup:
+ xmlFreeParserCtxt(pctxt);
+diff --git a/src/node_device_conf.h b/src/node_device_conf.h
+index 26e5558..62b4e71 100644
+--- a/src/node_device_conf.h
++++ b/src/node_device_conf.h
+@@ -28,6 +28,9 @@
+ #include "util.h"
+ #include "threads.h"
+
++#define CREATE_DEVICE 1
++#define EXISTING_DEVICE 0
++
+ enum virNodeDevCapType {
+ /* Keep in sync with VIR_ENUM_IMPL in node_device_conf.c */
+ VIR_NODE_DEV_CAP_SYSTEM, /* System capability */
+@@ -48,8 +51,16 @@ enum virNodeDevNetCapType {
+ VIR_NODE_DEV_CAP_NET_LAST
+ };
+
++enum virNodeDevHBACapType {
++ /* Keep in sync with VIR_ENUM_IMPL in node_device_conf.c */
++ VIR_NODE_DEV_CAP_HBA_FC_HOST, /* fibre channel HBA */
++ VIR_NODE_DEV_CAP_HBA_VPORT_OPS, /* capable of vport operations */
++ VIR_NODE_DEV_CAP_HBA_LAST
++};
++
+ VIR_ENUM_DECL(virNodeDevCap)
+ VIR_ENUM_DECL(virNodeDevNetCap)
++VIR_ENUM_DECL(virNodeDevHBACap)
+
+ enum virNodeDevStorageCapFlags {
+ VIR_NODE_DEV_CAP_STORAGE_REMOVABLE = (1 << 0),
+@@ -57,6 +68,11 @@ enum virNodeDevStorageCapFlags {
+ VIR_NODE_DEV_CAP_STORAGE_HOTPLUGGABLE = (1 << 2),
+ };
+
++enum virNodeDevScsiHostCapFlags {
++ VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST = (1 << 0),
++ VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS = (1 << 1),
++};
++
+ typedef struct _virNodeDevCapsDef virNodeDevCapsDef;
+ typedef virNodeDevCapsDef *virNodeDevCapsDefPtr;
+ struct _virNodeDevCapsDef {
+@@ -108,6 +124,9 @@ struct _virNodeDevCapsDef {
+ } net;
+ struct {
+ unsigned host;
++ char *wwnn;
++ char *wwpn;
++ unsigned flags;
+ } scsi_host;
+ struct {
+ unsigned host;
+@@ -185,7 +204,8 @@ char *virNodeDeviceDefFormat(virConnectPtr conn,
+ const virNodeDeviceDefPtr def);
+
+ virNodeDeviceDefPtr virNodeDeviceDefParseString(virConnectPtr conn,
+- const char *str);
++ const char *str,
++ int create);
+
+ void virNodeDeviceDefFree(virNodeDeviceDefPtr def);
+
+diff --git a/src/node_device_hal.c b/src/node_device_hal.c
+index b214f60..5927ba1 100644
+--- a/src/node_device_hal.c
++++ b/src/node_device_hal.c
+@@ -28,6 +28,7 @@
+ #include <libhal.h>
+
+ #include "node_device_conf.h"
++#include "node_device_hal.h"
+ #include "virterror_internal.h"
+ #include "driver.h"
+ #include "datatypes.h"
+@@ -37,6 +38,8 @@
+ #include "logging.h"
+ #include "node_device.h"
+
++#define VIR_FROM_THIS VIR_FROM_NODEDEV
++
+ /*
+ * Host device enumeration (HAL implementation)
+ */
+@@ -215,6 +218,8 @@ static int gather_scsi_host_cap(LibHalContext *ctx, const char *udi,
+ union _virNodeDevCapData *d)
+ {
+ (void)get_int_prop(ctx, udi, "scsi_host.host", (int
*)&d->scsi_host.host);
++ (void)check_fc_host(d);
++ (void)check_vport_capable(d);
+ return 0;
+ }
+
+diff --git a/src/node_device_hal.h b/src/node_device_hal.h
+new file mode 100644
+index 0000000..0b4a2ef
+--- /dev/null
++++ b/src/node_device_hal.h
+@@ -0,0 +1,40 @@
++/*
++ * node_device_hal.h: node device enumeration - HAL-based implementation
++ *
++ * Copyright (C) 2009 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
++ *
++ */
++
++#ifndef __VIR_NODE_DEVICE_HAL_H__
++#define __VIR_NODE_DEVICE_HAL_H__
++
++#ifdef __linux__
++
++#define check_fc_host(d) check_fc_host_linux(d)
++int check_fc_host_linux(union _virNodeDevCapData *d);
++
++#define check_vport_capable(d) check_vport_capable_linux(d)
++int check_vport_capable_linux(union _virNodeDevCapData *d);
++
++#else /* __linux__ */
++
++#define check_fc_host(d)
++#define check_vport_capable(d)
++
++#endif /* __linux__ */
++
++#endif /* __VIR_NODE_DEVICE_HAL_H__ */
+diff --git a/src/node_device_hal_linux.c b/src/node_device_hal_linux.c
+new file mode 100644
+index 0000000..1deb6d2
+--- /dev/null
++++ b/src/node_device_hal_linux.c
+@@ -0,0 +1,170 @@
++/*
++ * node_device_hal_linuc.c: Linux specific code to gather device data
++ * not available through HAL.
++ *
++ * Copyright (C) 2009 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
++ *
++ */
++
++#include <config.h>
++
++#include <fcntl.h>
++
++#include "node_device.h"
++#include "node_device_hal.h"
++#include "virterror_internal.h"
++#include "memory.h"
++#include "logging.h"
++
++#define VIR_FROM_THIS VIR_FROM_NODEDEV
++
++#ifdef __linux__
++
++int check_fc_host_linux(union _virNodeDevCapData *d)
++{
++ char *sysfs_path = NULL;
++ char *wwnn_path = NULL;
++ char *wwpn_path = NULL;
++ char *p = NULL;
++ int fd = -1;
++ char buf[64];
++ struct stat st;
++
++ VIR_DEBUG(_("Checking if host%d is an FC HBA"), d->scsi_host.host);
++
++ if (virAsprintf(&sysfs_path, "%s/host%d",
++ LINUX_SYSFS_FC_HOST_PREFIX,
++ d->scsi_host.host) < 0) {
++ virReportOOMError(NULL);
++ goto out;
++ }
++
++ if (stat(sysfs_path, &st) != 0) {
++ /* Not an FC HBA */
++ goto out;
++ }
++
++ d->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_FC_HOST;
++
++ if (virAsprintf(&wwnn_path, "%s/node_name",
++ sysfs_path) < 0) {
++ virReportOOMError(NULL);
++ goto out;
++ }
++
++ if ((fd = open(wwnn_path, O_RDONLY)) < 0) {
++ goto out;
++ }
++
++ memset(buf, 0, sizeof(buf));
++ if (saferead(fd, buf, sizeof(buf)) < 0) {
++ goto out;
++ }
++
++ close(fd);
++ fd = -1;
++
++ p = strstr(buf, "0x");
++ if (p != NULL) {
++ p += strlen("0x");
++ } else {
++ p = buf;
++ }
++
++ d->scsi_host.wwnn = strndup(p, sizeof(buf));
++ if (d->scsi_host.wwnn == NULL) {
++ virReportOOMError(NULL);
++ goto out;
++ }
++
++ p = strchr(d->scsi_host.wwnn, '\n');
++ if (p != NULL) {
++ *p = '\0';
++ }
++
++ if (virAsprintf(&wwpn_path, "%s/port_name",
++ sysfs_path) < 0) {
++ virReportOOMError(NULL);
++ goto out;
++ }
++
++ if ((fd = open(wwpn_path, O_RDONLY)) < 0) {
++ goto out;
++ }
++
++ memset(buf, 0, sizeof(buf));
++ if (saferead(fd, buf, sizeof(buf)) < 0) {
++ goto out;
++ }
++
++ close(fd);
++ fd = -1;
++
++ p = strstr(buf, "0x");
++ if (p != NULL) {
++ p += strlen("0x");
++ } else {
++ p = buf;
++ }
++
++ d->scsi_host.wwpn = strndup(p, sizeof(buf));
++ if (d->scsi_host.wwpn == NULL) {
++ virReportOOMError(NULL);
++ goto out;
++ }
++
++ p = strchr(d->scsi_host.wwpn, '\n');
++ if (p != NULL) {
++ *p = '\0';
++ }
++
++out:
++ if (fd != -1) {
++ close(fd);
++ }
++ VIR_FREE(sysfs_path);
++ VIR_FREE(wwnn_path);
++ VIR_FREE(wwpn_path);
++ return 0;
++}
++
++
++int check_vport_capable_linux(union _virNodeDevCapData *d)
++{
++ char *sysfs_path = NULL;
++ struct stat st;
++
++ if (virAsprintf(&sysfs_path, "%s/host%d/vport_create",
++ LINUX_SYSFS_FC_HOST_PREFIX,
++ d->scsi_host.host) < 0) {
++ virReportOOMError(NULL);
++ goto out;
++ }
++
++ if (stat(sysfs_path, &st) != 0) {
++ /* Not a vport capable HBA */
++ goto out;
++ }
++
++ d->scsi_host.flags |= VIR_NODE_DEV_CAP_FLAG_HBA_VPORT_OPS;
++
++out:
++ VIR_FREE(sysfs_path);
++ return 0;
++}
++
++#endif /* __linux__ */
+diff --git a/src/qemu_driver.c b/src/qemu_driver.c
+index bd60b29..057e97b 100644
+--- a/src/qemu_driver.c
++++ b/src/qemu_driver.c
+@@ -5089,7 +5089,7 @@ qemudNodeDeviceGetPciInfo (virNodeDevicePtr dev,
+ if (!xml)
+ goto out;
+
+- def = virNodeDeviceDefParseString(dev->conn, xml);
++ def = virNodeDeviceDefParseString(dev->conn, xml, EXISTING_DEVICE);
+ if (!def)
+ goto out;
+
+diff --git a/src/storage_backend.c b/src/storage_backend.c
+index b154140..74759cf 100644
+--- a/src/storage_backend.c
++++ b/src/storage_backend.c
+@@ -46,6 +46,7 @@
+ #include "virterror_internal.h"
+ #include "util.h"
+ #include "memory.h"
++#include "node_device.h"
+
+ #include "storage_backend.h"
+
+@@ -245,30 +246,11 @@ virStorageBackendUpdateVolTargetInfoFD(virConnectPtr conn,
+ return 0;
+ }
+
+-#if defined(UDEVADM) || defined(UDEVSETTLE)
+ void virStorageBackendWaitForDevices(virConnectPtr conn)
+ {
+-#ifdef UDEVADM
+- const char *const settleprog[] = { UDEVADM, "settle", NULL };
+-#else
+- const char *const settleprog[] = { UDEVSETTLE, NULL };
+-#endif
+- int exitstatus;
+-
+- if (access(settleprog[0], X_OK) != 0)
+- return;
+-
+- /*
+- * NOTE: we ignore errors here; this is just to make sure that any device
+- * nodes that are being created finish before we try to scan them.
+- * If this fails for any reason, we still have the backup of polling for
+- * 5 seconds for device nodes.
+- */
+- virRun(conn, settleprog, &exitstatus);
++ virNodeDeviceWaitForDevices(conn);
++ return;
+ }
+-#else
+-void virStorageBackendWaitForDevices(virConnectPtr conn ATTRIBUTE_UNUSED) {}
+-#endif
+
+ /*
+ * Given a volume path directly in /dev/XXX, iterate over the
+diff --git a/src/xen_unified.c b/src/xen_unified.c
+index e708980..8da4e23 100644
+--- a/src/xen_unified.c
++++ b/src/xen_unified.c
+@@ -1439,7 +1439,7 @@ xenUnifiedNodeDeviceGetPciInfo (virNodeDevicePtr dev,
+ if (!xml)
+ goto out;
+
+- def = virNodeDeviceDefParseString(dev->conn, xml);
++ def = virNodeDeviceDefParseString(dev->conn, xml, EXISTING_DEVICE);
+ if (!def)
+ goto out;
+
+diff --git a/tests/nodedevxml2xmltest.c b/tests/nodedevxml2xmltest.c
+index 29cdb9e..7621212 100644
+--- a/tests/nodedevxml2xmltest.c
++++ b/tests/nodedevxml2xmltest.c
+@@ -29,7 +29,7 @@ static int testCompareXMLToXMLFiles(const char *xml) {
+ if (virtTestLoadFile(xml, &xmlPtr, MAX_FILE) < 0)
+ goto fail;
+
+- if (!(dev = virNodeDeviceDefParseString(NULL, xmlData)))
++ if (!(dev = virNodeDeviceDefParseString(NULL, xmlData, EXISTING_DEVICE)))
+ goto fail;
+
+ if (!(actual = virNodeDeviceDefFormat(NULL, dev)))
+--
+1.6.0.6
+
diff --git a/docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
b/docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
new file mode 100644
index 0000000..55b758b
--- /dev/null
+++ b/docs/api_extension/0008-Step-8-of-8-Add-virsh-support.patch
@@ -0,0 +1,133 @@
+From 193cc4abbb6c2fc5557d3699f86ff0103d5a21ef Mon Sep 17 00:00:00 2001
+From: David Allan <dallan(a)redhat.com>
+Date: Tue, 19 May 2009 16:47:31 -0400
+Subject: [PATCH 8/8] Step 8 of 8 Add virsh support
+
+---
+ src/virsh.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 1 files changed, 103 insertions(+), 0 deletions(-)
+
+diff --git a/src/virsh.c b/src/virsh.c
+index cb32ede..ab2a2b7 100644
+--- a/src/virsh.c
++++ b/src/virsh.c
+@@ -2962,6 +2962,107 @@ cmdPoolCreate(vshControl *ctl, const vshCmd *cmd)
+
+
+ /*
++ * "nodedev-create" command
++ */
++static const vshCmdInfo info_node_device_create[] = {
++ {"help", gettext_noop("create a device defined "
++ "by an XML file on the node")},
++ {"desc", gettext_noop("Create a device on the node. Note that this
"
++ "command creates devices on the physical host "
++ "that can then be assigned to a virtual
machine.")},
++ {NULL, NULL}
++};
++
++static const vshCmdOptDef opts_node_device_create[] = {
++ {"file", VSH_OT_DATA, VSH_OFLAG_REQ,
++ gettext_noop("file containing an XML description of the device")},
++ {NULL, 0, 0, NULL}
++};
++
++static int
++cmdNodeDeviceCreate(vshControl *ctl, const vshCmd *cmd)
++{
++ virNodeDevicePtr dev = NULL;
++ char *from;
++ int found = 0;
++ int ret = TRUE;
++ char *buffer;
++
++ if (!vshConnectionUsability(ctl, ctl->conn, TRUE))
++ return FALSE;
++
++ from = vshCommandOptString(cmd, "file", &found);
++ if (!found) {
++ return FALSE;
++ }
++
++ if (virFileReadAll(from, VIRSH_MAX_XML_FILE, &buffer) < 0) {
++ return FALSE;
++ }
++
++ dev = virNodeDeviceCreateXML(ctl->conn, buffer, 0);
++ free (buffer);
++
++ if (dev != NULL) {
++ vshPrint(ctl, _("Node device %s created from %s\n"),
++ virNodeDeviceGetName(dev), from);
++ } else {
++ vshError(ctl, FALSE, _("Failed to create node device from %s"),
from);
++ ret = FALSE;
++ }
++
++ return ret;
++}
++
++
++/*
++ * "nodedev-destroy" command
++ */
++static const vshCmdInfo info_node_device_destroy[] = {
++ {"help", gettext_noop("destroy a device on the node")},
++ {"desc", gettext_noop("Destroy a device on the node. Note that this
"
++ "command destroys devices on the physical host ")},
++ {NULL, NULL}
++};
++
++static const vshCmdOptDef opts_node_device_destroy[] = {
++ {"name", VSH_OT_DATA, VSH_OFLAG_REQ,
++ gettext_noop("name of the device to be destroyed")},
++ {NULL, 0, 0, NULL}
++};
++
++static int
++cmdNodeDeviceDestroy(vshControl *ctl, const vshCmd *cmd)
++{
++ virNodeDevicePtr dev = NULL;
++ int ret = TRUE;
++ int found = 0;
++ char *name;
++
++ if (!vshConnectionUsability(ctl, ctl->conn, TRUE)) {
++ return FALSE;
++ }
++
++ name = vshCommandOptString(cmd, "name", &found);
++ if (!found) {
++ return FALSE;
++ }
++
++ dev = virNodeDeviceLookupByName(ctl->conn, name);
++
++ if (virNodeDeviceDestroy(dev) == 0) {
++ vshPrint(ctl, _("Destroyed node device '%s'\n"), name);
++ } else {
++ vshError(ctl, FALSE, _("Failed to destroy node device '%s'"),
name);
++ ret = FALSE;
++ }
++
++ virNodeDeviceFree(dev);
++ return ret;
++}
++
++
++/*
+ * XML Building helper for pool-define-as and pool-create-as
+ */
+ static const vshCmdOptDef opts_pool_X_as[] = {
+@@ -5895,6 +5996,8 @@ static const vshCmdDef commands[] = {
+ {"nodedev-dettach", cmdNodeDeviceDettach, opts_node_device_dettach,
info_node_device_dettach},
+ {"nodedev-reattach", cmdNodeDeviceReAttach, opts_node_device_reattach,
info_node_device_reattach},
+ {"nodedev-reset", cmdNodeDeviceReset, opts_node_device_reset,
info_node_device_reset},
++ {"nodedev-create", cmdNodeDeviceCreate, opts_node_device_create,
info_node_device_create},
++ {"nodedev-destroy", cmdNodeDeviceDestroy, opts_node_device_destroy,
info_node_device_destroy},
+
+ {"pool-autostart", cmdPoolAutostart, opts_pool_autostart,
info_pool_autostart},
+ {"pool-build", cmdPoolBuild, opts_pool_build, info_pool_build},
+--
+1.6.0.6
+
diff --git a/docs/api_extension/api_extension.html
b/docs/api_extension/api_extension.html
new file mode 100644
index 0000000..28250fb
--- /dev/null
+++ b/docs/api_extension/api_extension.html
@@ -0,0 +1,264 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+"http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<style type="text/css">
+h1 {text-align:center}
+p {margin-left:10px}
+p.filename {font-family:courier}
+ol.steps li {font-size:larger;font-weight:bold}
+ol.steps p {margin-left:-30px;font-size:smaller;font-weight:normal}
+ol.steps p.stepname {margin-left:10px;font-weight:bold}
+ol.ordinarylist li {margin-left:10px;font-size:smaller;font-weight:normal}
+</style>
+<title>Implementing a new API in Libvirt</title>
+</head>
+
+<body>
+<h1>Implementing a new API in Libvirt</h1>
+
+<p>This document walks you through the process of implementing a new
+API in libvirt. It uses as an example the addition of the node device
+create and destroy APIs.</p>
+
+<p>Before you begin coding, it is critical that you propose your
+changes on the libvirt mailing list and get feedback on your ideas to
+make sure what you're proposing fits with the general direction of the
+project. Even before doing a proof of concept implementation, send an
+email giving an overview of the functionality you think should be
+added to libvirt. Someone may already be working on the feature you
+want. Also, recognize that everything you write is likely to undergo
+significant rework as you discuss it with the other developers, so
+don't wait too long before getting feedback.</p>
+
+<p>Adding a new API to libvirt is not difficult, but there are quite a
+few steps. This document assumes that you are familiar with C
+programming and have checked out the libvirt code from the source code
+repository and successfully built the existing tree. Instructions on
+how to check out and build the code can be found at:</p>
+
+<p><a
href="http://libvirt.org/downloads.html">http://libvirt.org/...
+
+<p>Once you have a working development environment, the steps to create a
+new API are:</p>
+<ol>
+ <li>define the public API</li>
+ <li>define the internal driver API</li>
+ <li>implement the public API</li>
+ <li>define the wire protocol format</li>
+ <li>implement the RPC client</li>
+ <li>implement the server side dispatcher</li>
+ <li>implement the driver methods</li>
+ <li>add virsh support</li>
+</ol>
+<p>It is, of course, possible to implement the pieces in any order, but
+if the development tasks are completed in the order listed, the code
+will compile after each step. Given the number of changes required,
+verification after each step is highly recommended.</p>
+
+<p>Submit new code in the form shown in the example code: one patch
+per step. That's not to say submit patches before you have working
+functionality--get the whole thing working and make sure you're happy
+with it. Then use git or some other version control system that lets
+you rewrite your commit history and break patches into pieces so you
+don't drop a big blob of code on the mailing list at one go. For
+example, I didn't follow my own advice when I originally submitted the
+example code to the libvirt list but rather submitted it in several
+large chunks. I've used git's ability to rewrite my commit history to
+break the code apart into the example patches shown.</p>
+
+<p>Don't mix anything else into the patches you submit. The patches
+should be the minimal changes required to implement the functionality
+you're adding. If you notice a bug in unrelated code (i.e., code you
+don't have to touch to implement your API change) during development,
+create a patch that just addresses that bug and submit it
+separately.</p>
+
+<p>With that said, let's begin.</p>
+
+<ol class="steps">
+<li>
+<p class="stepname">DEFINING THE PUBLIC API</p>
+
+<p>The first task is to define the public API and add it to:</p>
+
+<p class="filename">include/libvirt/libvirt.h.in</p>
+
+<p>This task is in many ways the most important to get right, since once
+the API has been committed to the repository, it's libvirt's policy
+never to change it. Mistakes in the implementation are bugs that you
+can fix. Make a mistake in the API definition and you're stuck with
+it, so think carefully about the interface and don't be afraid to
+rework it as you go through the process of implementing it.</p>
+
+<p>Once you have defined the API, you have to add the symbol names to:</p>
+
+<p class="filename">src/libvirt_public.syms</p>
+
+<p class="example">See <a
href="0001-Step-1-of-8-Define-the-public-API.patch">0001-Step-1-of-8-Define-the-public-API.patch</a>
for example code.</p>
+</li>
+<li>
+<p class="stepname">DEFINING THE INTERNAL API</p>
+
+<p>Each public API call is associated with a driver, such as a host
+virtualization driver, a network virtualization driver, a storage
+virtualization driver, a state driver, or a device monitor. Adding
+the internal API is ordinarily a matter of adding a new member to the
+struct representing one of these drivers.</p>
+
+<p>Of course, it's possible that the new API will involve the creation of
+an entire new driver type, in which case the changes will include the
+creation of a new struct type to represent the new driver type.</p>
+
+<p>The driver structs are defined in:</p>
+
+<p class="filename">src/driver.h</p>
+
+<p>To define the internal API, first typedef the driver function
+prototype and then add a new field for it to the relevant driver
+struct.</p>
+
+<p class="example">See <a
href="0002-Step-2-of-8-Define-the-internal-driver-API.patch">0002-Step-2-of-8-Define-the-internal-driver-API.patch</a></p>
+</li>
+<li>
+<p class="stepname">IMPLEMENTING THE PUBLIC API</p>
+
+<p>Implementing the public API is largely a formality in which we wire up
+public API to the internal driver API. The public API implementation
+takes care of some basic validity checks before passing control to the
+driver implementation. In RFC 2119 vocabulary, this function:</p>
+<ol class="ordinarylist">
+ <li>SHOULD log a message with VIR_DEBUG() indicating that it is
+ being called and its parameters;</li>
+ <li>MUST call virResetLastError();</li>
+ <li>SHOULD confirm that the connection is valid with
+ VIR_IS_CONNECT(conn);</li>
+ <li>If the API requires a connection with write privileges, SHOULD
+ confirm that the connection flags do not indicate that the
+ connection is read-only;</li>
+ <li>SHOULD do basic validation of the parameters that are being
+ passed in;</li>
+ <li>MUST confirm that the driver for this connection exists and that
+ it implements this function;</li>
+ <li>MUST call the internal API;</li>
+ <li>SHOULD log a message with VIR_DEBUG() indicating that it is
+ returning, its return value, and status.</li>
+ <li>MUST return status to the caller.</li>
+</ol>
+
+<p>The public API calls are implemented in:</p>
+
+<p class="filename">src/libvirt.c</p>
+
+<p class="example">See <a
href="0003-Step-3-of-8-Implement-the-public-API.patch">0003-Step-3-of-8-Implement-the-public-API.patch</a></p>
+</li>
+<li>
+<p class="stepname">DEFINING THE WIRE PROTOCOL FORMAT</p>
+
+<p>Defining the wire protocol is essentially a straightforward exercise
+which is probably most easily understood by referring to the existing
+remote protocol wire format definitions and the example patch. It
+involves making two additions to:</p>
+
+<p class="filename">qemud/remote_protocol.x</p>
+
+<p>First, create two new structs for each new function that you're adding
+to the API. One struct describes the parameters to be passed to the
+remote function, and a second struct describes the value returned by
+the remote function. The one exception to this rule is that functions
+that return only integer status do not require a struct for returned
+data.</p>
+
+<p>Second, add values to the remote_procedure enum for each new function
+added to the API.</p>
+
+<p class="example">See <a
href="0004-Step-4-of-8-Define-the-wire-protocol-format.patch">0004-Step-4-of-8-Define-the-wire-protocol-format.patch</a></p>
+
+<p>Once these changes are in place, it's necessary to run 'make
rpcgen'
+in the qemud directory to create the .c and .h files required by the
+remote protocol code.</p>
+</li>
+<li>
+<p class="stepname">IMPLEMENT THE RPC CLIENT</p>
+
+<p>Implementing the RPC client is also relatively mechanical, so refer to
+the exising code and example patch for guidance. The RPC client uses
+the rpcgen generated .h files. The remote method calls go in:</p>
+
+<p class="filename">src/remote_internal.c</p>
+
+<p>Each remote method invocation does the following:</p>
+
+<ol class="ordinarylist">
+ <li>locks the remote driver;</li>
+ <li>sets up the method arguments;</li>
+ <li>invokes the remote function;</li>
+ <li>checks the return value, if necessary;</li>
+ <li>extracts any returned data;</li>
+ <li>frees any returned data;</li>
+ <li>unlocks the remote driver.</li>
+</ol>
+
+<p>Once you have created the remote method calls, you have to add fields
+for them to the driver structs for the appropriate remote driver.</p>
+
+<p class="example">See <a
href="0005-Step-5-of-8-Implement-the-RPC-client.patch">0005-Step-5-of-8-Implement-the-RPC-client.patch</a></p>
+</li>
+<li>
+<p class=stepname>IMPLEMENTING THE SERVER SIDE DISPATCHER</p>
+
+<p>Implementing the server side of the remote function calls is simply a
+matter of deserializing the parameters passed in from the remote
+caller and passing them to the corresponding internal API function.
+The server side dispatchers are implemented in:</p>
+
+<p class="filename">qemud/remote.c</p>
+
+<p>Again, this step uses the .h files generated by make rpcgen.</p>
+
+<p class="example">See <a
href="0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch">0006-Step-6-of-8-Implement-the-server-side-dispatcher.patch</a></p>
+</li>
+<li>
+<p class=stepname>IMPLEMENT THE DRIVER METHODS</p>
+
+<p>So, after all that, we get to the fun part. All functionality in
+libvirt is implemented inside a driver. Thus, here is where you
+implement whatever functionality you're adding to libvirt. You'll
+either need to add additional files to the src directory or extend
+files that are already there, depending on what functionality you're
+adding.</p>
+
+<p>In the example code, the extension is only an additional two function
+calls in the node device API, so most of the new code is additions to
+existing files. The only new files are there for multi-platform
+implementation convenience, as some of the new code is Linux specific.</p>
+
+<p>The example code is probably uninteresting unless you're concerned
+with libvirt storage, but I've included it here to show how new files
+are added to the build environment.</p>
+
+<p class="example">See <a
href="0007-Step-7-of-8-Implement-the-driver-methods.patch">0007-Step-7-of-8-Implement-the-driver-methods.patch</a></p>
+</li>
+<li>
+<p class=stepname>IMPLEMENT VIRSH SUPPORT</p>
+
+<p>Once you have the new functionality in place, the easiest way to test
+it and also to provide it to end users is to implement support for it
+in virsh. </p>
+
+<p>A virsh command is composed of a few pieces of code. You need to
+define an array of vshCmdInfo structs for each new command that
+contain the help text and the command description text. You also need
+an array of vshCmdOptDef structs to describe the command options.
+Once you have those pieces of data in place you can write the function
+implementing the virsh command. Finally, you need to add the new
+command to the commands[] array.</p>
+
+<p class="example">See <a
href="0008-Step-8-of-8-Add-virsh-support.patch">0008-Step-8-of-8-Add-virsh-support.patch</a></p>
+</li>
+</ol>
+
+<p>Once you have working functionality, run make check and make
+syntax-check before generating patches.</p>
+</body>
+</html>
diff --git a/docs/sitemap.html.in b/docs/sitemap.html.in
index 00328e1..b850a0b 100644
--- a/docs/sitemap.html.in
+++ b/docs/sitemap.html.in
@@ -65,6 +65,10 @@
<span>The libvirt API concepts</span>
</li>
<li>
+ <a
href="api_extension/api_extension.html">Internals</a>
+ <span>Extending the libvirt APIs</span>
+ </li>
+ <li>
<a href="archdomain.html">Domains</a>
<span>Managing virtual machines</span>
</li>
--
1.6.0.6