From bf46a167114ce24712c56a9c050ed216dfeaf671 Mon Sep 17 00:00:00 2001 From: Hans Leidekker Date: Mon, 2 Dec 2019 10:11:50 +0100 Subject: [PATCH] wusa: Parse assembly manifests. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on patches by Michael Müller and Sebastian Lackner. Signed-off-by: Hans Leidekker Signed-off-by: Alexandre Julliard --- programs/wusa/Makefile.in | 5 +- programs/wusa/main.c | 87 ++++++ programs/wusa/manifest.c | 567 ++++++++++++++++++++++++++++++++++++++ programs/wusa/wusa.h | 65 +++++ 4 files changed, 722 insertions(+), 2 deletions(-) create mode 100644 programs/wusa/manifest.c diff --git a/programs/wusa/Makefile.in b/programs/wusa/Makefile.in index 5e60bdf1b06..87bfe1dae06 100644 --- a/programs/wusa/Makefile.in +++ b/programs/wusa/Makefile.in @@ -1,7 +1,8 @@ MODULE = wusa.exe -IMPORTS = cabinet shlwapi +IMPORTS = cabinet shlwapi ole32 oleaut32 EXTRADLLFLAGS = -mconsole -municode -mno-cygwin C_SRCS = \ - main.c + main.c \ + manifest.c diff --git a/programs/wusa/main.c b/programs/wusa/main.c index a719eefc5b7..a1cdea4ee88 100644 --- a/programs/wusa/main.c +++ b/programs/wusa/main.c @@ -40,6 +40,7 @@ struct installer_state BOOL norestart; BOOL quiet; struct list tempdirs; + struct list assemblies; }; static void * CDECL cabinet_alloc(ULONG cb) @@ -315,6 +316,7 @@ static BOOL delete_directory(const WCHAR *path) static void installer_cleanup(struct installer_state *state) { struct installer_tempdir *tempdir, *tempdir2; + struct assembly_entry *assembly, *assembly2; LIST_FOR_EACH_ENTRY_SAFE(tempdir, tempdir2, &state->tempdirs, struct installer_tempdir, entry) { @@ -323,14 +325,79 @@ static void installer_cleanup(struct installer_state *state) heap_free(tempdir->path); heap_free(tempdir); } + LIST_FOR_EACH_ENTRY_SAFE(assembly, assembly2, &state->assemblies, struct assembly_entry, entry) + { + list_remove(&assembly->entry); + free_assembly(assembly); + } +} + +static BOOL str_ends_with(const WCHAR *str, const WCHAR *suffix) +{ + DWORD str_len = lstrlenW(str), suffix_len = lstrlenW(suffix); + if (suffix_len > str_len) return FALSE; + return !wcsicmp(str + str_len - suffix_len, suffix); +} + +static BOOL load_assemblies_from_cab(const WCHAR *filename, struct installer_state *state) +{ + struct assembly_entry *assembly; + const WCHAR *temp_path; + WIN32_FIND_DATAW data; + HANDLE search; + WCHAR *path; + + TRACE("Processing cab file %s\n", debugstr_w(filename)); + + if (!(temp_path = create_temp_directory(state))) return FALSE; + if (!extract_cabinet(filename, temp_path)) + { + ERR("Failed to extract %s\n", debugstr_w(filename)); + return FALSE; + } + + if (!(path = path_combine(temp_path, L"_manifest_.cix.xml"))) return FALSE; + if (GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES) + { + FIXME("Cabinet uses proprietary msdelta file compression which is not (yet) supported\n"); + FIXME("Installation of msu file will most likely fail\n"); + } + heap_free(path); + + if (!(path = path_combine(temp_path, L"*"))) return FALSE; + search = FindFirstFileW(path, &data); + heap_free(path); + + if (search != INVALID_HANDLE_VALUE) + { + do + { + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; + if (!str_ends_with(data.cFileName, L".manifest") && + !str_ends_with(data.cFileName, L".mum")) continue; + if (!(path = path_combine(temp_path, data.cFileName))) continue; + if ((assembly = load_manifest(path))) + list_add_tail(&state->assemblies, &assembly->entry); + heap_free(path); + } + while (FindNextFileW(search, &data)); + FindClose(search); + } + + return TRUE; } static BOOL install_msu(const WCHAR *filename, struct installer_state *state) { const WCHAR *temp_path; + WIN32_FIND_DATAW data; + HANDLE search; + WCHAR *path; BOOL ret = FALSE; list_init(&state->tempdirs); + list_init(&state->assemblies); + CoInitialize(NULL); TRACE("Processing msu file %s\n", debugstr_w(filename)); @@ -341,6 +408,26 @@ static BOOL install_msu(const WCHAR *filename, struct installer_state *state) goto done; } + /* load all manifests from contained cabinet archives */ + if (!(path = path_combine(temp_path, L"*.cab"))) goto done; + search = FindFirstFileW(path, &data); + heap_free(path); + + if (search != INVALID_HANDLE_VALUE) + { + do + { + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; + if (!wcsicmp(data.cFileName, L"WSUSSCAN.cab")) continue; + if (!(path = path_combine(temp_path, data.cFileName))) continue; + if (!load_assemblies_from_cab(path, state)) + ERR("Failed to load all manifests from %s, ignoring\n", debugstr_w(path)); + heap_free(path); + } + while (FindNextFileW(search, &data)); + FindClose(search); + } + ret = TRUE; done: diff --git a/programs/wusa/manifest.c b/programs/wusa/manifest.c new file mode 100644 index 00000000000..44c263f6c0e --- /dev/null +++ b/programs/wusa/manifest.c @@ -0,0 +1,567 @@ +/* + * Manifest parser for WUSA + * + * Copyright 2015 Michael Müller + * Copyright 2015 Sebastian Lackner + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include +#define COBJMACROS +#include +#include + +#include "wine/debug.h" +#include "wine/list.h" +#include "wusa.h" + +WINE_DEFAULT_DEBUG_CHANNEL(wusa); + +static struct dependency_entry *alloc_dependency(void) +{ + struct dependency_entry *entry = heap_alloc_zero(sizeof(*entry)); + if (!entry) ERR("Failed to allocate memory for dependency\n"); + return entry; +} + +static struct fileop_entry *alloc_fileop(void) +{ + struct fileop_entry *entry = heap_alloc_zero(sizeof(*entry)); + if (!entry) ERR("Failed to allocate memory for fileop\n"); + return entry; +} + +static struct registrykv_entry *alloc_registrykv(void) +{ + struct registrykv_entry *entry = heap_alloc_zero(sizeof(*entry)); + if (!entry) ERR("Failed to allocate memory for registrykv\n"); + return entry; +} + +static struct registryop_entry *alloc_registryop(void) +{ + struct registryop_entry *entry = heap_alloc_zero(sizeof(*entry)); + if (!entry) ERR("Failed to allocate memory for registryop\n"); + else + { + list_init(&entry->keyvalues); + } + return entry; +} + +static struct assembly_entry *alloc_assembly(void) +{ + struct assembly_entry *entry = heap_alloc_zero(sizeof(*entry)); + if (!entry) ERR("Failed to allocate memory for assembly\n"); + else + { + list_init(&entry->dependencies); + list_init(&entry->fileops); + list_init(&entry->registryops); + } + return entry; +} + +static void clear_identity(struct assembly_identity *entry) +{ + heap_free(entry->name); + heap_free(entry->version); + heap_free(entry->architecture); + heap_free(entry->language); + heap_free(entry->pubkey_token); +} + +void free_dependency(struct dependency_entry *entry) +{ + clear_identity(&entry->identity); + heap_free(entry); +} + +static void free_fileop(struct fileop_entry *entry) +{ + heap_free(entry->source); + heap_free(entry->target); + heap_free(entry); +} + +static void free_registrykv(struct registrykv_entry *entry) +{ + heap_free(entry->name); + heap_free(entry->value_type); + heap_free(entry->value); + heap_free(entry); +} + +static void free_registryop(struct registryop_entry *entry) +{ + struct registrykv_entry *keyvalue, *keyvalue2; + + heap_free(entry->key); + + LIST_FOR_EACH_ENTRY_SAFE(keyvalue, keyvalue2, &entry->keyvalues, struct registrykv_entry, entry) + { + list_remove(&keyvalue->entry); + free_registrykv(keyvalue); + } + + heap_free(entry); +} + +void free_assembly(struct assembly_entry *entry) +{ + struct dependency_entry *dependency, *dependency2; + struct fileop_entry *fileop, *fileop2; + struct registryop_entry *registryop, *registryop2; + + heap_free(entry->filename); + heap_free(entry->displayname); + clear_identity(&entry->identity); + + LIST_FOR_EACH_ENTRY_SAFE(dependency, dependency2, &entry->dependencies, struct dependency_entry, entry) + { + list_remove(&dependency->entry); + free_dependency(dependency); + } + LIST_FOR_EACH_ENTRY_SAFE(fileop, fileop2, &entry->fileops, struct fileop_entry, entry) + { + list_remove(&fileop->entry); + free_fileop(fileop); + } + LIST_FOR_EACH_ENTRY_SAFE(registryop, registryop2, &entry->registryops, struct registryop_entry, entry) + { + list_remove(®istryop->entry); + free_registryop(registryop); + } + + heap_free(entry); +} + +static WCHAR *get_xml_attribute(IXMLDOMElement *root, const WCHAR *name) +{ + WCHAR *ret = NULL; + VARIANT var; + BSTR bstr; + + if ((bstr = SysAllocString(name))) + { + VariantInit(&var); + if (SUCCEEDED(IXMLDOMElement_getAttribute(root, bstr, &var))) + { + ret = (V_VT(&var) == VT_BSTR) ? strdupW(V_BSTR(&var)) : NULL; + VariantClear(&var); + } + SysFreeString(bstr); + } + + return ret; +} + +static BOOL check_xml_tagname(IXMLDOMElement *root, const WCHAR *tagname) +{ + BOOL ret = FALSE; + BSTR bstr; + + if (SUCCEEDED(IXMLDOMElement_get_tagName(root, &bstr))) + { + ret = !wcscmp(bstr, tagname); + SysFreeString(bstr); + } + + return ret; +} + +static IXMLDOMElement *select_xml_node(IXMLDOMElement *root, const WCHAR *name) +{ + IXMLDOMElement *ret = NULL; + IXMLDOMNode *node; + BSTR bstr; + + if ((bstr = SysAllocString(name))) + { + if (SUCCEEDED(IXMLDOMElement_selectSingleNode(root, bstr, &node))) + { + if (FAILED(IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void **)&ret))) + ret = NULL; + IXMLDOMNode_Release(node); + } + SysFreeString(bstr); + } + + return ret; +} + +static BOOL call_xml_callbacks(IXMLDOMElement *root, BOOL (*func)(IXMLDOMElement *child, WCHAR *tagname, void *context), void *context) +{ + IXMLDOMNodeList *children; + IXMLDOMElement *child; + IXMLDOMNode *node; + BSTR tagname; + BOOL ret = TRUE; + + if (FAILED(IXMLDOMElement_get_childNodes(root, &children))) + return FALSE; + + while (ret && IXMLDOMNodeList_nextNode(children, &node) == S_OK) + { + if (SUCCEEDED(IXMLDOMNode_QueryInterface(node, &IID_IXMLDOMElement, (void **)&child))) + { + if (SUCCEEDED(IXMLDOMElement_get_tagName(child, &tagname))) + { + ret = func(child, tagname, context); + SysFreeString(tagname); + } + IXMLDOMElement_Release(child); + } + IXMLDOMNode_Release(node); + } + + IXMLDOMNodeList_Release(children); + return ret; +} + +static IXMLDOMElement *load_xml(const WCHAR *filename) +{ + IXMLDOMDocument *document = NULL; + IXMLDOMElement *root = NULL; + VARIANT_BOOL success; + VARIANT variant; + BSTR bstr; + + TRACE("Loading XML from %s\n", debugstr_w(filename)); + + if (!(bstr = SysAllocString(filename))) + return FALSE; + + if (SUCCEEDED(CoCreateInstance(&CLSID_DOMDocument, NULL, CLSCTX_INPROC_SERVER, &IID_IXMLDOMDocument, (void **)&document))) + { + VariantInit(&variant); + V_VT(&variant) = VT_BSTR; + V_BSTR(&variant) = bstr; + + if (SUCCEEDED(IXMLDOMDocument_load(document, variant, &success)) && success) + { + if (FAILED(IXMLDOMDocument_get_documentElement(document, &root))) + root = NULL; + } + IXMLDOMDocument_Release(document); + } + + SysFreeString(bstr); + return root; +} + +static BOOL read_identity(IXMLDOMElement *root, struct assembly_identity *identity) +{ + memset(identity, 0, sizeof(*identity)); + if (!(identity->name = get_xml_attribute(root, L"name"))) goto error; + if (!(identity->version = get_xml_attribute(root, L"version"))) goto error; + if (!(identity->architecture = get_xml_attribute(root, L"processorArchitecture"))) goto error; + if (!(identity->language = get_xml_attribute(root, L"language"))) goto error; + if (!(identity->pubkey_token = get_xml_attribute(root, L"publicKeyToken"))) goto error; + return TRUE; + +error: + clear_identity(identity); + return FALSE; +} + +/* */ +static BOOL read_dependent_assembly(IXMLDOMElement *root, struct assembly_identity *identity) +{ + IXMLDOMElement *child = NULL; + WCHAR *dependency_type; + BOOL ret = FALSE; + + if (!(dependency_type = get_xml_attribute(root, L"dependencyType"))) + { + WARN("Failed to get dependency type, assuming install\n"); + } + if (dependency_type && wcscmp(dependency_type, L"install") && wcscmp(dependency_type, L"prerequisite")) + { + FIXME("Unimplemented dependency type %s\n", debugstr_w(dependency_type)); + goto error; + } + if (!(child = select_xml_node(root, L".//assemblyIdentity"))) + { + FIXME("Failed to find assemblyIdentity child node\n"); + goto error; + } + + ret = read_identity(child, identity); + +error: + if (child) IXMLDOMElement_Release(child); + heap_free(dependency_type); + return ret; +} + +/* */ +static BOOL read_dependency(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct assembly_entry *assembly = context; + struct dependency_entry *entry; + + if (wcscmp(tagname, L"dependentAssembly")) + { + FIXME("Don't know how to handle dependency tag %s\n", debugstr_w(tagname)); + return FALSE; + } + + if ((entry = alloc_dependency())) + { + if (read_dependent_assembly(child, &entry->identity)) + { + TRACE("Found dependency %s\n", debugstr_w(entry->identity.name)); + list_add_tail(&assembly->dependencies, &entry->entry); + return TRUE; + } + free_dependency(entry); + } + + return FALSE; +} + +static BOOL iter_dependency(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_dependency, assembly); +} + +/* */ +/* */ +static BOOL read_components(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct assembly_entry *assembly = context; + struct dependency_entry *entry; + + if (wcscmp(tagname, L"assemblyIdentity")) + { + FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); + return TRUE; + } + + if ((entry = alloc_dependency())) + { + if (read_identity(child, &entry->identity)) + { + TRACE("Found identity %s\n", debugstr_w(entry->identity.name)); + list_add_tail(&assembly->dependencies, &entry->entry); + return TRUE; + } + free_dependency(entry); + } + + return FALSE; +} + +static BOOL iter_components(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_components, assembly); +} + +/* */ +static BOOL read_update(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct assembly_entry *assembly = context; + + if (!wcscmp(tagname, L"component")) + return iter_components(child, assembly); + if (!wcscmp(tagname, L"package")) + return iter_components(child, assembly); + if (!wcscmp(tagname, L"applicable")) + return TRUE; + + FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); + return FALSE; +} + +static BOOL iter_update(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_update, assembly); +} + +/* */ +static BOOL read_package(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct assembly_entry *assembly = context; + + if (!wcscmp(tagname, L"update")) + return iter_update(child, assembly); + if (!wcscmp(tagname, L"parent")) + return TRUE; + + FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); + return TRUE; +} + +static BOOL iter_package(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_package, assembly); +} + +/* */ +static BOOL read_file(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + struct fileop_entry *entry; + + if (!(entry = alloc_fileop())) + return FALSE; + + if (!(entry->source = get_xml_attribute(root, L"sourceName"))) goto error; + if (!(entry->target = get_xml_attribute(root, L"destinationPath"))) goto error; + + TRACE("Found fileop %s -> %s\n", debugstr_w(entry->source), debugstr_w(entry->target)); + list_add_tail(&assembly->fileops, &entry->entry); + return TRUE; + +error: + free_fileop(entry); + return FALSE; +} + +/* */ +static BOOL read_registry_key(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct registryop_entry *registryop = context; + struct registrykv_entry *entry; + + if (!wcscmp(tagname, L"securityDescriptor")) return TRUE; + if (!wcscmp(tagname, L"systemProtection")) return TRUE; + if (wcscmp(tagname, L"registryValue")) + { + FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); + return TRUE; + } + + if (!(entry = alloc_registrykv())) + return FALSE; + + if (!(entry->value_type = get_xml_attribute(child, L"valueType"))) goto error; + entry->name = get_xml_attribute(child, L"name"); /* optional */ + entry->value = get_xml_attribute(child, L"value"); /* optional */ + + TRACE("Found registry %s -> %s\n", debugstr_w(entry->name), debugstr_w(entry->value)); + list_add_tail(®istryop->keyvalues, &entry->entry); + return TRUE; + +error: + free_registrykv(entry); + return FALSE; +} + +static BOOL iter_registry_key(IXMLDOMElement *root, struct registryop_entry *registryop) +{ + return call_xml_callbacks(root, read_registry_key, registryop); +} + +/* */ +static BOOL read_registry_keys(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct assembly_entry *assembly = context; + struct registryop_entry *entry; + WCHAR *keyname; + + if (wcscmp(tagname, L"registryKey")) + { + FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); + return TRUE; + } + + if (!(keyname = get_xml_attribute(child, L"keyName"))) + { + FIXME("RegistryKey tag doesn't specify keyName\n"); + return FALSE; + } + + if ((entry = alloc_registryop())) + { + list_init(&entry->keyvalues); + if (iter_registry_key(child, entry)) + { + entry->key = keyname; + TRACE("Found registryop %s\n", debugstr_w(entry->key)); + list_add_tail(&assembly->registryops, &entry->entry); + return TRUE; + } + free_registryop(entry); + } + + heap_free(keyname); + return FALSE; +} + +static BOOL iter_registry_keys(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_registry_keys, assembly); +} + +/* */ +static BOOL read_assembly(IXMLDOMElement *child, WCHAR *tagname, void *context) +{ + struct assembly_entry *assembly = context; + + if (!wcscmp(tagname, L"assemblyIdentity") && !assembly->identity.name) + return read_identity(child, &assembly->identity); + if (!wcscmp(tagname, L"dependency")) + return iter_dependency(child, assembly); + if (!wcscmp(tagname, L"package")) + return iter_package(child, assembly); + if (!wcscmp(tagname, L"file")) + return read_file(child, assembly); + if (!wcscmp(tagname, L"registryKeys")) + return iter_registry_keys(child, assembly); + if (!wcscmp(tagname, L"trustInfo")) + return TRUE; + if (!wcscmp(tagname, L"configuration")) + return TRUE; + if (!wcscmp(tagname, L"deployment")) + return TRUE; + + FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); + return TRUE; +} + +static BOOL iter_assembly(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_assembly, assembly); +} + +struct assembly_entry *load_manifest(const WCHAR *filename) +{ + struct assembly_entry *entry = NULL; + IXMLDOMElement *root = NULL; + + TRACE("Loading manifest %s\n", debugstr_w(filename)); + + if (!(root = load_xml(filename))) return NULL; + if (!check_xml_tagname(root, L"assembly")) + { + FIXME("Didn't find assembly root node?\n"); + goto done; + } + + if ((entry = alloc_assembly())) + { + entry->filename = strdupW(filename); + entry->displayname = get_xml_attribute(root, L"displayName"); + if (iter_assembly(root, entry)) goto done; + free_assembly(entry); + entry = NULL; + } + +done: + IXMLDOMElement_Release(root); + return entry; +} diff --git a/programs/wusa/wusa.h b/programs/wusa/wusa.h index c6bf7eda11e..2c81b639b91 100644 --- a/programs/wusa/wusa.h +++ b/programs/wusa/wusa.h @@ -17,12 +17,77 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ +enum +{ + ASSEMBLY_STATUS_NONE, + ASSEMBLY_STATUS_IN_PROGRESS, + ASSEMBLY_STATUS_INSTALLED, +}; + +struct assembly_identity +{ + WCHAR *name; + WCHAR *version; + WCHAR *architecture; + WCHAR *language; + WCHAR *pubkey_token; +}; + +struct dependency_entry +{ + struct list entry; + struct assembly_identity identity; +}; + +struct fileop_entry +{ + struct list entry; + WCHAR *source; + WCHAR *target; +}; + +struct registrykv_entry +{ + struct list entry; + WCHAR *name; + WCHAR *value_type; + WCHAR *value; +}; + +struct registryop_entry +{ + struct list entry; + WCHAR *key; + struct list keyvalues; +}; + +struct assembly_entry +{ + struct list entry; + DWORD status; + WCHAR *filename; + WCHAR *displayname; + struct assembly_identity identity; + struct list dependencies; + struct list fileops; + struct list registryops; +}; + +void free_assembly(struct assembly_entry *entry) DECLSPEC_HIDDEN; +struct assembly_entry *load_manifest(const WCHAR *filename) DECLSPEC_HIDDEN; + static void *heap_alloc(size_t len) __WINE_ALLOC_SIZE(1); static inline void *heap_alloc(size_t len) { return HeapAlloc(GetProcessHeap(), 0, len); } +static void *heap_alloc_zero(size_t len) __WINE_ALLOC_SIZE(1); +static inline void *heap_alloc_zero(size_t len) +{ + return HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len); +} + static inline BOOL heap_free(void *mem) { return HeapFree(GetProcessHeap(), 0, mem);