| #include <stdio.h> |
| #include <limits.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <libxml/parser.h> |
| #include <libxml/tree.h> |
| #include <libxml/xmlwriter.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include "snapshot.h" |
| #include "vzerror.h" |
| #include "list.h" |
| #include "logger.h" |
| |
| struct xml_node_param { |
| list_elem_t list; |
| void *p; |
| }; |
| |
| static void free_node_list(list_head_t *head) |
| { |
| struct xml_node_param *tmp, *it; |
| |
| list_for_each_safe(it, tmp, head, list) { |
| list_del(&it->list); |
| free(it); |
| } |
| } |
| |
| static struct xml_node_param *add_xml_node(list_head_t *head, void *node, int tail) |
| { |
| struct xml_node_param *p; |
| |
| p = malloc(sizeof(struct xml_node_param)); |
| if (p == NULL) { |
| logger(-1, ENOMEM, "malloc"); |
| return NULL; |
| } |
| p->p = node; |
| |
| if (tail) |
| list_add_tail(&p->list, head); |
| else |
| list_add(&p->list, head); |
| return p; |
| } |
| |
| static xmlNodePtr find_child_node(xmlNode *cur_node, const char *elem) |
| { |
| xmlNodePtr child; |
| |
| for (child = cur_node->xmlChildrenNode; child != NULL; child = child->next) { |
| if (!xmlStrcmp(child->name, (const xmlChar *) elem) && |
| child->type == XML_ELEMENT_NODE) |
| { |
| return child; |
| } |
| } |
| return NULL; |
| } |
| |
| static xmlNodePtr seek(xmlNodePtr root, const char *elem) |
| { |
| xmlNodePtr childNode = root; |
| const char *path, *p; |
| char nodename[128]; |
| int last = 0; |
| |
| path = elem; |
| if (path[0] == '/') |
| path++; |
| if (path[0] == 0) |
| return NULL; |
| while (!last) { |
| if ((p = strchr(path, '/')) == NULL) { |
| p = path + strlen(path); |
| last = 1; |
| } |
| snprintf(nodename, p - path + 1, "%s", path); |
| childNode = find_child_node(childNode, nodename); |
| if (childNode == NULL) |
| return NULL; |
| path = ++p; |
| } |
| return childNode; |
| } |
| |
| static const char *get_element_txt(xmlNode *node) |
| { |
| xmlNode *child; |
| |
| for (child = node->xmlChildrenNode; child; child = child->next) { |
| if (child->type == XML_TEXT_NODE || |
| child->type == XML_CDATA_SECTION_NODE) |
| return (const char*)child->content; |
| } |
| return NULL; |
| } |
| |
| static int add_child_nodes(struct vzctl_snapshot_tree *tree, list_head_t *pool, |
| xmlNode *cur_node, const char *parent_guid) |
| { |
| xmlNode *node; |
| xmlChar *guid = NULL; |
| xmlChar *val = NULL; |
| const char *name = NULL; |
| const char *date = NULL; |
| const char *desc = NULL; |
| int ret; |
| int current; |
| |
| cur_node = seek(cur_node, "SavedStateItem"); |
| if (cur_node == NULL) |
| return 0; |
| |
| ret = VZ_RESOURCE_ERROR; |
| for (; cur_node; cur_node = cur_node->next) { |
| if (cur_node->type != XML_ELEMENT_NODE) |
| continue; |
| guid = xmlGetProp(cur_node, BAD_CAST "guid"); |
| if (guid == NULL) { |
| logger(-1, 0, "Invalid snapshot file format: no guid attribute"); |
| goto err; |
| } |
| current = 0; |
| val = xmlGetProp(cur_node, BAD_CAST "current"); |
| if (val != NULL) { |
| current = (strcasecmp((const char *)val, "yes") == 0) ? 1 : 0; |
| free(val); |
| } |
| name = NULL; |
| node = seek(cur_node, "Name"); |
| if (node != NULL) |
| name = get_element_txt(node); |
| date = NULL; |
| node = seek(cur_node, "DateTime"); |
| if (node != NULL) |
| date = get_element_txt(node); |
| desc = NULL; |
| node = seek(cur_node, "Description"); |
| if (node != NULL) |
| desc = get_element_txt(node); |
| |
| if (vzctl_add_snapshot_tree_entry(tree, current, (const char *) guid, parent_guid, |
| name, date, desc)) |
| goto err; |
| if (add_xml_node(pool, cur_node, 1) == NULL) |
| goto err; |
| free(guid); |
| guid = NULL; |
| } |
| ret = 0; |
| err: |
| free(guid); |
| return ret; |
| } |
| |
| static int parse_xml(const char *basedir, xmlNode *root_node, struct vzctl_snapshot_tree *tree) |
| { |
| xmlNode *cur_node ; |
| xmlChar *guid; |
| list_head_t pool; |
| struct xml_node_param *tmp, *it; |
| int ret = 0; |
| |
| list_head_init(&pool); |
| cur_node = seek(root_node, "/SavedStateItem"); |
| if (cur_node == NULL) |
| return 0; |
| if (add_xml_node(&pool, cur_node, 1) == NULL) |
| return VZ_RESOURCE_ERROR; |
| while (!list_empty(&pool)) { |
| list_for_each_safe(it, tmp, &pool, list) { |
| xmlNode *cur_node = (xmlNode*) it->p; |
| |
| guid = xmlGetProp(cur_node, BAD_CAST "guid"); |
| if (guid == NULL) { |
| logger(-1, 0, "Invalid snapshot file format: no guid attribute"); |
| ret = -1; |
| break; |
| } |
| ret = add_child_nodes(tree, &pool, cur_node, (const char *)guid); |
| free(guid); |
| if (ret) |
| break; |
| list_del(&it->list); |
| free(it); |
| } |
| } |
| free_node_list(&pool); |
| return ret; |
| } |
| |
| static int update_child_by_guid(struct vzctl_snapshot_tree *tree, list_head_t *head, const char *guid) |
| { |
| int i, cnt = 0; |
| struct xml_node_param *it; |
| list_head_t childs; |
| |
| list_head_init(&childs); |
| for (i = 0; i < tree->nsnapshots; i++) { |
| if (strcmp(tree->snapshots[i]->parent_guid, guid) != 0) |
| continue; |
| if (add_xml_node(&childs, tree->snapshots[i]->guid, 1) == NULL) |
| return -1; |
| cnt++; |
| } |
| // add on top |
| list_for_each_prev(it, &childs, list) { |
| if (add_xml_node(head, it->p, 0) == NULL) |
| return -1; |
| } |
| free_node_list(&childs); |
| return cnt; |
| } |
| |
| #define WRITE_ELEMENT(name, data) \ |
| if ((rc = xmlTextWriterWriteElement(writer, BAD_CAST name, \ |
| BAD_CAST (data ? data : ""))) < 0) { \ |
| return vzctl_err(-1, 0, "WriteElement %s rc=%d\n", name, rc); \ |
| } |
| |
| static int write_SavedStateItem(xmlTextWriterPtr writer, struct vzctl_snapshot_data *snapshot) |
| { |
| int rc; |
| |
| rc = xmlTextWriterStartElement(writer, BAD_CAST "SavedStateItem"); |
| if (rc < 0) |
| return vzctl_err(-1, 0, "Error at WriterStartElemen"); |
| rc = xmlTextWriterWriteAttribute(writer, BAD_CAST "guid", BAD_CAST snapshot->guid); |
| if (rc < 0) |
| return vzctl_err(-1, 0, "Error at WriteAttribute"); |
| if (snapshot->current) { |
| rc = xmlTextWriterWriteAttribute(writer, BAD_CAST "current", BAD_CAST "yes"); |
| if (rc < 0) |
| return vzctl_err(-1, 0, "Error at WriteAttribute"); |
| } |
| WRITE_ELEMENT("Name", snapshot->name) |
| WRITE_ELEMENT("DateTime", snapshot->date) |
| WRITE_ELEMENT("Creator", NULL) |
| WRITE_ELEMENT("ScreenShot", NULL) |
| xmlTextWriterStartElement(writer, BAD_CAST "Description"); |
| xmlTextWriterWriteCDATA(writer, BAD_CAST (snapshot->desc ? snapshot->desc : "")); |
| xmlTextWriterEndElement(writer); |
| |
| return 0; |
| } |
| |
| static int is_last_in_subtree(struct vzctl_snapshot_tree *tree, int snap_idx) |
| { |
| int i, max = -1; |
| |
| for (i = 0; i < tree->nsnapshots; i++) { |
| if (strcmp(tree->snapshots[i]->parent_guid, |
| tree->snapshots[snap_idx]->parent_guid) == 0) |
| max = i; |
| } |
| return (max == snap_idx); |
| } |
| |
| static void write_close_tag(xmlTextWriterPtr writer, |
| struct vzctl_snapshot_tree *tree, const char *guid) |
| { |
| int i; |
| |
| do { |
| if (xmlTextWriterEndElement(writer) < 0) |
| vzctl_err(-1, 0, "Error at xmlTextWriterEndElement"); |
| if (strcmp(guid, "") == 0) |
| break; |
| i = vzctl_find_snapshot_by_guid(tree, guid); |
| if (i == -1) |
| break; |
| guid = tree->snapshots[i]->parent_guid; |
| } while (is_last_in_subtree(tree, i)); |
| } |
| |
| int vzctl_store_snapshot_tree(const char *fname, struct vzctl_snapshot_tree *tree) |
| { |
| int i, rc = -1; |
| xmlTextWriterPtr writer = NULL; |
| xmlDocPtr doc = NULL; |
| char tmpfname[PATH_MAX]; |
| struct xml_node_param *it; |
| list_head_t pool; |
| char *guid; |
| |
| logger(0, 0, "Storing %s", fname); |
| list_head_init(&pool); |
| doc = xmlNewDoc(BAD_CAST XML_DEFAULT_VERSION); |
| if (doc == NULL) |
| return vzctl_err(-1, 0, "Error creating the xml document tree"); |
| /* Create a new XmlWriter for DOM tree, with no compression. */ |
| writer = xmlNewTextWriterTree(doc, NULL, 0); |
| if (writer == NULL) { |
| vzctl_err(-1, 0, "Error creating the xml writer"); |
| goto err; |
| } |
| |
| /* Start the document with the xml default for the version, |
| * encoding ISO 8859-1 and the default for the standalone |
| * declaration. */ |
| rc = xmlTextWriterStartDocument(writer, NULL, NULL, NULL); |
| if (rc < 0) { |
| vzctl_err(-1, 0, "Error at xmlTextWriterStartDocument"); |
| goto err; |
| } |
| rc = xmlTextWriterStartElement(writer, BAD_CAST "ParallelsSavedStates"); |
| if (rc < 0) { |
| vzctl_err(-1, 0, "Error at ParallelsSavedStates"); |
| goto err; |
| } |
| if (tree->nsnapshots == 0) { |
| // ParallelsSavedStates |
| xmlTextWriterEndElement(writer); |
| goto out; |
| } |
| // add initial entry |
| if (update_child_by_guid(tree, &pool, "") == -1) { |
| rc = VZ_RESOURCE_ERROR; |
| goto err; |
| } |
| rc = xmlTextWriterStartElement(writer, BAD_CAST "SavedStateItem"); |
| if (rc < 0) { |
| vzctl_err(-1, 0, "Error at ParallelsSavedStates"); |
| goto err; |
| } |
| xmlTextWriterWriteAttribute(writer, BAD_CAST "guid", BAD_CAST ""); |
| if (rc < 0) { |
| vzctl_err(-1, 0, "Error at WriteAttribute"); |
| goto err; |
| } |
| WRITE_ELEMENT("Name", NULL) |
| WRITE_ELEMENT("DateTime", NULL) |
| WRITE_ELEMENT("Creator", NULL) |
| WRITE_ELEMENT("ScreenShot", NULL) |
| WRITE_ELEMENT("Description", NULL) |
| |
| while (!list_empty(&pool)) { |
| list_for_each(it, &pool, list) { |
| struct vzctl_snapshot_data *snapshot; |
| |
| guid = it->p; |
| i = vzctl_find_snapshot_by_guid(tree, guid); |
| if (i == -1) { |
| vzctl_err(-1, 0, "Inconsistent snapshot: no %s found", |
| guid); |
| goto err; |
| } |
| snapshot = tree->snapshots[i]; |
| rc = write_SavedStateItem(writer, snapshot); |
| if (rc) |
| goto err; |
| rc = update_child_by_guid(tree, &pool, guid); |
| if (rc == -1) |
| goto err; |
| else if (rc == 0)// no more child |
| write_close_tag(writer, tree, guid); |
| |
| list_del(&it->list); |
| free(it); |
| break; |
| } |
| } |
| out: |
| // <SavedStateItem guid =""> |
| xmlTextWriterEndElement(writer); |
| // ParallelsSavedStates |
| xmlTextWriterEndElement(writer); |
| |
| xmlFreeTextWriter(writer); |
| writer = NULL; |
| snprintf(tmpfname, sizeof(tmpfname), "%s.tmp", fname); |
| rc = xmlSaveFormatFile(tmpfname, doc, 1); |
| if (rc < 0) { |
| vzctl_err(-1, 0, "Error at xmlSaveFormatFile %s", tmpfname); |
| goto err; |
| } |
| rc = rename(tmpfname, fname); |
| if (rc) { |
| vzctl_err(-1, errno, "Can't rename %s -> %s", |
| tmpfname, fname); |
| unlink(tmpfname); |
| goto err; |
| } |
| rc = 0; |
| err: |
| free_node_list(&pool); |
| |
| if (writer) |
| xmlFreeTextWriter(writer); |
| if (doc) |
| xmlFreeDoc(doc); |
| |
| return rc; |
| } |
| |
| int vzctl_read_snapshot_tree(const char *fname, struct vzctl_snapshot_tree *tree) |
| { |
| int ret; |
| xmlDoc *doc = NULL; |
| xmlNode *root_element = NULL; |
| |
| LIBXML_TEST_VERSION |
| |
| doc = xmlReadFile(fname, NULL, 0); |
| if (doc == NULL) |
| return vzctl_err(VZ_SYSTEM_ERROR, 0, "Error: could not parse file %s", fname); |
| |
| root_element = xmlDocGetRootElement(doc); |
| |
| ret = parse_xml(fname, root_element, tree); |
| |
| xmlFreeDoc(doc); |
| |
| return ret; |
| } |