X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fxml.c;h=3812ce1e19d7926e499a9c230e0bddb9664d02ea;hp=58750a1f1eb4d907a56b2162f18de42900536dfe;hb=HEAD;hpb=3de1ec66f778edda19865482d685bc6f4e17faf7 diff --git a/src/xml.c b/src/xml.c index 58750a1f..d4400ee6 100644 --- a/src/xml.c +++ b/src/xml.c @@ -1,11 +1,9 @@ /* - * xml.c - * - * Deals with the XML information in WIM files. Uses the C library libxml2. + * xml.c - deals with the XML information in WIM files */ /* - * Copyright (C) 2012, 2013 Eric Biggers + * Copyright 2012-2023 Eric Biggers * * This file 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 @@ -18,20 +16,16 @@ * details. * * You should have received a copy of the GNU Lesser General Public License - * along with this file; if not, see http://www.gnu.org/licenses/. + * along with this file; if not, see https://www.gnu.org/licenses/. */ #ifdef HAVE_CONFIG_H # include "config.h" #endif -#include -#include -#include -#include +#include #include -#include "wimlib/assert.h" #include "wimlib/blob_table.h" #include "wimlib/dentry.h" #include "wimlib/encoding.h" @@ -41,1604 +35,1074 @@ #include "wimlib/resource.h" #include "wimlib/timestamp.h" #include "wimlib/xml.h" +#include "wimlib/xmlproc.h" #include "wimlib/write.h" -/* Structures used to form an in-memory representation of the XML data (other - * than the raw parse tree from libxml). */ - -struct windows_version { - u64 major; - u64 minor; - u64 build; - u64 sp_build; - u64 sp_level; -}; - -struct windows_info { - u64 arch; - tchar *product_name; - tchar *edition_id; - tchar *installation_type; - tchar *pkeyconfigversion; - tchar *hal; - tchar *product_type; - tchar *product_suite; - tchar **languages; - tchar *default_language; - size_t num_languages; - tchar *system_root; - bool windows_version_exists; - struct windows_version windows_version; -}; - -struct image_info { - int index; - bool windows_info_exists; - u64 dir_count; - u64 file_count; - u64 total_bytes; - u64 hard_link_bytes; - u64 creation_time; - u64 last_modification_time; - struct windows_info windows_info; - tchar *name; - tchar *description; - tchar *display_name; - tchar *display_description; - tchar *flags; - bool wimboot; - - /* Note: must update clone_image_info() if adding new fields here */ - - struct blob_table *blob_table; /* temporary field */ -}; +/* + * A wrapper around a WIM file's XML document. The XML document contains + * metadata about each image in the WIM file as well as metadata about the WIM + * file itself. + */ +struct wim_xml_info { -/* A struct wim_info structure corresponds to the entire XML data for a WIM file. */ -struct wim_info { - u64 total_bytes; - int num_images; - /* Array of `struct image_info's, one for each image in the WIM that is - * mentioned in the XML data. */ - struct image_info *images; -}; + /* The XML document in tree form */ + struct xml_node *root; -struct xml_string_spec { - const char *name; - size_t offset; -}; + /* A malloc()ed array containing a pointer to the IMAGE element for each + * WIM image. The image with 1-based index 'i' is at index 'i - 1' in + * this array. Note: these pointers are cached values, since they could + * also be found by searching the document. */ + struct xml_node **images; -#define ELEM(STRING_NAME, MEMBER_NAME) \ - {STRING_NAME, offsetof(struct image_info, MEMBER_NAME)} -static const struct xml_string_spec -image_info_xml_string_specs[] = { - ELEM("NAME", name), - ELEM("DESCRIPTION", description), - ELEM("DISPLAYNAME", display_name), - ELEM("DISPLAYDESCRIPTION", display_description), - ELEM("FLAGS", flags), + /* The number of WIM images (the length of 'images') */ + int image_count; }; -#undef ELEM - -#define ELEM(STRING_NAME, MEMBER_NAME) \ - {STRING_NAME, offsetof(struct windows_info, MEMBER_NAME)} -static const struct xml_string_spec -windows_info_xml_string_specs[] = { - ELEM("PRODUCTNAME", product_name), - ELEM("EDITIONID", edition_id), - ELEM("INSTALLATIONTYPE", installation_type), - ELEM("HAL", hal), - ELEM("PRODUCTTYPE", product_type), - ELEM("PRODUCTSUITE", product_suite), -}; -#undef ELEM -u64 -wim_info_get_total_bytes(const struct wim_info *info) +static u64 +parse_number(const tchar *str, int base) { - if (info) - return info->total_bytes; - else - return 0; -} + tchar *end; + unsigned long long v; -u64 -wim_info_get_image_hard_link_bytes(const struct wim_info *info, int image) -{ - if (info) - return info->images[image - 1].hard_link_bytes; - else + if (!str) return 0; -} - -u64 -wim_info_get_image_total_bytes(const struct wim_info *info, int image) -{ - if (info) - return info->images[image - 1].total_bytes; - else + v = tstrtoull(str, &end, base); + if (end == str || *end || v >= UINT64_MAX) return 0; + return v; } -unsigned -wim_info_get_num_images(const struct wim_info *info) +/* + * Retrieve an unsigned integer from the contents of the specified element, + * decoding it using the specified base. If the element has no contents or does + * not contain a valid number, returns 0. + */ +static u64 +xml_element_get_number(const struct xml_node *element, int base) { - if (info) - return info->num_images; - else - return 0; + return parse_number(xml_element_get_text(element), base); } -void -wim_info_set_wimboot(struct wim_info *info, int image, bool value) +/* + * Retrieve the timestamp from a time element. This element should have child + * elements HIGHPART and LOWPART; these elements will be used to construct a + * Windows-style timestamp. + */ +static u64 +xml_element_get_timestamp(const struct xml_node *element) { - info->images[image - 1].wimboot = value; + u64 timestamp = 0; + const struct xml_node *child; + + xml_node_for_each_child(element, child) { + if (xml_node_is_element(child, T("HIGHPART"))) + timestamp |= xml_element_get_number(child, 16) << 32; + else if (xml_node_is_element(child, T("LOWPART"))) + timestamp |= xml_element_get_number(child, 16); + } + return timestamp; } -bool -wim_info_get_wimboot(const struct wim_info *info, int image) +/* Create a new timestamp element and optionally link it into a tree. */ +static struct xml_node * +xml_new_element_with_timestamp(struct xml_node *parent, const tchar *name, + u64 timestamp) { - return info->images[image - 1].wimboot; -} + struct xml_node *element; + tchar buf[32]; -/* Architecture constants are from w64 mingw winnt.h */ -#define PROCESSOR_ARCHITECTURE_INTEL 0 -#define PROCESSOR_ARCHITECTURE_MIPS 1 -#define PROCESSOR_ARCHITECTURE_ALPHA 2 -#define PROCESSOR_ARCHITECTURE_PPC 3 -#define PROCESSOR_ARCHITECTURE_SHX 4 -#define PROCESSOR_ARCHITECTURE_ARM 5 -#define PROCESSOR_ARCHITECTURE_IA64 6 -#define PROCESSOR_ARCHITECTURE_ALPHA64 7 -#define PROCESSOR_ARCHITECTURE_MSIL 8 -#define PROCESSOR_ARCHITECTURE_AMD64 9 -#define PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10 - -/* Returns a statically allocated string that is a string representation of the - * architecture number. */ -static const tchar * -get_arch(int arch) -{ - switch (arch) { - case PROCESSOR_ARCHITECTURE_INTEL: - return T("x86"); - case PROCESSOR_ARCHITECTURE_MIPS: - return T("MIPS"); - case PROCESSOR_ARCHITECTURE_ARM: - return T("ARM"); - case PROCESSOR_ARCHITECTURE_IA64: - return T("ia64"); - case PROCESSOR_ARCHITECTURE_AMD64: - return T("x86_64"); - default: - return T("unknown"); - } -} + element = xml_new_element(NULL, name); + if (!element) + goto err; + tsprintf(buf, T("0x%08"PRIX32), (u32)(timestamp >> 32)); + if (!xml_new_element_with_text(element, T("HIGHPART"), buf)) + goto err; -/* Iterate through the children of an xmlNode. */ -#define for_node_child(parent, child) \ - for (child = parent->children; child != NULL; child = child->next) + tsprintf(buf, T("0x%08"PRIX32), (u32)timestamp); + if (!xml_new_element_with_text(element, T("LOWPART"), buf)) + goto err; -/* Utility functions for xmlNodes */ -static inline bool -node_is_element(xmlNode *node) -{ - return node->type == XML_ELEMENT_NODE; -} + if (parent) + xml_add_child(parent, element); + return element; -static inline bool -node_is_text(xmlNode *node) -{ - return node->type == XML_TEXT_NODE; +err: + xml_free_node(element); + return NULL; } -static inline bool -node_name_is(xmlNode *node, const char *name) +/* Create a new number element and optionally link it into a tree. */ +static struct xml_node * +xml_new_element_with_u64(struct xml_node *parent, const tchar *name, u64 value) { - /* For now, both upper case and lower case element names are accepted. */ - return strcasecmp((const char *)node->name, name) == 0; -} + tchar buf[32]; -static u64 -node_get_number(const xmlNode *u64_node, int base) -{ - xmlNode *child; - for_node_child(u64_node, child) - if (node_is_text(child)) - return strtoull(child->content, NULL, base); - return 0; + tsprintf(buf, T("%"PRIu64), value); + return xml_new_element_with_text(parent, name, buf); } -/* Finds the text node that is a child of an element node and returns its - * content converted to a 64-bit unsigned integer. Returns 0 if no text node is - * found. */ -static u64 -node_get_u64(const xmlNode *u64_node) +static bool +parse_index(tchar **pp, u32 *index_ret) { - return node_get_number(u64_node, 10); -} + tchar *p = *pp; + u32 index = 0; + + *p++ = '\0'; /* overwrite '[' */ + while (*p >= '0' && *p <= '9') { + u32 n = (index * 10) + (*p++ - '0'); + if (n < index) + return false; + index = n; + } + if (index == 0) + return false; + if (*p != ']') + return false; + p++; + if (*p != '/' && *p != '\0') + return false; -/* Like node_get_u64(), but expects a number in base 16. */ -static u64 -node_get_hex_u64(const xmlNode *u64_node) -{ - return node_get_number(u64_node, 16); + *pp = p; + *index_ret = index; + return true; } static int -node_get_string(const xmlNode *string_node, tchar **tstr_ret) +do_xml_path_walk(struct xml_node *element, const tchar *path, bool create, + struct xml_node **result_ret) { - xmlNode *child; + size_t n = tstrlen(path) + 1; + tchar buf[n]; + tchar *p; + tchar c; + + *result_ret = NULL; - if (*tstr_ret) + if (!element) return 0; - for_node_child(string_node, child) - if (node_is_text(child) && child->content) - return utf8_to_tstr_simple(child->content, tstr_ret); - return 0; -} + /* Copy the path to a temporary buffer. */ + tmemcpy(buf, path, n); + p = buf; -/* Returns the timestamp from a time node. It has child elements and - * that are then used to construct a 64-bit timestamp. */ -static u64 -node_get_timestamp(const xmlNode *time_node) -{ - u32 high_part = 0; - u32 low_part = 0; - xmlNode *child; - for_node_child(time_node, child) { - if (!node_is_element(child)) - continue; - if (node_name_is(child, "HIGHPART")) - high_part = node_get_hex_u64(child); - else if (node_name_is(child, "LOWPART")) - low_part = node_get_hex_u64(child); + if (*p == '/') + goto bad_syntax; + c = *p; + + while (c != '\0') { + const tchar *name; + struct xml_node *child; + u32 index = 1; + + /* We have another path component. */ + + /* Parse the element name. */ + name = p; + while (*p != '/' && *p != '\0' && *p != '[') + p++; + if (p == name) /* empty name? */ + goto bad_syntax; + + /* Handle a bracketed index, if one was specified. */ + if (*p == '[' && !parse_index(&p, &index)) + goto bad_syntax; + + c = *p; + *p = '\0'; + + /* Look for a matching child. */ + xml_node_for_each_child(element, child) + if (xml_node_is_element(child, name) && !--index) + goto next_step; + + /* No child matched the path. If create=false, the lookup + * failed. If create=true, create the needed element. */ + if (!create) + return 0; + + /* We can't create an element at index 'n' if indices 1...n-1 + * didn't already exist. */ + if (index != 1) + return WIMLIB_ERR_INVALID_PARAM; + + child = xml_new_element(element, name); + if (!child) + return WIMLIB_ERR_NOMEM; + next_step: + /* Continue to the next path component, if there is one. */ + element = child; + p++; } - return (u64)low_part | ((u64)high_part << 32); + + *result_ret = element; + return 0; + +bad_syntax: + ERROR("The XML path \"%"TS"\" has invalid syntax.", path); + return WIMLIB_ERR_INVALID_PARAM; } -/* Used to sort an array of struct image_infos by their image indices. */ -static int -sort_by_index(const void *p1, const void *p2) +/* Retrieve the XML element, if any, at the specified 'path'. This supports a + * simple filesystem-like syntax. If the element was found, returns a pointer + * to it; otherwise returns NULL. */ +static struct xml_node * +xml_get_element_by_path(struct xml_node *root, const tchar *path) { - int index_1 = ((const struct image_info*)p1)->index; - int index_2 = ((const struct image_info*)p2)->index; - if (index_1 < index_2) - return -1; - else if (index_1 > index_2) - return 1; - else - return 0; -} + struct xml_node *element; + do_xml_path_walk(root, path, false, &element); + return element; +} -/* Frees memory allocated inside a struct windows_info structure. */ -static void -destroy_windows_info(struct windows_info *windows_info) +/* + * Similar to xml_get_element_by_path(), but creates the element and any + * requisite ancestor elements as needed. If successful, 0 is returned and + * *element_ret is set to a pointer to the resulting element. If unsuccessful, + * an error code is returned and *element_ret is set to NULL. + */ +static int +xml_ensure_element_by_path(struct xml_node *root, const tchar *path, + struct xml_node **element_ret) { - FREE(windows_info->product_name); - FREE(windows_info->edition_id); - FREE(windows_info->installation_type); - FREE(windows_info->hal); - FREE(windows_info->product_type); - FREE(windows_info->product_suite); - FREE(windows_info->pkeyconfigversion); - for (size_t i = 0; i < windows_info->num_languages; i++) - FREE(windows_info->languages[i]); - FREE(windows_info->languages); - FREE(windows_info->default_language); - FREE(windows_info->system_root); + return do_xml_path_walk(root, path, true, element_ret); } -/* Frees memory allocated inside a struct image_info structure. */ -static void -destroy_image_info(struct image_info *image_info) +static u64 +xml_get_number_by_path(struct xml_node *root, const tchar *path) { - FREE(image_info->name); - FREE(image_info->description); - FREE(image_info->flags); - FREE(image_info->display_name); - FREE(image_info->display_description); - destroy_windows_info(&image_info->windows_info); - memset(image_info, 0, sizeof(struct image_info)); + return xml_element_get_number(xml_get_element_by_path(root, path), 10); } -void -free_wim_info(struct wim_info *info) +static u64 +xml_get_timestamp_by_path(struct xml_node *root, const tchar *path) { - if (info) { - if (info->images) { - for (int i = 0; i < info->num_images; i++) - destroy_image_info(&info->images[i]); - FREE(info->images); - } - FREE(info); - } + return xml_element_get_timestamp(xml_get_element_by_path(root, path)); } -/* Reads the information from a element inside the element. - * */ -static void -xml_read_windows_version(const xmlNode *version_node, - struct windows_version* windows_version) +static const tchar * +xml_get_text_by_path(struct xml_node *root, const tchar *path) { - xmlNode *child; - for_node_child(version_node, child) { - if (!node_is_element(child)) - continue; - if (node_name_is(child, "MAJOR")) - windows_version->major = node_get_u64(child); - else if (node_name_is(child, "MINOR")) - windows_version->minor = node_get_u64(child); - else if (node_name_is(child, "BUILD")) - windows_version->build = node_get_u64(child); - else if (node_name_is(child, "SPBUILD")) - windows_version->sp_build = node_get_u64(child); - else if (node_name_is(child, "SPLEVEL")) - windows_version->sp_level = node_get_u64(child); - } + return xml_element_get_text(xml_get_element_by_path(root, path)); } -/* Reads the information from a element inside a element. - * */ +/* + * Create/replace (if text is not NULL and not empty) or remove (if text is NULL + * or empty) an element containing text. + */ static int -xml_read_languages(const xmlNode *languages_node, - tchar ***languages_ret, - size_t *num_languages_ret, - tchar **default_language_ret) +xml_set_text_by_path(struct xml_node *root, const tchar *path, + const tchar *text) { - xmlNode *child; - size_t num_languages = 0; - tchar **languages; int ret; + struct xml_node *element; - for_node_child(languages_node, child) - if (node_is_element(child) && node_name_is(child, "LANGUAGE")) - num_languages++; - - languages = CALLOC(num_languages, sizeof(languages[0])); - if (!languages) - return WIMLIB_ERR_NOMEM; - - *languages_ret = languages; - *num_languages_ret = num_languages; - - ret = 0; - for_node_child(languages_node, child) { - if (!node_is_element(child)) - continue; - if (node_name_is(child, "LANGUAGE")) - ret = node_get_string(child, languages++); - else if (node_name_is(child, "DEFAULT")) - ret = node_get_string(child, default_language_ret); - if (ret != 0) - break; + if (text && *text) { + /* Create or replace */ + ret = xml_ensure_element_by_path(root, path, &element); + if (ret) + return ret; + return xml_element_set_text(element, text); + } else { + /* Remove */ + xml_free_node(xml_get_element_by_path(root, path)); + return 0; } - return ret; } -/* Reads the information from a element inside an element. */ -static int -xml_read_windows_info(const xmlNode *windows_node, - struct windows_info *windows_info) +/* Unlink and return the node which represents the INDEX attribute of the + * specified IMAGE element. */ +static struct xml_node * +unlink_index_attribute(struct xml_node *image_node) { - xmlNode *child; - int ret = 0; + struct xml_node *attr = xml_get_attrib(image_node, T("INDEX")); - for_node_child(windows_node, child) { - if (!node_is_element(child)) - continue; - if (node_name_is(child, "ARCH")) { - windows_info->arch = node_get_u64(child); - } else if (node_name_is(child, "PRODUCTNAME")) { - ret = node_get_string(child, - &windows_info->product_name); - } else if (node_name_is(child, "EDITIONID")) { - ret = node_get_string(child, - &windows_info->edition_id); - } else if (node_name_is(child, "INSTALLATIONTYPE")) { - ret = node_get_string(child, - &windows_info->installation_type); - } else if (node_name_is(child, "PRODUCTTYPE")) { - ret = node_get_string(child, - &windows_info->product_type); - } else if (node_name_is(child, "PRODUCTSUITE")) { - ret = node_get_string(child, - &windows_info->product_suite); - } else if (node_name_is(child, "LANGUAGES")) { - ret = xml_read_languages(child, - &windows_info->languages, - &windows_info->num_languages, - &windows_info->default_language); - } else if (node_name_is(child, "VERSION")) { - xml_read_windows_version(child, - &windows_info->windows_version); - windows_info->windows_version_exists = true; - } else if (node_name_is(child, "SYSTEMROOT")) { - ret = node_get_string(child, &windows_info->system_root); - } else if (node_name_is(child, "HAL")) { - ret = node_get_string(child, &windows_info->hal); - } else if (node_name_is(child, "SERVICINGDATA")) { - xmlNode *grandchild; - - for_node_child(child, grandchild) { - if (node_is_element(grandchild) && - node_name_is(grandchild, "PKEYCONFIGVERSION")) - { - ret = node_get_string(grandchild, - &windows_info->pkeyconfigversion); - } - } - } - - if (ret != 0) - return ret; - } - return ret; + xml_unlink_node(attr); + return attr; } -/* Reads the information from an element. */ -static int -xml_read_image_info(xmlNode *image_node, struct image_info *image_info) +/* Compute the total uncompressed size of the streams of the specified inode. */ +static u64 +inode_sum_stream_sizes(const struct wim_inode *inode, + const struct blob_table *blob_table) { - xmlNode *child; - xmlChar *index_prop; - int ret; + u64 total_size = 0; - index_prop = xmlGetProp(image_node, "INDEX"); - if (index_prop) { - image_info->index = atoi(index_prop); - xmlFree(index_prop); - } else { - image_info->index = 1; - } + for (unsigned i = 0; i < inode->i_num_streams; i++) { + const struct blob_descriptor *blob; - ret = 0; - for_node_child(image_node, child) { - if (!node_is_element(child)) - continue; - if (node_name_is(child, "DIRCOUNT")) - image_info->dir_count = node_get_u64(child); - else if (node_name_is(child, "FILECOUNT")) - image_info->file_count = node_get_u64(child); - else if (node_name_is(child, "TOTALBYTES")) - image_info->total_bytes = node_get_u64(child); - else if (node_name_is(child, "HARDLINKBYTES")) - image_info->hard_link_bytes = node_get_u64(child); - else if (node_name_is(child, "CREATIONTIME")) - image_info->creation_time = node_get_timestamp(child); - else if (node_name_is(child, "LASTMODIFICATIONTIME")) - image_info->last_modification_time = node_get_timestamp(child); - else if (node_name_is(child, "WINDOWS")) { - ret = xml_read_windows_info(child, - &image_info->windows_info); - image_info->windows_info_exists = true; - } else if (node_name_is(child, "NAME")) { - ret = node_get_string(child, &image_info->name); - } else if (node_name_is(child, "DESCRIPTION")) { - ret = node_get_string(child, &image_info->description); - } else if (node_name_is(child, "FLAGS")) { - ret = node_get_string(child, &image_info->flags); - } else if (node_name_is(child, "DISPLAYNAME")) { - ret = node_get_string(child, &image_info->display_name); - } else if (node_name_is(child, "DISPLAYDESCRIPTION")) { - ret = node_get_string(child, &image_info->display_description); - } else if (node_name_is(child, "WIMBOOT")) { - if (node_get_u64(child) == 1) { - image_info->wimboot = true; - } - } - if (ret != 0) - return ret; - } - if (!image_info->name) { - tchar *empty_name; - empty_name = MALLOC(sizeof(tchar)); - if (!empty_name) - return WIMLIB_ERR_NOMEM; - *empty_name = T('\0'); - image_info->name = empty_name; + blob = stream_blob(&inode->i_streams[i], blob_table); + if (blob) + total_size += blob->size; } - return ret; + return total_size; } -/* Reads the information from a element, which should be the root element - * of the XML tree. */ static int -xml_read_wim_info(const xmlNode *wim_node, struct wim_info **wim_info_ret) +append_image_node(struct wim_xml_info *info, struct xml_node *image_node) { - struct wim_info *wim_info; - xmlNode *child; + tchar buf[32]; + struct xml_node **images; int ret; - int num_images; - int i; - - wim_info = CALLOC(1, sizeof(struct wim_info)); - if (!wim_info) - return WIMLIB_ERR_NOMEM; - /* Count how many images there are. */ - num_images = 0; - for_node_child(wim_node, child) { - if (node_is_element(child) && node_name_is(child, "IMAGE")) { - if (unlikely(num_images == MAX_IMAGES)) { - ret = WIMLIB_ERR_IMAGE_COUNT; - goto err; - } - num_images++; - } - } + /* Limit exceeded? */ + if (unlikely(info->image_count >= MAX_IMAGES)) + return WIMLIB_ERR_IMAGE_COUNT; - if (num_images > 0) { - /* Allocate the array of struct image_infos and fill them in. */ - wim_info->images = CALLOC(num_images, sizeof(wim_info->images[0])); - if (!wim_info->images) { - ret = WIMLIB_ERR_NOMEM; - goto err; - } - wim_info->num_images = num_images; - i = 0; - for_node_child(wim_node, child) { - if (!node_is_element(child)) - continue; - if (node_name_is(child, "IMAGE")) { - ret = xml_read_image_info(child, - &wim_info->images[i]); - if (ret != 0) - goto err; - i++; - } else if (node_name_is(child, "TOTALBYTES")) { - wim_info->total_bytes = node_get_u64(child); - } else if (node_name_is(child, "ESD")) { - xmlNode *esdchild; - for_node_child(child, esdchild) { - if (node_is_element(esdchild) && - node_name_is(esdchild, "ENCRYPTED")) - { - ret = WIMLIB_ERR_WIM_IS_ENCRYPTED; - goto err; - } - } - } - } + /* Set the INDEX attribute. */ + tsprintf(buf, T("%d"), info->image_count + 1); + ret = xml_set_attrib(image_node, T("INDEX"), buf); + if (ret) + return ret; - /* Sort the array of image info by image index. */ - qsort(wim_info->images, num_images, - sizeof(struct image_info), sort_by_index); - - /* Make sure the image indices make sense */ - for (i = 0; i < num_images; i++) { - if (wim_info->images[i].index != i + 1) { - ERROR("WIM images are not indexed [1...%d] " - "in XML data as expected", - num_images); - ret = WIMLIB_ERR_IMAGE_COUNT; - goto err; - } - } + /* Append the IMAGE element to the 'images' array. */ + images = REALLOC(info->images, + (info->image_count + 1) * sizeof(info->images[0])); + if (unlikely(!images)) + return WIMLIB_ERR_NOMEM; + info->images = images; + images[info->image_count++] = image_node; - } - *wim_info_ret = wim_info; + /* Add the IMAGE element to the document. */ + xml_add_child(info->root, image_node); return 0; -err: - free_wim_info(wim_info); - return ret; } -/* Prints the information contained in a `struct windows_info'. */ -static void -print_windows_info(const struct windows_info *windows_info) -{ - const struct windows_version *windows_version; - - tprintf(T("Architecture: %"TS"\n"), - get_arch(windows_info->arch)); - - if (windows_info->product_name) { - tprintf(T("Product Name: %"TS"\n"), - windows_info->product_name); - } - - if (windows_info->edition_id) { - tprintf(T("Edition ID: %"TS"\n"), - windows_info->edition_id); - } - - if (windows_info->installation_type) { - tprintf(T("Installation Type: %"TS"\n"), - windows_info->installation_type); - } - - if (windows_info->hal) { - tprintf(T("HAL: %"TS"\n"), - windows_info->hal); - } - - if (windows_info->product_type) { - tprintf(T("Product Type: %"TS"\n"), - windows_info->product_type); - } - - if (windows_info->product_suite) { - tprintf(T("Product Suite: %"TS"\n"), - windows_info->product_suite); - } +/*----------------------------------------------------------------------------* + * Functions for internal library use * + *----------------------------------------------------------------------------*/ - tprintf(T("Languages: ")); - for (size_t i = 0; i < windows_info->num_languages; i++) { +/* Allocate an empty 'struct wim_xml_info', containing no images. */ +struct wim_xml_info * +xml_new_info_struct(void) +{ + struct wim_xml_info *info = CALLOC(1, sizeof(*info)); - tfputs(windows_info->languages[i], stdout); - tputchar(T(' ')); - } - tputchar(T('\n')); - if (windows_info->default_language) { - tprintf(T("Default Language: %"TS"\n"), - windows_info->default_language); - } - if (windows_info->system_root) { - tprintf(T("System Root: %"TS"\n"), - windows_info->system_root); - } + if (!info) + return NULL; - if (windows_info->windows_version_exists) { - windows_version = &windows_info->windows_version; - tprintf(T("Major Version: %"PRIu64"\n"), - windows_version->major); - tprintf(T("Minor Version: %"PRIu64"\n"), - windows_version->minor); - tprintf(T("Build: %"PRIu64"\n"), - windows_version->build); - tprintf(T("Service Pack Build: %"PRIu64"\n"), - windows_version->sp_build); - tprintf(T("Service Pack Level: %"PRIu64"\n"), - windows_version->sp_level); + info->root = xml_new_element(NULL, T("WIM")); + if (!info->root) { + FREE(info); + return NULL; } + return info; } -static int -xml_write_string(xmlTextWriter *writer, const char *name, - const tchar *tstr) +/* Free a 'struct wim_xml_info'. */ +void +xml_free_info_struct(struct wim_xml_info *info) { - if (tstr) { - char *utf8_str; - int rc = tstr_to_utf8_simple(tstr, &utf8_str); - if (rc) - return rc; - rc = xmlTextWriterWriteElement(writer, name, utf8_str); - FREE(utf8_str); - if (rc < 0) - return rc; + if (info) { + xml_free_node(info->root); + FREE(info->images); + FREE(info); } - return 0; } -static int -xml_write_strings_from_specs(xmlTextWriter *writer, - const void *struct_with_strings, - const struct xml_string_spec specs[], - size_t num_specs) +/* Retrieve the number of images for which there exist IMAGE elements in the XML + * document. */ +int +xml_get_image_count(const struct wim_xml_info *info) { - for (size_t i = 0; i < num_specs; i++) { - int rc = xml_write_string(writer, specs[i].name, - *(const tchar * const *) - (struct_with_strings + specs[i].offset)); - if (rc) - return rc; - } - return 0; + return info->image_count; } -static int -dup_strings_from_specs(const void *old_struct_with_strings, - void *new_struct_with_strings, - const struct xml_string_spec specs[], - size_t num_specs) +/* Retrieve the TOTALBYTES value for the WIM file, or 0 if this value is + * unavailable. */ +u64 +xml_get_total_bytes(const struct wim_xml_info *info) { - for (size_t i = 0; i < num_specs; i++) { - const tchar *old_str = *(const tchar * const *) - ((const void*)old_struct_with_strings + specs[i].offset); - tchar **new_str_p = (tchar **)((void*)new_struct_with_strings + specs[i].offset); - if (old_str) { - *new_str_p = TSTRDUP(old_str); - if (!*new_str_p) - return WIMLIB_ERR_NOMEM; - } - } - return 0; + return xml_get_number_by_path(info->root, T("TOTALBYTES")); } -/* Writes the information contained in a `struct windows_version' to the XML - * document being written. This is the element inside the - * element. */ -static int -xml_write_windows_version(xmlTextWriter *writer, - const struct windows_version *version) +/* Retrieve the TOTALBYTES value for the specified image, or 0 if this value is + * unavailable. */ +u64 +xml_get_image_total_bytes(const struct wim_xml_info *info, int image) { - int rc; - rc = xmlTextWriterStartElement(writer, "VERSION"); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "MAJOR", "%"PRIu64, - version->major); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "MINOR", "%"PRIu64, - version->minor); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "BUILD", "%"PRIu64, - version->build); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "SPBUILD", "%"PRIu64, - version->sp_build); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "SPLEVEL", "%"PRIu64, - version->sp_level); - if (rc < 0) - return rc; - - rc = xmlTextWriterEndElement(writer); /* */ - if (rc < 0) - return rc; - - return 0; + return xml_get_number_by_path(info->images[image - 1], T("TOTALBYTES")); } -/* Writes the information contained in a `struct windows_info' to the XML - * document being written. This is the element. */ -static int -xml_write_windows_info(xmlTextWriter *writer, - const struct windows_info *windows_info) +/* Retrieve the HARDLINKBYTES value for the specified image, or 0 if this value + * is unavailable. */ +u64 +xml_get_image_hard_link_bytes(const struct wim_xml_info *info, int image) { - int rc; - rc = xmlTextWriterStartElement(writer, "WINDOWS"); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "ARCH", "%"PRIu64, - windows_info->arch); - if (rc < 0) - return rc; - - rc = xml_write_strings_from_specs(writer, - windows_info, - windows_info_xml_string_specs, - ARRAY_LEN(windows_info_xml_string_specs)); - if (rc) - return rc; - - if (windows_info->num_languages) { - rc = xmlTextWriterStartElement(writer, "LANGUAGES"); - if (rc < 0) - return rc; - - for (size_t i = 0; i < windows_info->num_languages; i++) { - rc = xml_write_string(writer, "LANGUAGE", - windows_info->languages[i]); - if (rc) - return rc; - } - - rc = xml_write_string(writer, "DEFAULT", - windows_info->default_language); - if (rc) - return rc; - - rc = xmlTextWriterEndElement(writer); /* */ - if (rc < 0) - return rc; - } - - if (windows_info->pkeyconfigversion) { - rc = xmlTextWriterStartElement(writer, "SERVICINGDATA"); - if (rc < 0) - return rc; - - rc = xml_write_string(writer, "PKEYCONFIGVERSION", - windows_info->pkeyconfigversion); - if (rc) - return rc; - - rc = xmlTextWriterEndElement(writer); - if (rc < 0) - return rc; - } - - if (windows_info->windows_version_exists) { - rc = xml_write_windows_version(writer, &windows_info->windows_version); - if (rc) - return rc; - } - - rc = xml_write_string(writer, "SYSTEMROOT", windows_info->system_root); - if (rc) - return rc; - - rc = xmlTextWriterEndElement(writer); /* */ - if (rc < 0) - return rc; - - return 0; + return xml_get_number_by_path(info->images[image - 1], + T("HARDLINKBYTES")); } -/* Writes a time element to the XML document being constructed in memory. */ -static int -xml_write_time(xmlTextWriter *writer, const char *element_name, u64 time) +/* Retrieve the WIMBOOT value for the specified image, or false if this value is + * unavailable. */ +bool +xml_get_wimboot(const struct wim_xml_info *info, int image) { - int rc; - rc = xmlTextWriterStartElement(writer, element_name); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "HIGHPART", - "0x%08"PRIX32, (u32)(time >> 32)); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "LOWPART", - "0x%08"PRIX32, (u32)time); - if (rc < 0) - return rc; - - rc = xmlTextWriterEndElement(writer); /* */ - if (rc < 0) - return rc; - return 0; + return xml_get_number_by_path(info->images[image - 1], T("WIMBOOT")); } -/* Writes an element to the XML document. */ -static int -xml_write_image_info(xmlTextWriter *writer, const struct image_info *image_info) +/* Retrieve the Windows build number for the specified image, or 0 if this + * information is not available. */ +u64 +xml_get_windows_build_number(const struct wim_xml_info *info, int image) { - int rc; - rc = xmlTextWriterStartElement(writer, "IMAGE"); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatAttribute(writer, "INDEX", "%d", - image_info->index); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "DIRCOUNT", "%"PRIu64, - image_info->dir_count); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "FILECOUNT", "%"PRIu64, - image_info->file_count); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "TOTALBYTES", "%"PRIu64, - image_info->total_bytes); - if (rc < 0) - return rc; - - rc = xmlTextWriterWriteFormatElement(writer, "HARDLINKBYTES", "%"PRIu64, - image_info->hard_link_bytes); - if (rc < 0) - return rc; - - rc = xml_write_time(writer, "CREATIONTIME", image_info->creation_time); - if (rc < 0) - return rc; - - rc = xml_write_time(writer, "LASTMODIFICATIONTIME", - image_info->last_modification_time); - if (rc < 0) - return rc; - - if (image_info->windows_info_exists) { - rc = xml_write_windows_info(writer, &image_info->windows_info); - if (rc) - return rc; - } - - rc = xml_write_strings_from_specs(writer, image_info, - image_info_xml_string_specs, - ARRAY_LEN(image_info_xml_string_specs)); - if (rc) - return rc; - - if (image_info->wimboot) { - rc = xmlTextWriterWriteFormatElement(writer, "WIMBOOT", "%d", 1); - if (rc < 0) - return rc; - } - - rc = xmlTextWriterEndElement(writer); /* */ - if (rc < 0) - return rc; - - return 0; + return xml_get_number_by_path(info->images[image - 1], + T("WINDOWS/VERSION/BUILD")); } - - -/* Makes space for another image in the XML information and return a pointer to - * it.*/ -static struct image_info * -add_image_info_struct(struct wim_info *wim_info) +/* Set the WIMBOOT value for the specified image. */ +int +xml_set_wimboot(struct wim_xml_info *info, int image) { - struct image_info *images; - - images = CALLOC(wim_info->num_images + 1, sizeof(struct image_info)); - if (!images) - return NULL; - memcpy(images, wim_info->images, - wim_info->num_images * sizeof(struct image_info)); - FREE(wim_info->images); - wim_info->images = images; - wim_info->num_images++; - return &images[wim_info->num_images - 1]; + return xml_set_text_by_path(info->images[image - 1], + T("WIMBOOT"), T("1")); } -static int -clone_windows_info(const struct windows_info *old, struct windows_info *new) +/* + * Update the DIRCOUNT, FILECOUNT, TOTALBYTES, HARDLINKBYTES, and + * LASTMODIFICATIONTIME elements for the specified WIM image. + * + * Note: since these stats are likely to be used for display purposes only, we + * no longer attempt to duplicate WIMGAPI's weird bugs when calculating them. + */ +int +xml_update_image_info(WIMStruct *wim, int image) { - int ret; - - new->arch = old->arch; - - ret = dup_strings_from_specs(old, new, windows_info_xml_string_specs, - ARRAY_LEN(windows_info_xml_string_specs)); - if (ret) - return ret; - - if (old->pkeyconfigversion) { - new->pkeyconfigversion = TSTRDUP(old->pkeyconfigversion); - if (new->pkeyconfigversion == NULL) - return WIMLIB_ERR_NOMEM; + const struct wim_image_metadata *imd = wim->image_metadata[image - 1]; + struct xml_node *image_node = wim->xml_info->images[image - 1]; + const struct wim_inode *inode; + u64 dir_count = 0; + u64 file_count = 0; + u64 total_bytes = 0; + u64 hard_link_bytes = 0; + u64 size; + struct xml_node *dircount_node; + struct xml_node *filecount_node; + struct xml_node *totalbytes_node; + struct xml_node *hardlinkbytes_node; + struct xml_node *lastmodificationtime_node; + + image_for_each_inode(inode, imd) { + if (inode_is_directory(inode)) + dir_count += inode->i_nlink; + else + file_count += inode->i_nlink; + size = inode_sum_stream_sizes(inode, wim->blob_table); + total_bytes += size * inode->i_nlink; + hard_link_bytes += size * (inode->i_nlink - 1); } - if (old->languages) { - new->languages = CALLOC(old->num_languages, sizeof(new->languages[0])); - if (!new->languages) - return WIMLIB_ERR_NOMEM; - new->num_languages = old->num_languages; - for (size_t i = 0; i < new->num_languages; i++) { - if (!old->languages[i]) - continue; - new->languages[i] = TSTRDUP(old->languages[i]); - if (!new->languages[i]) - return WIMLIB_ERR_NOMEM; - } - } - if (old->default_language && - !(new->default_language = TSTRDUP(old->default_language))) - return WIMLIB_ERR_NOMEM; - if (old->system_root && !(new->system_root = TSTRDUP(old->system_root))) + dircount_node = xml_new_element_with_u64(NULL, T("DIRCOUNT"), + dir_count); + filecount_node = xml_new_element_with_u64(NULL, T("FILECOUNT"), + file_count); + totalbytes_node = xml_new_element_with_u64(NULL, T("TOTALBYTES"), + total_bytes); + hardlinkbytes_node = xml_new_element_with_u64(NULL, T("HARDLINKBYTES"), + hard_link_bytes); + lastmodificationtime_node = xml_new_element_with_timestamp(NULL, + T("LASTMODIFICATIONTIME"), now_as_wim_timestamp()); + + if (unlikely(!dircount_node || !filecount_node || !totalbytes_node || + !hardlinkbytes_node || !lastmodificationtime_node)) { + xml_free_node(dircount_node); + xml_free_node(filecount_node); + xml_free_node(totalbytes_node); + xml_free_node(hardlinkbytes_node); + xml_free_node(lastmodificationtime_node); return WIMLIB_ERR_NOMEM; - if (old->windows_version_exists) { - new->windows_version_exists = true; - memcpy(&new->windows_version, &old->windows_version, - sizeof(old->windows_version)); } + + xml_replace_child(image_node, dircount_node); + xml_replace_child(image_node, filecount_node); + xml_replace_child(image_node, totalbytes_node); + xml_replace_child(image_node, hardlinkbytes_node); + xml_replace_child(image_node, lastmodificationtime_node); return 0; } -static int -clone_image_info(const struct image_info *old, struct image_info *new) +/* Add an image to the XML information. */ +int +xml_add_image(struct wim_xml_info *info, const tchar *name) { + const u64 now = now_as_wim_timestamp(); + struct xml_node *image_node; int ret; - new->dir_count = old->dir_count; - new->file_count = old->file_count; - new->total_bytes = old->total_bytes; - new->hard_link_bytes = old->hard_link_bytes; - new->creation_time = old->creation_time; - new->last_modification_time = old->last_modification_time; + if (name && !xml_legal_value(name)) { + ERROR("Name of new image contains illegal characters"); + return WIMLIB_ERR_INVALID_PARAM; + } - ret = dup_strings_from_specs(old, new, - image_info_xml_string_specs, - ARRAY_LEN(image_info_xml_string_specs)); + ret = WIMLIB_ERR_NOMEM; + image_node = xml_new_element(NULL, T("IMAGE")); + if (!image_node) + goto err; + if (name && *name && + !xml_new_element_with_text(image_node, T("NAME"), name)) + goto err; + if (!xml_new_element_with_u64(image_node, T("DIRCOUNT"), 0)) + goto err; + if (!xml_new_element_with_u64(image_node, T("FILECOUNT"), 0)) + goto err; + if (!xml_new_element_with_u64(image_node, T("TOTALBYTES"), 0)) + goto err; + if (!xml_new_element_with_u64(image_node, T("HARDLINKBYTES"), 0)) + goto err; + if (!xml_new_element_with_timestamp(image_node, T("CREATIONTIME"), now)) + goto err; + if (!xml_new_element_with_timestamp(image_node, + T("LASTMODIFICATIONTIME"), now)) + goto err; + ret = append_image_node(info, image_node); if (ret) - return ret; - - if (old->windows_info_exists) { - new->windows_info_exists = true; - ret = clone_windows_info(&old->windows_info, - &new->windows_info); - if (ret) - return ret; - } - new->wimboot = old->wimboot; + goto err; return 0; + +err: + xml_free_node(image_node); + return ret; } -/* Copies the XML information for an image between WIM files. - * - * @dest_image_name and @dest_image_description are ignored if they are NULL; - * otherwise, they are used to override the image name and/or image description - * from the XML data in the source WIM file. +/* + * Make a copy of the XML information for the image with index @src_image in the + * @src_info XML document and append it to the @dest_info XML document. * - * On failure, WIMLIB_ERR_NOMEM is returned and no changes are made. Otherwise, - * 0 is returned and the WIM information at *new_wim_info_p is modified. + * In the process, change the image's name and description to the values + * specified by @dest_image_name and @dest_image_description. Either or both + * may be NULL, which indicates that the corresponding element will not be + * included in the destination image. */ int -xml_export_image(const struct wim_info *old_wim_info, - int image, - struct wim_info **new_wim_info_p, - const tchar *dest_image_name, - const tchar *dest_image_description) +xml_export_image(const struct wim_xml_info *src_info, int src_image, + struct wim_xml_info *dest_info, const tchar *dest_image_name, + const tchar *dest_image_description, bool wimboot) { - struct wim_info *new_wim_info; - struct image_info *image_info; + struct xml_node *dest_node; int ret; - DEBUG("Copying XML data between WIM files for source image %d.", image); - - wimlib_assert(old_wim_info != NULL); - wimlib_assert(image >= 1 && image <= old_wim_info->num_images); - - if (*new_wim_info_p) { - new_wim_info = *new_wim_info_p; - } else { - new_wim_info = CALLOC(1, sizeof(struct wim_info)); - if (!new_wim_info) - goto err; + if (dest_image_name && !xml_legal_value(dest_image_name)) { + ERROR("Destination image name contains illegal characters"); + return WIMLIB_ERR_INVALID_PARAM; + } + if (dest_image_description && + !xml_legal_value(dest_image_description)) { + ERROR("Destination image description contains illegal characters"); + return WIMLIB_ERR_INVALID_PARAM; } - image_info = add_image_info_struct(new_wim_info); - if (!image_info) + ret = WIMLIB_ERR_NOMEM; + dest_node = xml_clone_tree(src_info->images[src_image - 1]); + if (!dest_node) goto err; - ret = clone_image_info(&old_wim_info->images[image - 1], image_info); - if (ret != 0) - goto err_destroy_image_info; + ret = xml_set_text_by_path(dest_node, T("NAME"), dest_image_name); + if (ret) + goto err; - image_info->index = new_wim_info->num_images; + ret = xml_set_text_by_path(dest_node, T("DESCRIPTION"), + dest_image_description); + if (ret) + goto err; - if (dest_image_name) { - FREE(image_info->name); - image_info->name = TSTRDUP(dest_image_name); - if (!image_info->name) - goto err_destroy_image_info; - } - if (dest_image_description) { - FREE(image_info->description); - image_info->description = TSTRDUP(dest_image_description); - if (!image_info->description) - goto err_destroy_image_info; + if (wimboot) { + ret = xml_set_text_by_path(dest_node, T("WIMBOOT"), T("1")); + if (ret) + goto err; } - *new_wim_info_p = new_wim_info; + + ret = append_image_node(dest_info, dest_node); + if (ret) + goto err; return 0; -err_destroy_image_info: - destroy_image_info(image_info); + err: - if (new_wim_info != *new_wim_info_p) - free_wim_info(new_wim_info); - return WIMLIB_ERR_NOMEM; + xml_free_node(dest_node); + return ret; } -/* Removes an image from the XML information. */ +/* Remove the specified image from the XML document. */ void -xml_delete_image(struct wim_info **wim_info_p, int image) +xml_delete_image(struct wim_xml_info *info, int image) { - struct wim_info *wim_info; - - wim_info = *wim_info_p; - wimlib_assert(image >= 1 && image <= wim_info->num_images); - DEBUG("Deleting image %d from the XML data.", image); - - destroy_image_info(&wim_info->images[image - 1]); - - memmove(&wim_info->images[image - 1], - &wim_info->images[image], - (wim_info->num_images - image) * sizeof(struct image_info)); - - if (--wim_info->num_images == 0) { - free_wim_info(wim_info); - *wim_info_p = NULL; - } else { - for (int i = image - 1; i < wim_info->num_images; i++) - wim_info->images[i].index--; + struct xml_node *next_image; + struct xml_node *index_attr, *next_index_attr; + + /* Free the IMAGE element for the deleted image. Then, shift all + * higher-indexed IMAGE elements down by 1, in the process re-assigning + * their INDEX attributes. */ + + next_image = info->images[image - 1]; + next_index_attr = unlink_index_attribute(next_image); + xml_free_node(next_image); + + while (image < info->image_count) { + index_attr = next_index_attr; + next_image = info->images[image]; + next_index_attr = unlink_index_attribute(next_image); + xml_add_child(next_image, index_attr); + info->images[image - 1] = next_image; + image++; } -} -size_t -xml_get_max_image_name_len(const WIMStruct *wim) -{ - size_t max_len = 0; - for (u32 i = 0; i < wim->hdr.image_count; i++) - max_len = max(max_len, tstrlen(wim->wim_info->images[i].name)); - return max_len; + xml_free_node(next_index_attr); + info->image_count--; } -void -xml_set_memory_allocator(void *(*malloc_func)(size_t), - void (*free_func)(void *), - void *(*realloc_func)(void *, size_t)) +/* Architecture constants are from w64 mingw winnt.h */ +#define PROCESSOR_ARCHITECTURE_INTEL 0 +#define PROCESSOR_ARCHITECTURE_MIPS 1 +#define PROCESSOR_ARCHITECTURE_ALPHA 2 +#define PROCESSOR_ARCHITECTURE_PPC 3 +#define PROCESSOR_ARCHITECTURE_SHX 4 +#define PROCESSOR_ARCHITECTURE_ARM 5 +#define PROCESSOR_ARCHITECTURE_IA64 6 +#define PROCESSOR_ARCHITECTURE_ALPHA64 7 +#define PROCESSOR_ARCHITECTURE_MSIL 8 +#define PROCESSOR_ARCHITECTURE_AMD64 9 +#define PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 10 +#define PROCESSOR_ARCHITECTURE_ARM64 12 + +static const tchar * +describe_arch(u64 arch) { - xmlMemSetup(free_func, malloc_func, realloc_func, STRDUP); + static const tchar * const descriptions[] = { + [PROCESSOR_ARCHITECTURE_INTEL] = T("x86"), + [PROCESSOR_ARCHITECTURE_MIPS] = T("MIPS"), + [PROCESSOR_ARCHITECTURE_ARM] = T("ARM"), + [PROCESSOR_ARCHITECTURE_IA64] = T("ia64"), + [PROCESSOR_ARCHITECTURE_AMD64] = T("x86_64"), + [PROCESSOR_ARCHITECTURE_ARM64] = T("ARM64"), + }; + + if (arch < ARRAY_LEN(descriptions) && descriptions[arch] != NULL) + return descriptions[arch]; + + return T("unknown"); } -static int -calculate_dentry_statistics(struct wim_dentry *dentry, void *arg) +/* Print information from the WINDOWS element, if present. */ +static void +print_windows_info(struct xml_node *image_node) { - struct image_info *info = arg; - const struct wim_inode *inode = dentry->d_inode; - - /* Update directory count and file count. - * - * Each dentry counts as either a file or a directory, but not both. - * The root directory is an exception: it is not counted at all. - * - * Symbolic links and junction points (and presumably other reparse - * points) count as regular files. This is despite the fact that - * junction points have FILE_ATTRIBUTE_DIRECTORY set. - */ - - if (!dentry_is_root(dentry)) { - if (inode_is_directory(inode)) - info->dir_count++; - else - info->file_count++; - } + struct xml_node *windows_node; + struct xml_node *langs_node; + struct xml_node *version_node; + const tchar *text; - /* - * Update total bytes and hard link bytes. - * - * We try to act the same as the MS implementation, even though there - * are some inconsistencies/bugs in the way it operates. - * - * If there are no alternate data streams in the image, the "total - * bytes" is the sum of the size of the un-named data stream of each - * inode times the link count of that inode. In other words, it would - * be the total number of bytes of regular files you would have if you - * extracted the full image without any hard-links. The "hard link - * bytes" is equal to the "total bytes" minus the size of the un-named - * data stream of each inode. In other words, the "hard link bytes" - * counts the size of the un-named data stream for all the links to each - * inode except the first one. - * - * Reparse points and directories don't seem to be counted in either the - * total bytes or the hard link bytes. - * - * And now we get to the most confusing part, the alternate data - * streams. They are not counted in the "total bytes". However, if the - * link count of an inode with alternate data streams is 2 or greater, - * the size of all the alternate data streams is included in the "hard - * link bytes", and this size is multiplied by the link count (NOT one - * less than the link count). - */ - if (!(inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY | - FILE_ATTRIBUTE_REPARSE_POINT))) - { - struct blob_descriptor *blob; - - blob = inode_get_blob_for_unnamed_data_stream(inode, - info->blob_table); - if (blob) { - info->total_bytes += blob->size; - if (!dentry_is_first_in_inode(dentry)) - info->hard_link_bytes += blob->size; - } + windows_node = xml_get_element_by_path(image_node, T("WINDOWS")); + if (!windows_node) + return; - if (inode->i_nlink >= 2 && dentry_is_first_in_inode(dentry)) { - for (unsigned i = 0; i < inode->i_num_streams; i++) { - if (stream_is_named_data_stream(&inode->i_streams[i])) { - blob = stream_blob(&inode->i_streams[i], - info->blob_table); - if (blob) { - info->hard_link_bytes += inode->i_nlink * - blob->size; - } - } - } - } - } - return 0; -} + tprintf(T("Architecture: %"TS"\n"), + describe_arch(xml_get_number_by_path(windows_node, T("ARCH")))); -/* - * Calculate what to put in the , , , and - * elements of the specified WIM image. - */ -void -xml_update_image_info(WIMStruct *wim, int image) -{ - struct image_info *image_info; + text = xml_get_text_by_path(windows_node, T("PRODUCTNAME")); + if (text) + tprintf(T("Product Name: %"TS"\n"), text); - DEBUG("Updating the image info for image %d", image); + text = xml_get_text_by_path(windows_node, T("EDITIONID")); + if (text) + tprintf(T("Edition ID: %"TS"\n"), text); - image_info = &wim->wim_info->images[image - 1]; + text = xml_get_text_by_path(windows_node, T("INSTALLATIONTYPE")); + if (text) + tprintf(T("Installation Type: %"TS"\n"), text); - image_info->file_count = 0; - image_info->dir_count = 0; - image_info->total_bytes = 0; - image_info->hard_link_bytes = 0; - image_info->blob_table = wim->blob_table; + text = xml_get_text_by_path(windows_node, T("HAL")); + if (text) + tprintf(T("HAL: %"TS"\n"), text); - for_dentry_in_tree(wim->image_metadata[image - 1]->root_dentry, - calculate_dentry_statistics, - image_info); - image_info->last_modification_time = now_as_wim_timestamp(); -} + text = xml_get_text_by_path(windows_node, T("PRODUCTTYPE")); + if (text) + tprintf(T("Product Type: %"TS"\n"), text); -/* Adds an image to the XML information. */ -int -xml_add_image(WIMStruct *wim, const tchar *name) -{ - struct wim_info *wim_info; - struct image_info *image_info; + text = xml_get_text_by_path(windows_node, T("PRODUCTSUITE")); + if (text) + tprintf(T("Product Suite: %"TS"\n"), text); - wimlib_assert(name != NULL); + langs_node = xml_get_element_by_path(windows_node, T("LANGUAGES")); + if (langs_node) { + struct xml_node *lang_node; - /* If this is the first image, allocate the struct wim_info. Otherwise - * use the existing struct wim_info. */ - if (wim->wim_info) { - wim_info = wim->wim_info; - } else { - wim_info = CALLOC(1, sizeof(struct wim_info)); - if (!wim_info) - return WIMLIB_ERR_NOMEM; - } - - image_info = add_image_info_struct(wim_info); - if (!image_info) - goto out_free_wim_info; + tprintf(T("Languages: ")); + xml_node_for_each_child(langs_node, lang_node) { + if (!xml_node_is_element(lang_node, T("LANGUAGE"))) + continue; + text = xml_element_get_text(lang_node); + if (!text) + continue; + tprintf(T("%"TS" "), text); + } + tputchar(T('\n')); - if (!(image_info->name = TSTRDUP(name))) - goto out_destroy_image_info; + text = xml_get_text_by_path(langs_node, T("DEFAULT")); + if (text) + tprintf(T("Default Language: %"TS"\n"), text); + } - wim->wim_info = wim_info; - image_info->index = wim_info->num_images; - image_info->creation_time = now_as_wim_timestamp(); - xml_update_image_info(wim, image_info->index); - return 0; + text = xml_get_text_by_path(windows_node, T("SYSTEMROOT")); + if (text) + tprintf(T("System Root: %"TS"\n"), text); -out_destroy_image_info: - destroy_image_info(image_info); - wim_info->num_images--; -out_free_wim_info: - if (wim_info != wim->wim_info) - FREE(wim_info); - return WIMLIB_ERR_NOMEM; + version_node = xml_get_element_by_path(windows_node, T("VERSION")); + if (version_node) { + tprintf(T("Major Version: %"PRIu64"\n"), + xml_get_number_by_path(version_node, T("MAJOR"))); + tprintf(T("Minor Version: %"PRIu64"\n"), + xml_get_number_by_path(version_node, T("MINOR"))); + tprintf(T("Build: %"PRIu64"\n"), + xml_get_number_by_path(version_node, T("BUILD"))); + tprintf(T("Service Pack Build: %"PRIu64"\n"), + xml_get_number_by_path(version_node, T("SPBUILD"))); + tprintf(T("Service Pack Level: %"PRIu64"\n"), + xml_get_number_by_path(version_node, T("SPLEVEL"))); + } } -/* Prints information about the specified image from struct wim_info structure. - * */ +/* Prints information about the specified image. */ void -print_image_info(const struct wim_info *wim_info, int image) +xml_print_image_info(struct wim_xml_info *info, int image) { - const struct image_info *image_info; - const tchar *desc; - tchar buf[50]; + struct xml_node * const image_node = info->images[image - 1]; + const tchar *text; + tchar timebuf[64]; - wimlib_assert(image >= 1 && image <= wim_info->num_images); + tprintf(T("Index: %d\n"), image); - image_info = &wim_info->images[image - 1]; + /* Always print the Name and Description, even if the corresponding XML + * elements are not present. */ + text = xml_get_text_by_path(image_node, T("NAME")); + tprintf(T("Name: %"TS"\n"), text ? text : T("")); + text = xml_get_text_by_path(image_node, T("DESCRIPTION")); + tprintf(T("Description: %"TS"\n"), text ? text : T("")); - tprintf(T("Index: %d\n"), image_info->index); - tprintf(T("Name: %"TS"\n"), image_info->name); + text = xml_get_text_by_path(image_node, T("DISPLAYNAME")); + if (text) + tprintf(T("Display Name: %"TS"\n"), text); - /* Always print the Description: part even if there is no - * description. */ - if (image_info->description) - desc = image_info->description; - else - desc = T(""); - tprintf(T("Description: %"TS"\n"), desc); + text = xml_get_text_by_path(image_node, T("DISPLAYDESCRIPTION")); + if (text) + tprintf(T("Display Description: %"TS"\n"), text); - if (image_info->display_name) { - tprintf(T("Display Name: %"TS"\n"), - image_info->display_name); - } + tprintf(T("Directory Count: %"PRIu64"\n"), + xml_get_number_by_path(image_node, T("DIRCOUNT"))); - if (image_info->display_description) { - tprintf(T("Display Description: %"TS"\n"), - image_info->display_description); - } + tprintf(T("File Count: %"PRIu64"\n"), + xml_get_number_by_path(image_node, T("FILECOUNT"))); + + tprintf(T("Total Bytes: %"PRIu64"\n"), + xml_get_number_by_path(image_node, T("TOTALBYTES"))); - tprintf(T("Directory Count: %"PRIu64"\n"), image_info->dir_count); - tprintf(T("File Count: %"PRIu64"\n"), image_info->file_count); - tprintf(T("Total Bytes: %"PRIu64"\n"), image_info->total_bytes); - tprintf(T("Hard Link Bytes: %"PRIu64"\n"), image_info->hard_link_bytes); + tprintf(T("Hard Link Bytes: %"PRIu64"\n"), + xml_get_number_by_path(image_node, T("HARDLINKBYTES"))); - wim_timestamp_to_str(image_info->creation_time, buf, sizeof(buf)); - tprintf(T("Creation Time: %"TS"\n"), buf); + wim_timestamp_to_str(xml_get_timestamp_by_path(image_node, + T("CREATIONTIME")), + timebuf, ARRAY_LEN(timebuf)); + tprintf(T("Creation Time: %"TS"\n"), timebuf); + + wim_timestamp_to_str(xml_get_timestamp_by_path(image_node, + T("LASTMODIFICATIONTIME")), + timebuf, ARRAY_LEN(timebuf)); + tprintf(T("Last Modification Time: %"TS"\n"), timebuf); + + print_windows_info(image_node); + + text = xml_get_text_by_path(image_node, T("FLAGS")); + if (text) + tprintf(T("Flags: %"TS"\n"), text); - wim_timestamp_to_str(image_info->last_modification_time, buf, sizeof(buf)); - tprintf(T("Last Modification Time: %"TS"\n"), buf); - if (image_info->windows_info_exists) - print_windows_info(&image_info->windows_info); - if (image_info->flags) - tprintf(T("Flags: %"TS"\n"), image_info->flags); tprintf(T("WIMBoot compatible: %"TS"\n"), - image_info->wimboot ? T("yes") : T("no")); + xml_get_number_by_path(image_node, T("WIMBOOT")) ? + T("yes") : T("no")); + tputchar('\n'); } -void -libxml_global_init(void) -{ - xmlInitParser(); - xmlInitCharEncodingHandlers(); -} +/*----------------------------------------------------------------------------* + * Reading and writing the XML data * + *----------------------------------------------------------------------------*/ -void -libxml_global_cleanup(void) +static int +image_element_get_index(struct xml_node *element) { - xmlCleanupParser(); - xmlCleanupCharEncodingHandlers(); + struct xml_node *attrib = xml_get_attrib(element, T("INDEX")); + + if (!attrib) + return 0; + return min(INT_MAX, parse_number(attrib->value, 10)); } -/* Reads the XML data from a WIM file. */ -int -read_wim_xml_data(WIMStruct *wim) +/* Prepare the 'images' array from the XML document tree. */ +static int +setup_images(struct wim_xml_info *info, struct xml_node *root) { - void *buf; - size_t bufsize; - u8 *xml_data; - xmlDoc *doc; - xmlNode *root; + struct xml_node *child; + int index; + int max_index = 0; int ret; - ret = wimlib_get_xml_data(wim, &buf, &bufsize); - if (ret) - goto out; - xml_data = buf; - - doc = xmlReadMemory((const char *)xml_data, bufsize, - NULL, "UTF-16LE", 0); - if (!doc) { - ERROR("Failed to parse XML data"); - ret = WIMLIB_ERR_XML; - goto out_free_xml_data; + xml_node_for_each_child(root, child) { + if (!xml_node_is_element(child, T("IMAGE"))) + continue; + index = image_element_get_index(child); + if (unlikely(index < 1 || info->image_count >= MAX_IMAGES)) + goto err_indices; + max_index = max(max_index, index); + info->image_count++; } - - root = xmlDocGetRootElement(doc); - if (!root || !node_is_element(root) || !node_name_is(root, "WIM")) { - ERROR("WIM XML data is invalid"); - ret = WIMLIB_ERR_XML; - goto out_free_doc; + if (unlikely(max_index != info->image_count)) + goto err_indices; + ret = WIMLIB_ERR_NOMEM; + info->images = CALLOC(info->image_count, sizeof(info->images[0])); + if (unlikely(!info->images)) + goto err; + xml_node_for_each_child(root, child) { + if (!xml_node_is_element(child, T("IMAGE"))) + continue; + index = image_element_get_index(child); + if (unlikely(info->images[index - 1])) + goto err_indices; + info->images[index - 1] = child; } + return 0; - ret = xml_read_wim_info(root, &wim->wim_info); -out_free_doc: - xmlFreeDoc(doc); -out_free_xml_data: - FREE(xml_data); -out: +err_indices: + ERROR("The WIM file's XML document does not contain exactly one IMAGE " + "element per image!"); + ret = WIMLIB_ERR_XML; +err: + FREE(info->images); return ret; } -/* Prepares an in-memory buffer containing the UTF-16LE XML data for a WIM file. - * - * total_bytes is the number to write in , or - * WIM_TOTALBYTES_USE_EXISTING to use the existing value in memory, or - * WIM_TOTALBYTES_OMIT to omit entirely. - */ static int -prepare_wim_xml_data(WIMStruct *wim, int image, u64 total_bytes, - u8 **xml_data_ret, size_t *xml_len_ret) +parse_wim_xml_document(const utf16lechar *raw_doc, size_t raw_doc_size, + struct xml_node **root_ret) { - xmlCharEncodingHandler *encoding_handler; - xmlBuffer *buf; - xmlOutputBuffer *outbuf; - xmlTextWriter *writer; + tchar *doc; int ret; - int first, last; - const xmlChar *content; - int len; - u8 *xml_data; - size_t xml_len; - - /* Open an xmlTextWriter that writes to an in-memory buffer using - * UTF-16LE encoding. */ - - encoding_handler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_UTF16LE); - if (!encoding_handler) { - ERROR("Failed to get XML character encoding handler for UTF-16LE"); - ret = WIMLIB_ERR_LIBXML_UTF16_HANDLER_NOT_AVAILABLE; - goto out; - } - buf = xmlBufferCreate(); - if (!buf) { - ERROR("Failed to create xmlBuffer"); - ret = WIMLIB_ERR_NOMEM; - goto out; - } + ret = utf16le_to_tstr(raw_doc, raw_doc_size, &doc, NULL); + if (ret) + return ret; + ret = xml_parse_document(doc, root_ret); + FREE(doc); + return ret; +} - outbuf = xmlOutputBufferCreateBuffer(buf, encoding_handler); - if (!outbuf) { - ERROR("Failed to allocate xmlOutputBuffer"); - ret = WIMLIB_ERR_NOMEM; - goto out_buffer_free; - } +/* Reads the XML data from a WIM file. */ +int +read_wim_xml_data(WIMStruct *wim) +{ + struct wim_xml_info *info; + void *raw_doc; + size_t raw_doc_size; + struct xml_node *root; + int ret; - writer = xmlNewTextWriter(outbuf); - if (!writer) { - ERROR("Failed to allocate xmlTextWriter"); - ret = WIMLIB_ERR_NOMEM; - goto out_output_buffer_close; - } + /* Allocate the 'struct wim_xml_info'. */ + ret = WIMLIB_ERR_NOMEM; + info = CALLOC(1, sizeof(*info)); + if (!info) + goto err; - /* Write the XML document. */ - - ret = xmlTextWriterStartElement(writer, "WIM"); - if (ret < 0) - goto out_write_error; - - /* The contents of the element in the XML data, under the - * element (not the element), is for non-split WIMs the - * size of the WIM file excluding the XML data and integrity table. - * For split WIMs, takes into account the entire WIM, not - * just the current part. */ - if (total_bytes != WIM_TOTALBYTES_OMIT) { - if (total_bytes == WIM_TOTALBYTES_USE_EXISTING) { - if (wim->wim_info) - total_bytes = wim->wim_info->total_bytes; - else - total_bytes = 0; - } - ret = xmlTextWriterWriteFormatElement(writer, "TOTALBYTES", - "%"PRIu64, total_bytes); - if (ret < 0) - goto out_write_error; + /* Read the raw UTF-16LE XML document. */ + ret = wimlib_get_xml_data(wim, &raw_doc, &raw_doc_size); + if (ret) + goto err; + + /* Parse the document, creating the document tree. */ + ret = parse_wim_xml_document(raw_doc, raw_doc_size, &info->root); + FREE(raw_doc); + raw_doc = NULL; + if (ret) { + if (ret != WIMLIB_ERR_NOMEM) + ret = WIMLIB_ERR_XML; + ERROR("Unable to parse the WIM file's XML document!"); + goto err; } + root = info->root; - if (image == WIMLIB_ALL_IMAGES) { - first = 1; - last = wim->hdr.image_count; - } else { - first = image; - last = image; + /* Verify the root element. */ + if (!xml_node_is_element(root, T("WIM"))) { + ERROR("The WIM file's XML document has an unexpected format!"); + ret = WIMLIB_ERR_XML; + goto err; } - for (int i = first; i <= last; i++) { - ret = xml_write_image_info(writer, &wim->wim_info->images[i - 1]); - if (ret) { - if (ret < 0) - goto out_write_error; - goto out_free_text_writer; - } + /* Verify the WIM file is not encrypted. */ + if (xml_get_element_by_path(root, T("ESD/ENCRYPTED"))) { + ret = WIMLIB_ERR_WIM_IS_ENCRYPTED; + goto err; } - ret = xmlTextWriterEndElement(writer); - if (ret < 0) - goto out_write_error; + /* Validate the image elements and set up the images[] array. */ + ret = setup_images(info, root); + if (ret) + goto err; - ret = xmlTextWriterEndDocument(writer); - if (ret < 0) - goto out_write_error; + /* Success! */ + wim->xml_info = info; + return 0; - ret = xmlTextWriterFlush(writer); - if (ret < 0) - goto out_write_error; +err: + xml_free_info_struct(info); + return ret; +} - /* Retrieve the buffer into which the document was written. */ +/* Swap the INDEX attributes of two IMAGE elements. */ +static void +swap_index_attributes(struct xml_node *image_element_1, + struct xml_node *image_element_2) +{ + struct xml_node *attr_1, *attr_2; - content = xmlBufferContent(buf); - len = xmlBufferLength(buf); + if (image_element_1 != image_element_2) { + attr_1 = unlink_index_attribute(image_element_1); + attr_2 = unlink_index_attribute(image_element_2); + xml_add_child(image_element_1, attr_2); + xml_add_child(image_element_2, attr_1); + } +} - /* Copy the data into a new buffer, and prefix it with the UTF-16LE BOM - * (byte order mark), which is required by MS's software to understand - * the data. */ +static int +prepare_document_for_write(struct wim_xml_info *info, int image, u64 total_bytes, + struct xml_node **orig_totalbytes_element_ret) +{ + struct xml_node *totalbytes_element = NULL; + + /* Allocate the new TOTALBYTES element if needed. */ + if (total_bytes != WIM_TOTALBYTES_USE_EXISTING && + total_bytes != WIM_TOTALBYTES_OMIT) { + totalbytes_element = xml_new_element_with_u64( + NULL, T("TOTALBYTES"), total_bytes); + if (!totalbytes_element) + return WIMLIB_ERR_NOMEM; + } - xml_len = len + 2; - xml_data = MALLOC(xml_len); - if (!xml_data) { - ret = WIMLIB_ERR_NOMEM; - goto out_free_text_writer; + /* Adjust the IMAGE elements if needed. */ + if (image != WIMLIB_ALL_IMAGES) { + /* We're writing a single image only. Temporarily unlink all + * other IMAGE elements from the document. */ + for (int i = 0; i < info->image_count; i++) + if (i + 1 != image) + xml_unlink_node(info->images[i]); + + /* Temporarily set the INDEX attribute of the needed IMAGE + * element to 1. */ + swap_index_attributes(info->images[0], info->images[image - 1]); } - xml_data[0] = 0xff; - xml_data[1] = 0xfe; - memcpy(&xml_data[2], content, len); - - /* Clean up libxml objects and return success. */ - *xml_data_ret = xml_data; - *xml_len_ret = xml_len; - ret = 0; -out_free_text_writer: - /* xmlFreeTextWriter will free the attached xmlOutputBuffer. */ - xmlFreeTextWriter(writer); - goto out_buffer_free; -out_output_buffer_close: - xmlOutputBufferClose(outbuf); -out_buffer_free: - xmlBufferFree(buf); -out: - DEBUG("ret=%d", ret); - return ret; -out_write_error: - ERROR("Error writing XML data"); - ret = WIMLIB_ERR_WRITE; - goto out_free_text_writer; + /* Adjust (add, change, or remove) the TOTALBYTES element if needed. */ + *orig_totalbytes_element_ret = NULL; + if (total_bytes != WIM_TOTALBYTES_USE_EXISTING) { + /* Unlink the previous TOTALBYTES element, if any. */ + *orig_totalbytes_element_ret = xml_get_element_by_path( + info->root, T("TOTALBYTES")); + if (*orig_totalbytes_element_ret) + xml_unlink_node(*orig_totalbytes_element_ret); + + /* Link in the new TOTALBYTES element, if any. */ + if (totalbytes_element) + xml_add_child(info->root, totalbytes_element); + } + return 0; } -/* Writes the XML data to a WIM file. */ +static void +restore_document_after_write(struct wim_xml_info *info, int image, + struct xml_node *orig_totalbytes_element) +{ + /* Restore the IMAGE elements if needed. */ + if (image != WIMLIB_ALL_IMAGES) { + /* We wrote a single image only. Re-link all other IMAGE + * elements to the document. */ + for (int i = 0; i < info->image_count; i++) + if (i + 1 != image) + xml_add_child(info->root, info->images[i]); + + /* Restore the original INDEX attributes. */ + swap_index_attributes(info->images[0], info->images[image - 1]); + } + + /* Restore the original TOTALBYTES element if needed. */ + if (orig_totalbytes_element) + xml_replace_child(info->root, orig_totalbytes_element); +} + +/* + * Writes the XML data to a WIM file. + * + * 'image' specifies the image(s) to include in the XML data. Normally it is + * WIMLIB_ALL_IMAGES, but it can also be a 1-based image index. + * + * 'total_bytes' is the number to use in the top-level TOTALBYTES element, or + * WIM_TOTALBYTES_USE_EXISTING to use the existing value from the XML document + * (if any), or WIM_TOTALBYTES_OMIT to omit the TOTALBYTES element entirely. + */ int write_wim_xml_data(WIMStruct *wim, int image, u64 total_bytes, - struct wim_reshdr *out_reshdr, - int write_resource_flags) + struct wim_reshdr *out_reshdr, int write_resource_flags) { + struct wim_xml_info *info = wim->xml_info; int ret; - u8 *xml_data; - size_t xml_len; + struct xml_node *orig_totalbytes_element; + struct xml_out_buf buf = {}; + const utf16lechar *raw_doc; + size_t raw_doc_size; + + /* Make any needed temporary changes to the document. */ + ret = prepare_document_for_write(info, image, total_bytes, + &orig_totalbytes_element); + if (ret) + goto out; - DEBUG("Writing WIM XML data (image=%d, offset=%"PRIu64")", - image, wim->out_fd.offset); + ret = xml_write_document(info->root, &buf); + if (ret) + goto out_restore_document; - ret = prepare_wim_xml_data(wim, image, total_bytes, - &xml_data, &xml_len); + ret = tstr_get_utf16le_and_len(buf.buf, &raw_doc, &raw_doc_size); if (ret) - return ret; + goto out_restore_document; /* Write the XML data uncompressed. Although wimlib can handle - * compressed XML data, MS software cannot. */ - ret = write_wim_resource_from_buffer(xml_data, - xml_len, - WIM_RESHDR_FLAG_METADATA, + * compressed XML data, some other WIM software cannot. */ + ret = write_wim_resource_from_buffer(raw_doc, raw_doc_size, + true, &wim->out_fd, WIMLIB_COMPRESSION_TYPE_NONE, 0, out_reshdr, NULL, write_resource_flags); - FREE(xml_data); - DEBUG("ret=%d", ret); + tstr_put_utf16le(raw_doc); +out_restore_document: + /* Revert any temporary changes we made to the document. */ + restore_document_after_write(info, image, orig_totalbytes_element); + FREE(buf.buf); +out: return ret; } -/* API function documented in wimlib.h */ -WIMLIBAPI const tchar * -wimlib_get_image_name(const WIMStruct *wim, int image) -{ - if (image < 1 || image > wim->hdr.image_count) - return NULL; - return wim->wim_info->images[image - 1].name; -} - -/* API function documented in wimlib.h */ -WIMLIBAPI const tchar * -wimlib_get_image_description(const WIMStruct *wim, int image) -{ - if (image < 1 || image > wim->hdr.image_count) - return NULL; - return wim->wim_info->images[image - 1].description; -} - -/* API function documented in wimlib.h */ -WIMLIBAPI bool -wimlib_image_name_in_use(const WIMStruct *wim, const tchar *name) -{ - if (!name || !*name) - return false; - for (int i = 1; i <= wim->hdr.image_count; i++) - if (!tstrcmp(wim->wim_info->images[i - 1].name, name)) - return true; - return false; -} - +/*----------------------------------------------------------------------------* + * Library API functions * + *----------------------------------------------------------------------------*/ -/* API function documented in wimlib.h */ WIMLIBAPI int wimlib_get_xml_data(WIMStruct *wim, void **buf_ret, size_t *bufsize_ret) { const struct wim_reshdr *xml_reshdr; if (wim->filename == NULL && filedes_is_seekable(&wim->in_fd)) - return WIMLIB_ERR_INVALID_PARAM; + return WIMLIB_ERR_NO_FILENAME; if (buf_ret == NULL || bufsize_ret == NULL) return WIMLIB_ERR_INVALID_PARAM; xml_reshdr = &wim->hdr.xml_data_reshdr; - DEBUG("Reading XML data."); *bufsize_ret = xml_reshdr->uncompressed_size; return wim_reshdr_to_data(xml_reshdr, wim, buf_ret); } @@ -1662,75 +1126,111 @@ wimlib_extract_xml_data(WIMStruct *wim, FILE *fp) return ret; } -/* API function documented in wimlib.h */ -WIMLIBAPI int -wimlib_set_image_name(WIMStruct *wim, int image, const tchar *name) +static bool +image_name_in_use(const WIMStruct *wim, const tchar *name, int excluded_image) { - tchar *p; - int i; - - if (name == NULL) - name = T(""); + const struct wim_xml_info *info = wim->xml_info; + const tchar *existing_name; - if (image < 1 || image > wim->hdr.image_count) - return WIMLIB_ERR_INVALID_IMAGE; + /* Any number of images can have "no name". */ + if (!name || !*name) + return false; - if (*name) { - for (i = 1; i <= wim->hdr.image_count; i++) { - if (i == image) - continue; - if (!tstrcmp(wim->wim_info->images[i - 1].name, name)) - return WIMLIB_ERR_IMAGE_NAME_COLLISION; - } + /* Check for images that have the specified name. */ + for (int i = 0; i < info->image_count; i++) { + if (i + 1 == excluded_image) + continue; + existing_name = xml_get_text_by_path(info->images[i], + T("NAME")); + if (existing_name && !tstrcmp(existing_name, name)) + return true; } + return false; +} - p = TSTRDUP(name); - if (!p) - return WIMLIB_ERR_NOMEM; +WIMLIBAPI bool +wimlib_image_name_in_use(const WIMStruct *wim, const tchar *name) +{ + return image_name_in_use(wim, name, WIMLIB_NO_IMAGE); +} - FREE(wim->wim_info->images[image - 1].name); - wim->wim_info->images[image - 1].name = p; - return 0; +WIMLIBAPI const tchar * +wimlib_get_image_name(const WIMStruct *wim, int image) +{ + const struct wim_xml_info *info = wim->xml_info; + const tchar *name; + + if (image < 1 || image > info->image_count) + return NULL; + name = wimlib_get_image_property(wim, image, T("NAME")); + return name ? name : T(""); } -static int -do_set_image_info_str(WIMStruct *wim, int image, const tchar *tstr, - size_t offset) +WIMLIBAPI const tchar * +wimlib_get_image_description(const WIMStruct *wim, int image) { - tchar *tstr_copy; - tchar **dest_tstr_p; + return wimlib_get_image_property(wim, image, T("DESCRIPTION")); +} - if (image < 1 || image > wim->hdr.image_count) { - ERROR("%d is not a valid image", image); - return WIMLIB_ERR_INVALID_IMAGE; - } - if (tstr) { - tstr_copy = TSTRDUP(tstr); - if (!tstr_copy) - return WIMLIB_ERR_NOMEM; - } else { - tstr_copy = NULL; - } - dest_tstr_p = (tchar**)((void*)&wim->wim_info->images[image - 1] + offset); +WIMLIBAPI const tchar * +wimlib_get_image_property(const WIMStruct *wim, int image, + const tchar *property_name) +{ + const struct wim_xml_info *info = wim->xml_info; - FREE(*dest_tstr_p); - *dest_tstr_p = tstr_copy; - return 0; + if (!property_name || !*property_name) + return NULL; + if (image < 1 || image > info->image_count) + return NULL; + return xml_get_text_by_path(info->images[image - 1], property_name); +} + +WIMLIBAPI int +wimlib_set_image_name(WIMStruct *wim, int image, const tchar *name) +{ + return wimlib_set_image_property(wim, image, T("NAME"), name); } -/* API function documented in wimlib.h */ WIMLIBAPI int -wimlib_set_image_descripton(WIMStruct *wim, int image, - const tchar *description) +wimlib_set_image_descripton(WIMStruct *wim, int image, const tchar *description) { - return do_set_image_info_str(wim, image, description, - offsetof(struct image_info, description)); + return wimlib_set_image_property(wim, image, T("DESCRIPTION"), + description); } -/* API function documented in wimlib.h */ WIMLIBAPI int wimlib_set_image_flags(WIMStruct *wim, int image, const tchar *flags) { - return do_set_image_info_str(wim, image, flags, - offsetof(struct image_info, flags)); + return wimlib_set_image_property(wim, image, T("FLAGS"), flags); +} + +WIMLIBAPI int +wimlib_set_image_property(WIMStruct *wim, int image, const tchar *property_name, + const tchar *property_value) +{ + struct wim_xml_info *info = wim->xml_info; + + if (!property_name || !*property_name) + return WIMLIB_ERR_INVALID_PARAM; + + if (!xml_legal_path(property_name)) { + ERROR("Property name '%"TS"' is illegal in XML", property_name); + return WIMLIB_ERR_INVALID_PARAM; + } + + if (property_value && !xml_legal_value(property_value)) { + WARNING("Value of property '%"TS"' contains illegal characters", + property_name); + return WIMLIB_ERR_INVALID_PARAM; + } + + if (image < 1 || image > info->image_count) + return WIMLIB_ERR_INVALID_IMAGE; + + if (!tstrcmp(property_name, T("NAME")) && + image_name_in_use(wim, property_value, image)) + return WIMLIB_ERR_IMAGE_NAME_COLLISION; + + return xml_set_text_by_path(info->images[image - 1], property_name, + property_value); }