[PATCH 1/3] conf: Add infrastructure for disk source private data XML

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

 



VM drivers may need to store additional private data to the status XML
so that it can be restored after libvirtd restart. Since not everything
is needed add a callback infrastructure, where VM drivers can add only
stuff they need.

Note that the private data is formatted as a <privateData> sub-element
of the <disk> or <backingStore> <source> sub-element. This is done since
storing it out of band (in the VM private data) would require a complex
matching process to allow to put the data into correct place.
---
 src/conf/domain_conf.c      | 136 ++++++++++++++++++++++++++++++++++----------
 src/conf/domain_conf.h      |  17 +++++-
 src/conf/snapshot_conf.c    |  18 +++---
 src/network/bridge_driver.c |   2 +-
 src/qemu/qemu_domain.c      |   2 +-
 tests/qemublocktest.c       |   4 +-
 tests/virstoragetest.c      |   2 +-
 7 files changed, 136 insertions(+), 45 deletions(-)

diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c
index 66e21c4bdb..9a62bc472c 100644
--- a/src/conf/domain_conf.c
+++ b/src/conf/domain_conf.c
@@ -8553,11 +8553,43 @@ virDomainDiskSourceEncryptionParse(xmlNodePtr node,
 }


+static int
+virDomainDiskSourcePrivateDataParse(xmlXPathContextPtr ctxt,
+                                    virStorageSourcePtr src,
+                                    unsigned int flags,
+                                    virDomainXMLOptionPtr xmlopt)
+{
+    xmlNodePtr saveNode = ctxt->node;
+    xmlNodePtr node;
+    int ret = -1;
+
+    if (!(flags & VIR_DOMAIN_DEF_PARSE_STATUS) ||
+        !xmlopt || !xmlopt->privateData.storageParse)
+        return 0;
+
+    if (!(node = virXPathNode("./privateData", ctxt)))
+        return 0;
+
+    ctxt->node = node;
+
+    if (xmlopt->privateData.storageParse(ctxt, src) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    ctxt->node = saveNode;
+
+    return ret;
+}
+
+
 int
 virDomainDiskSourceParse(xmlNodePtr node,
                          xmlXPathContextPtr ctxt,
                          virStorageSourcePtr src,
-                         unsigned int flags)
+                         unsigned int flags,
+                         virDomainXMLOptionPtr xmlopt)
 {
     int ret = -1;
     xmlNodePtr saveNode = ctxt->node;
@@ -8596,6 +8628,9 @@ virDomainDiskSourceParse(xmlNodePtr node,
     if (virDomainDiskSourceEncryptionParse(node, &src->encryption) < 0)
         goto cleanup;

+    if (virDomainDiskSourcePrivateDataParse(ctxt, src, flags, xmlopt) < 0)
+        goto cleanup;
+
     /* People sometimes pass a bogus '' source path when they mean to omit the
      * source element completely (e.g. CDROM without media). This is just a
      * little compatibility check to help those broken apps */
@@ -8613,7 +8648,8 @@ virDomainDiskSourceParse(xmlNodePtr node,
 static int
 virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt,
                                virStorageSourcePtr src,
-                               unsigned int flags)
+                               unsigned int flags,
+                               virDomainXMLOptionPtr xmlopt)
 {
     virStorageSourcePtr backingStore = NULL;
     xmlNodePtr save_ctxt = ctxt->node;
@@ -8671,8 +8707,8 @@ virDomainDiskBackingStoreParse(xmlXPathContextPtr ctxt,
         goto cleanup;
     }

-    if (virDomainDiskSourceParse(source, ctxt, backingStore, flags) < 0 ||
-        virDomainDiskBackingStoreParse(ctxt, backingStore, flags) < 0)
+    if (virDomainDiskSourceParse(source, ctxt, backingStore, flags, xmlopt) < 0 ||
+        virDomainDiskBackingStoreParse(ctxt, backingStore, flags, xmlopt) < 0)
         goto cleanup;

     VIR_STEAL_PTR(src->backingStore, backingStore);
@@ -8774,7 +8810,8 @@ static int
 virDomainDiskDefMirrorParse(virDomainDiskDefPtr def,
                             xmlNodePtr cur,
                             xmlXPathContextPtr ctxt,
-                            unsigned int flags)
+                            unsigned int flags,
+                            virDomainXMLOptionPtr xmlopt)
 {
     xmlNodePtr mirrorNode;
     char *mirrorFormat = NULL;
@@ -8812,7 +8849,8 @@ virDomainDiskDefMirrorParse(virDomainDiskDefPtr def,
             goto cleanup;
         }

-        if (virDomainDiskSourceParse(mirrorNode, ctxt, def->mirror, flags) < 0)
+        if (virDomainDiskSourceParse(mirrorNode, ctxt, def->mirror,
+                                     flags, xmlopt) < 0)
             goto cleanup;
     } else {
         /* For back-compat reasons, we handle a file name
@@ -9238,7 +9276,7 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt,
         if (!source && virXMLNodeNameEqual(cur, "source")) {
             sourceNode = cur;

-            if (virDomainDiskSourceParse(cur, ctxt, def->src, flags) < 0)
+            if (virDomainDiskSourceParse(cur, ctxt, def->src, flags, xmlopt) < 0)
                 goto error;

             /* If we've already found an <auth> as a child of <disk> and
@@ -9319,7 +9357,7 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt,
         } else if (!def->mirror &&
                    virXMLNodeNameEqual(cur, "mirror") &&
                    !(flags & VIR_DOMAIN_DEF_PARSE_INACTIVE)) {
-            if (virDomainDiskDefMirrorParse(def, cur, ctxt, flags) < 0)
+            if (virDomainDiskDefMirrorParse(def, cur, ctxt, flags, xmlopt) < 0)
                 goto error;
         } else if (!authdef &&
                    virXMLNodeNameEqual(cur, "auth")) {
@@ -9590,7 +9628,7 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt,
     product = NULL;

     if (!(flags & VIR_DOMAIN_DEF_PARSE_DISK_SOURCE)) {
-        if (virDomainDiskBackingStoreParse(ctxt, def->src, flags) < 0)
+        if (virDomainDiskBackingStoreParse(ctxt, def->src, flags, xmlopt) < 0)
             goto error;
     }

@@ -22356,12 +22394,43 @@ virDomainDiskSourceFormatNetwork(virBufferPtr attrBuf,
 }


+static int
+virDomainDiskSourceFormatPrivateData(virBufferPtr buf,
+                                     virStorageSourcePtr src,
+                                     unsigned int flags,
+                                     virDomainXMLOptionPtr xmlopt)
+{
+    virBuffer childBuf = VIR_BUFFER_INITIALIZER;
+    int ret = -1;
+
+    if (!(flags & VIR_DOMAIN_DEF_FORMAT_STATUS) ||
+        !xmlopt || !xmlopt->privateData.storageFormat)
+        return 0;
+
+    virBufferSetChildIndent(&childBuf, buf);
+
+    if (xmlopt->privateData.storageFormat(src, &childBuf) < 0)
+        goto cleanup;
+
+    if (virXMLFormatElement(buf, "privateData", NULL, &childBuf) < 0)
+        goto cleanup;
+
+    ret = 0;
+
+ cleanup:
+    virBufferFreeAndReset(&childBuf);
+
+    return ret;
+}
+
+
 static int
 virDomainDiskSourceFormatInternal(virBufferPtr buf,
                                   virStorageSourcePtr src,
                                   int policy,
                                   unsigned int flags,
-                                  bool skipSeclabels)
+                                  bool skipSeclabels,
+                                  virDomainXMLOptionPtr xmlopt)
 {
     const char *startupPolicy = NULL;
     virBuffer attrBuf = VIR_BUFFER_INITIALIZER;
@@ -22443,6 +22512,9 @@ virDomainDiskSourceFormatInternal(virBufferPtr buf,
             virStorageEncryptionFormat(&childBuf, src->encryption) < 0)
             return -1;

+        if (virDomainDiskSourceFormatPrivateData(&childBuf, src, flags, xmlopt) < 0)
+            return -1;
+
         if (virXMLFormatElement(buf, "source", &attrBuf, &childBuf) < 0)
             goto error;
     }
@@ -22460,15 +22532,18 @@ int
 virDomainDiskSourceFormat(virBufferPtr buf,
                           virStorageSourcePtr src,
                           int policy,
-                          unsigned int flags)
+                          unsigned int flags,
+                          virDomainXMLOptionPtr xmlopt)
 {
-    return virDomainDiskSourceFormatInternal(buf, src, policy, flags, false);
+    return virDomainDiskSourceFormatInternal(buf, src, policy, flags, false, xmlopt);
 }


 static int
 virDomainDiskBackingStoreFormat(virBufferPtr buf,
-                                virStorageSourcePtr backingStore)
+                                virStorageSourcePtr backingStore,
+                                virDomainXMLOptionPtr xmlopt,
+                                unsigned int flags)
 {
     const char *format;

@@ -22497,9 +22572,9 @@ virDomainDiskBackingStoreFormat(virBufferPtr buf,

     virBufferAsprintf(buf, "<format type='%s'/>\n", format);
     /* We currently don't output seclabels for backing chain element */
-    if (virDomainDiskSourceFormatInternal(buf, backingStore, 0, 0, true) < 0 ||
-        virDomainDiskBackingStoreFormat(buf,
-                                        backingStore->backingStore) < 0)
+    if (virDomainDiskSourceFormatInternal(buf, backingStore, 0, flags, true, xmlopt) < 0 ||
+        virDomainDiskBackingStoreFormat(buf, backingStore->backingStore,
+                                        xmlopt, flags) < 0)
         return -1;

     virBufferAdjustIndent(buf, -2);
@@ -22517,7 +22592,8 @@ virDomainDiskBackingStoreFormat(virBufferPtr buf,
 static int
 virDomainDiskDefFormat(virBufferPtr buf,
                        virDomainDiskDefPtr def,
-                       unsigned int flags)
+                       unsigned int flags,
+                       virDomainXMLOptionPtr xmlopt)
 {
     const char *type = virStorageTypeToString(def->src->type);
     const char *device = virDomainDiskDeviceTypeToString(def->device);
@@ -22630,13 +22706,14 @@ virDomainDiskDefFormat(virBufferPtr buf,
     }

     if (virDomainDiskSourceFormat(buf, def->src, def->startupPolicy,
-                                  flags) < 0)
+                                  flags, xmlopt) < 0)
         return -1;

     /* Don't format backingStore to inactive XMLs until the code for
      * persistent storage of backing chains is ready. */
     if (!(flags & VIR_DOMAIN_DEF_FORMAT_INACTIVE) &&
-        virDomainDiskBackingStoreFormat(buf, def->src->backingStore) < 0)
+        virDomainDiskBackingStoreFormat(buf, def->src->backingStore,
+                                        xmlopt, flags) < 0)
         return -1;

     virBufferEscapeString(buf, "<backenddomain name='%s'/>\n", def->domain_name);
@@ -22673,7 +22750,7 @@ virDomainDiskDefFormat(virBufferPtr buf,
         virBufferAddLit(buf, ">\n");
         virBufferAdjustIndent(buf, 2);
         virBufferEscapeString(buf, "<format type='%s'/>\n", formatStr);
-        if (virDomainDiskSourceFormat(buf, def->mirror, 0, 0) < 0)
+        if (virDomainDiskSourceFormat(buf, def->mirror, 0, 0, xmlopt) < 0)
             return -1;
         virBufferAdjustIndent(buf, -2);
         virBufferAddLit(buf, "</mirror>\n");
@@ -25904,7 +25981,8 @@ int
 virDomainDefFormatInternal(virDomainDefPtr def,
                            virCapsPtr caps,
                            unsigned int flags,
-                           virBufferPtr buf)
+                           virBufferPtr buf,
+                           virDomainXMLOptionPtr xmlopt)
 {
     unsigned char *uuid;
     char uuidstr[VIR_UUID_STRING_BUFLEN];
@@ -25959,10 +26037,10 @@ virDomainDefFormatInternal(virDomainDefPtr def,
          * but no leading indentation before the starting element.
          * Thankfully, libxml maps what looks like globals into
          * thread-local uses, so we are thread-safe.  */
-        xmlIndentTreeOutput = 1;
-        xmlbuf = xmlBufferCreate();
-        if (xmlNodeDump(xmlbuf, def->metadata->doc, def->metadata,
-                        virBufferGetIndent(buf, false) / 2, 1) < 0) {
+            xmlIndentTreeOutput = 1;
+            xmlbuf = xmlBufferCreate();
+            if (xmlNodeDump(xmlbuf, def->metadata->doc, def->metadata,
+                            virBufferGetIndent(buf, false) / 2, 1) < 0) {
             xmlBufferFree(xmlbuf);
             xmlIndentTreeOutput = oldIndentTreeOutput;
             goto error;
@@ -26535,7 +26613,7 @@ virDomainDefFormatInternal(virDomainDefPtr def,
                           def->emulator);

     for (n = 0; n < def->ndisks; n++)
-        if (virDomainDiskDefFormat(buf, def->disks[n], flags) < 0)
+        if (virDomainDiskDefFormat(buf, def->disks[n], flags, xmlopt) < 0)
             goto error;

     for (n = 0; n < def->ncontrollers; n++)
@@ -26722,7 +26800,7 @@ virDomainDefFormat(virDomainDefPtr def, virCapsPtr caps, unsigned int flags)
     virBuffer buf = VIR_BUFFER_INITIALIZER;

     virCheckFlags(VIR_DOMAIN_DEF_FORMAT_COMMON_FLAGS, NULL);
-    if (virDomainDefFormatInternal(def, caps, flags, &buf) < 0)
+    if (virDomainDefFormatInternal(def, caps, flags, &buf, NULL) < 0)
         return NULL;

     return virBufferContentAndReset(&buf);
@@ -26757,7 +26835,7 @@ virDomainObjFormat(virDomainXMLOptionPtr xmlopt,
         xmlopt->privateData.format(&buf, obj) < 0)
         goto error;

-    if (virDomainDefFormatInternal(obj->def, caps, flags, &buf) < 0)
+    if (virDomainDefFormatInternal(obj->def, caps, flags, &buf, xmlopt) < 0)
         goto error;

     virBufferAdjustIndent(&buf, -2);
@@ -27711,7 +27789,7 @@ virDomainDeviceDefCopy(virDomainDeviceDefPtr src,

     switch ((virDomainDeviceType) src->type) {
     case VIR_DOMAIN_DEVICE_DISK:
-        rc = virDomainDiskDefFormat(&buf, src->data.disk, flags);
+        rc = virDomainDiskDefFormat(&buf, src->data.disk, flags, xmlopt);
         break;
     case VIR_DOMAIN_DEVICE_LEASE:
         rc = virDomainLeaseDefFormat(&buf, src->data.lease);
diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h
index 59f250ac96..6f7f96b3dd 100644
--- a/src/conf/domain_conf.h
+++ b/src/conf/domain_conf.h
@@ -2631,6 +2631,12 @@ typedef int (*virDomainXMLPrivateDataParseFunc)(xmlXPathContextPtr,
                                                 virDomainObjPtr,
                                                 virDomainDefParserConfigPtr);

+typedef int (*virDomainXMLPrivateDataStorageSourceParseFunc)(xmlXPathContextPtr ctxt,
+                                                             virStorageSourcePtr src);
+typedef int (*virDomainXMLPrivateDataStorageSourceFormatFunc)(virStorageSourcePtr src,
+                                                              virBufferPtr buf);
+
+
 typedef struct _virDomainXMLPrivateDataCallbacks virDomainXMLPrivateDataCallbacks;
 typedef virDomainXMLPrivateDataCallbacks *virDomainXMLPrivateDataCallbacksPtr;
 struct _virDomainXMLPrivateDataCallbacks {
@@ -2643,6 +2649,8 @@ struct _virDomainXMLPrivateDataCallbacks {
     virDomainXMLPrivateDataNewFunc    chrSourceNew;
     virDomainXMLPrivateDataFormatFunc format;
     virDomainXMLPrivateDataParseFunc  parse;
+    virDomainXMLPrivateDataStorageSourceParseFunc storageParse;
+    virDomainXMLPrivateDataStorageSourceFormatFunc storageFormat;
 };

 typedef bool (*virDomainABIStabilityDomain)(const virDomainDef *src,
@@ -2967,12 +2975,14 @@ char *virDomainObjFormat(virDomainXMLOptionPtr xmlopt,
 int virDomainDefFormatInternal(virDomainDefPtr def,
                                virCapsPtr caps,
                                unsigned int flags,
-                               virBufferPtr buf);
+                               virBufferPtr buf,
+                               virDomainXMLOptionPtr xmlopt);

 int virDomainDiskSourceFormat(virBufferPtr buf,
                               virStorageSourcePtr src,
                               int policy,
-                              unsigned int flags);
+                              unsigned int flags,
+                              virDomainXMLOptionPtr xmlopt);

 int virDomainNetDefFormat(virBufferPtr buf,
                           virDomainNetDefPtr def,
@@ -3021,7 +3031,8 @@ virDomainDiskRemoveByName(virDomainDefPtr def, const char *name);
 int virDomainDiskSourceParse(xmlNodePtr node,
                              xmlXPathContextPtr ctxt,
                              virStorageSourcePtr src,
-                             unsigned int flags);
+                             unsigned int flags,
+                             virDomainXMLOptionPtr xmlopt);

 int virDomainNetFindIdx(virDomainDefPtr def, virDomainNetDefPtr net);
 virDomainNetDefPtr virDomainNetFind(virDomainDefPtr def, const char *device);
diff --git a/src/conf/snapshot_conf.c b/src/conf/snapshot_conf.c
index f0e852c92b..d7b086242b 100644
--- a/src/conf/snapshot_conf.c
+++ b/src/conf/snapshot_conf.c
@@ -110,7 +110,8 @@ static int
 virDomainSnapshotDiskDefParseXML(xmlNodePtr node,
                                  xmlXPathContextPtr ctxt,
                                  virDomainSnapshotDiskDefPtr def,
-                                 unsigned int flags)
+                                 unsigned int flags,
+                                 virDomainXMLOptionPtr xmlopt)
 {
     int ret = -1;
     char *snapshot = NULL;
@@ -155,7 +156,7 @@ virDomainSnapshotDiskDefParseXML(xmlNodePtr node,
     }

     if ((cur = virXPathNode("./source", ctxt)) &&
-        virDomainDiskSourceParse(cur, ctxt, def->src, flags) < 0)
+        virDomainDiskSourceParse(cur, ctxt, def->src, flags, xmlopt) < 0)
         goto cleanup;

     if ((driver = virXPathString("string(./driver/@type)", ctxt))) {
@@ -348,8 +349,8 @@ virDomainSnapshotDefParse(xmlXPathContextPtr ctxt,
             goto cleanup;
         def->ndisks = n;
         for (i = 0; i < def->ndisks; i++) {
-            if (virDomainSnapshotDiskDefParseXML(nodes[i], ctxt,
-                                                 &def->disks[i], flags) < 0)
+            if (virDomainSnapshotDiskDefParseXML(nodes[i], ctxt, &def->disks[i],
+                                                 flags, xmlopt) < 0)
                 goto cleanup;
         }
         VIR_FREE(nodes);
@@ -663,7 +664,8 @@ virDomainSnapshotAlignDisks(virDomainSnapshotDefPtr def,

 static void
 virDomainSnapshotDiskDefFormat(virBufferPtr buf,
-                               virDomainSnapshotDiskDefPtr disk)
+                               virDomainSnapshotDiskDefPtr disk,
+                               virDomainXMLOptionPtr xmlopt)
 {
     int type = disk->src->type;

@@ -686,7 +688,7 @@ virDomainSnapshotDiskDefFormat(virBufferPtr buf,
     if (disk->src->format > 0)
         virBufferEscapeString(buf, "<driver type='%s'/>\n",
                               virStorageFileFormatTypeToString(disk->src->format));
-    virDomainDiskSourceFormat(buf, disk->src, 0, 0);
+    virDomainDiskSourceFormat(buf, disk->src, 0, 0, xmlopt);

     virBufferAdjustIndent(buf, -2);
     virBufferAddLit(buf, "</disk>\n");
@@ -740,13 +742,13 @@ virDomainSnapshotDefFormat(const char *domain_uuid,
         virBufferAddLit(&buf, "<disks>\n");
         virBufferAdjustIndent(&buf, 2);
         for (i = 0; i < def->ndisks; i++)
-            virDomainSnapshotDiskDefFormat(&buf, &def->disks[i]);
+            virDomainSnapshotDiskDefFormat(&buf, &def->disks[i], xmlopt);
         virBufferAdjustIndent(&buf, -2);
         virBufferAddLit(&buf, "</disks>\n");
     }

     if (def->dom) {
-        if (virDomainDefFormatInternal(def->dom, caps, flags, &buf) < 0)
+        if (virDomainDefFormatInternal(def->dom, caps, flags, &buf, xmlopt) < 0)
             goto error;
     } else if (domain_uuid) {
         virBufferAddLit(&buf, "<domain>\n");
diff --git a/src/network/bridge_driver.c b/src/network/bridge_driver.c
index fcaa66df91..334da7a85d 100644
--- a/src/network/bridge_driver.c
+++ b/src/network/bridge_driver.c
@@ -233,7 +233,7 @@ networkRunHook(virNetworkObjPtr obj,
             goto cleanup;
         if (virNetworkDefFormatBuf(&buf, def, 0) < 0)
             goto cleanup;
-        if (dom && virDomainDefFormatInternal(dom, NULL, 0, &buf) < 0)
+        if (dom && virDomainDefFormatInternal(dom, NULL, 0, &buf, NULL) < 0)
             goto cleanup;

         virBufferAdjustIndent(&buf, -2);
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 347fc07425..18c2d2cb8a 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -5325,7 +5325,7 @@ qemuDomainDefFormatBufInternal(virQEMUDriverPtr driver,
  format:
     ret = virDomainDefFormatInternal(def, caps,
                                      virDomainDefFormatConvertXMLFlags(flags),
-                                     buf);
+                                     buf, driver->xmlopt);

  cleanup:
     virDomainDefFree(copy);
diff --git a/tests/qemublocktest.c b/tests/qemublocktest.c
index d1665756ab..e8fac100dd 100644
--- a/tests/qemublocktest.c
+++ b/tests/qemublocktest.c
@@ -57,7 +57,7 @@ testBackingXMLjsonXML(const void *args)
     if (!(xml = virXMLParseStringCtxt(data->xml, "(test storage source XML)", &ctxt)))
         goto cleanup;

-    if (virDomainDiskSourceParse(ctxt->node, ctxt, xmlsrc, 0) < 0) {
+    if (virDomainDiskSourceParse(ctxt->node, ctxt, xmlsrc, 0, NULL) < 0) {
         fprintf(stderr, "failed to parse disk source xml\n");
         goto cleanup;
     }
@@ -83,7 +83,7 @@ testBackingXMLjsonXML(const void *args)
         goto cleanup;
     }

-    if (virDomainDiskSourceFormat(&buf, jsonsrc, 0, 0) < 0 ||
+    if (virDomainDiskSourceFormat(&buf, jsonsrc, 0, 0, NULL) < 0 ||
         !(actualxml = virBufferContentAndReset(&buf))) {
         fprintf(stderr, "failed to format disk source xml\n");
         goto cleanup;
diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c
index 806c5465ce..7a0d4a8260 100644
--- a/tests/virstoragetest.c
+++ b/tests/virstoragetest.c
@@ -693,7 +693,7 @@ testBackingParse(const void *args)
         goto cleanup;
     }

-    if (virDomainDiskSourceFormat(&buf, src, 0, 0) < 0 ||
+    if (virDomainDiskSourceFormat(&buf, src, 0, 0, NULL) < 0 ||
         !(xml = virBufferContentAndReset(&buf))) {
         fprintf(stderr, "failed to format disk source xml\n");
         goto cleanup;
-- 
2.15.0

--
libvir-list mailing list
libvir-list@xxxxxxxxxx
https://www.redhat.com/mailman/listinfo/libvir-list



[Index of Archives]     [Virt Tools]     [Libvirt Users]     [Lib OS Info]     [Fedora Users]     [Fedora Desktop]     [Fedora SELinux]     [Big List of Linux Books]     [Yosemite News]     [KDE Users]     [Fedora Tools]
  Powered by Linux