[PATCH 4/4] wusa: Implement installation of msu files

Vijay Kiran Kamuju infyquest at gmail.com
Thu Mar 22 09:28:10 CDT 2018


From: Michael Müller <michael at fds-team.de>

Signed-off-by: Vijay Kiran Kamuju <infyquest at gmail.com>
---
 programs/wusa/Makefile.in |   2 +-
 programs/wusa/main.c      | 548 +++++++++++++++++++++++++++++++++++++++++++++-
 programs/wusa/wusa.h      |  13 ++
 3 files changed, 559 insertions(+), 4 deletions(-)

diff --git a/programs/wusa/Makefile.in b/programs/wusa/Makefile.in
index 05c7bb2594f..50be1c77ca4 100644
--- a/programs/wusa/Makefile.in
+++ b/programs/wusa/Makefile.in
@@ -1,6 +1,6 @@
 MODULE    = wusa.exe
 APPMODE   = -mconsole -municode
-IMPORTS   = cabinet shlwapi oleaut32 ole32
+IMPORTS   = cabinet shlwapi oleaut32 ole32 advapi32
 
 C_SRCS = \
 	main.c \
diff --git a/programs/wusa/main.c b/programs/wusa/main.c
index 4652e9ae081..ba01c8e7204 100644
--- a/programs/wusa/main.c
+++ b/programs/wusa/main.c
@@ -42,6 +42,13 @@ WINE_DEFAULT_DEBUG_CHANNEL(wusa);
 #define _O_TEXT        0x4000
 #define _O_BINARY      0x8000
 
+struct strbuf
+{
+    WCHAR *buf;
+    DWORD pos;
+    DWORD len;
+};
+
 struct installer_tempdir {
    struct list entry;
    WCHAR *path;
@@ -55,6 +62,51 @@ struct installer_state {
     struct list updates;
 };
 
+static BOOL strbuf_init(struct strbuf *buf)
+{
+    buf->pos = 0;
+    buf->len = 64;
+    buf->buf = heap_alloc(buf->len * sizeof(WCHAR));
+    return buf->buf != NULL;
+}
+
+static void strbuf_free(struct strbuf *buf)
+{
+    heap_free(buf->buf);
+    buf->buf = NULL;
+}
+
+static BOOL strbuf_append(struct strbuf *buf, const WCHAR *str, DWORD len)
+{
+    DWORD new_len;
+    WCHAR *new_buf;
+
+    if (!buf->buf)
+        return FALSE;
+    if (!str)
+        return TRUE;
+
+    if (len == ~0U)
+        len = strlenW(str);
+    if (buf->pos + len + 1 > buf->len)
+    {
+        new_len = max(buf->pos + len + 1, buf->len * 2);
+        new_buf = heap_realloc(buf->buf, new_len * sizeof(WCHAR));
+        if (!new_buf)
+        {
+            strbuf_free(buf);
+            return FALSE;
+        }
+        buf->buf = new_buf;
+        buf->len = new_len;
+    }
+
+    memcpy(&buf->buf[buf->pos], str, len * sizeof(WCHAR));
+    buf->buf[buf->pos + len] = 0;
+    buf->pos += len;
+    return TRUE;
+}
+
 static BOOL str_ends_with(const WCHAR *str, const WCHAR *suffix)
 {
     DWORD str_len = strlenW(str), suffix_len = strlenW(suffix);
@@ -328,6 +380,456 @@ static BOOL extract_cabinet(const WCHAR *filename, const WCHAR *destination)
     return ret;
 }
 
+static WCHAR *lookup_expression(struct assembly_entry *assembly, const WCHAR *key)
+{
+    static const WCHAR runtime_system32[] = {'r','u','n','t','i','m','e','.','s','y','s','t','e','m','3','2',0};
+    static const WCHAR runtime_windows[] = {'r','u','n','t','i','m','e','.','w','i','n','d','o','w','s',0};
+    WCHAR path[MAX_PATH];
+
+    if (!strcmpW(key, runtime_system32))
+    {
+        GetSystemDirectoryW(path, sizeof(path)/sizeof(path[0]));
+        return strdupW(path);
+    }
+    if (!strcmpW(key, runtime_windows))
+    {
+        GetWindowsDirectoryW(path, sizeof(path)/sizeof(path[0]));
+        return strdupW(path);
+    }
+
+    WINE_FIXME("Unknown expression %s\n", wine_dbgstr_w(key));
+    return NULL;
+}
+
+static WCHAR *expand_expression(struct assembly_entry *assembly, const WCHAR *expression)
+{
+    static const WCHAR beginW[] = {'$','(',0};
+    static const WCHAR endW[] = {')',0};
+    const WCHAR *pos, *next;
+    WCHAR *key, *value;
+    struct strbuf buf;
+
+    if (!expression || !strbuf_init(&buf))
+        return NULL;
+
+    for (pos = expression; (next = strstrW(pos, beginW)); pos = next + 1)
+    {
+        strbuf_append(&buf, pos, next - pos);
+        pos = next + 2;
+        if (!(next = strstrW(pos, endW)))
+        {
+            strbuf_append(&buf, beginW, 2);
+            break;
+        }
+
+        if (!(key = strdupWn(pos, next - pos)))
+            goto error;
+        value = lookup_expression(assembly, key);
+        heap_free(key);
+        if (!value)
+            goto error;
+        strbuf_append(&buf, value, ~0U);
+        heap_free(value);
+    }
+
+    strbuf_append(&buf, pos, ~0U);
+    return buf.buf;
+
+error:
+    WINE_FIXME("Couldn't resolve expression %s\n", wine_dbgstr_w(expression));
+    strbuf_free(&buf);
+    return NULL;
+}
+
+static WCHAR *get_assembly_source(struct assembly_entry *assembly)
+{
+    WCHAR *p, *path = strdupW(assembly->filename);
+    if (path && (p = strrchrW(path, '.')))
+        *p = 0;
+    return path;
+}
+
+static BOOL install_files_copy(struct assembly_entry *assembly, const WCHAR *source_path, struct fileop_entry *fileop, BOOL dryrun)
+{
+    WCHAR *target_path, *target, *source = NULL;
+    BOOL ret = FALSE;
+
+    if (!(target_path = expand_expression(assembly, fileop->target)))
+        return FALSE;
+    if (!(target = (WCHAR *)path_combine(target_path, fileop->source)))
+        goto error;
+    if (!(source = (WCHAR *)path_combine(source_path, fileop->source)))
+        goto error;
+
+    if (dryrun)
+    {
+        if (!(ret = PathFileExistsW(source)))
+        {
+            WINE_ERR("Required file %s not found\n", wine_dbgstr_w(source));
+            goto error;
+        }
+    }
+    else
+    {
+        if (!create_parent_directory(target))
+        {
+            WINE_ERR("Failed to create parent directory for %s\n", wine_dbgstr_w(target));
+            goto error;
+        }
+        if (!(ret = CopyFileExW(source, target, NULL, NULL, NULL, 0)))
+        {
+            WINE_ERR("Failed to copy %s to %s\n", wine_dbgstr_w(source), wine_dbgstr_w(target));
+            goto error;
+        }
+    }
+
+error:
+    heap_free(target_path);
+    heap_free(target);
+    heap_free(source);
+    return ret;
+}
+
+static BOOL install_files(struct assembly_entry *assembly, BOOL dryrun)
+{
+    struct fileop_entry *fileop;
+    WCHAR *source_path;
+    BOOL ret = TRUE;
+
+    if (!(source_path = get_assembly_source(assembly)))
+    {
+        WINE_ERR("Failed to get assembly source directory\n");
+        return FALSE;
+    }
+
+    LIST_FOR_EACH_ENTRY(fileop, &assembly->fileops, struct fileop_entry, entry)
+    {
+        if (!(ret = install_files_copy(assembly, source_path, fileop, dryrun)))
+            break;
+    }
+
+    heap_free(source_path);
+    return ret;
+}
+
+static WCHAR *split_registry_key(WCHAR *key, HKEY *root)
+{
+    static const WCHAR hkey_classes_rootW[] = {'H','K','E','Y','_','C','L','A','S','S','E','S','_','R','O','O','T',0};
+    static const WCHAR hkey_current_configW[] = {'H','K','E','Y','_','C','U','R','R','E','N','T','_','C','O','N','F','I','G',0};
+    static const WCHAR hkey_current_userW[] = {'H','K','E','Y','_','C','U','R','R','E','N','T','_','U','S','E','R',0};
+    static const WCHAR hkey_local_machineW[] = {'H','K','E','Y','_','L','O','C','A','L','_','M','A','C','H','I','N','E',0};
+    static const WCHAR hkey_usersW[] = {'H','K','E','Y','_','U','S','E','R','S',0};
+
+    DWORD size;
+    WCHAR *p;
+
+    p = strchrW(key, '\\');
+    if (!p)
+        return NULL;
+
+    size = p - key;
+
+    if (strlenW(hkey_classes_rootW) == size && !strncmpW(key, hkey_classes_rootW, size))
+        *root = HKEY_CLASSES_ROOT;
+    else if (strlenW(hkey_current_configW) == size && !strncmpW(key, hkey_current_configW, size))
+        *root = HKEY_CURRENT_CONFIG;
+    else if (strlenW(hkey_current_userW) == size && !strncmpW(key, hkey_current_userW, size))
+        *root = HKEY_CURRENT_USER;
+    else if (strlenW(hkey_local_machineW) == size && !strncmpW(key, hkey_local_machineW, size))
+        *root = HKEY_LOCAL_MACHINE;
+    else if (strlenW(hkey_usersW) == size && !strncmpW(key, hkey_usersW, size))
+        *root = HKEY_USERS;
+    else
+    {
+        WINE_FIXME("Unknown root key %s\n", debugstr_wn(key, size));
+        return NULL;
+    }
+
+    return p + 1;
+}
+
+static BOOL install_registry_string(struct assembly_entry *assembly, HKEY key, struct registrykv_entry *registrykv, DWORD type, BOOL dryrun)
+{
+    DWORD value_size;
+    WCHAR *value = expand_expression(assembly, registrykv->value);
+    BOOL ret = TRUE;
+
+    if (registrykv->value && !value)
+        return FALSE;
+
+    value_size = value ? (strlenW(value) + 1) * sizeof(WCHAR) : 0;
+    if (!dryrun && RegSetValueExW(key, registrykv->name, 0, type, (void *)value, value_size))
+    {
+        WINE_ERR("Failed to set registry key %s\n", debugstr_w(registrykv->name));
+        ret = FALSE;
+    }
+
+    heap_free(value);
+    return ret;
+}
+
+static WCHAR *parse_multisz(const WCHAR *input, DWORD *size)
+{
+    static const WCHAR quoteW[] = {'"',0};
+    static const WCHAR emptyW[] = {0};
+    const WCHAR *pos, *next;
+    struct strbuf buf;
+
+    *size = 0;
+    if (!input || !input[0] || !strbuf_init(&buf))
+        return NULL;
+
+    for (pos = input; pos[0] == '"'; pos++)
+    {
+        pos++;
+        if (!(next = strstrW(pos, quoteW)))
+            goto error;
+        strbuf_append(&buf, pos, next - pos);
+        strbuf_append(&buf, emptyW, sizeof(emptyW)/sizeof(emptyW[0]));
+
+        pos = next + 1;
+        if (!pos[0])
+            break;
+        if (pos[0] != ',')
+        {
+            WINE_FIXME("Error while parsing REG_MULTI_SZ string: Expected comma but got '%c'\n", pos[0]);
+            goto error;
+        }
+    }
+
+    if (pos[0])
+    {
+        WINE_FIXME("Error while parsing REG_MULTI_SZ string: Garbage at end of string\n");
+        goto error;
+    }
+
+    strbuf_append(&buf, emptyW, sizeof(emptyW)/sizeof(emptyW[0]));
+    *size = buf.pos * sizeof(WCHAR);
+    return buf.buf;
+
+error:
+    strbuf_free(&buf);
+    return NULL;
+}
+
+static BOOL install_registry_multisz(struct assembly_entry *assembly, HKEY key, struct registrykv_entry *registrykv, BOOL dryrun)
+{
+    DWORD value_size;
+    WCHAR *value = parse_multisz(registrykv->value, &value_size);
+    BOOL ret = TRUE;
+
+    if (registrykv->value && registrykv->value[0] && !value)
+        return FALSE;
+
+    if (!dryrun && RegSetValueExW(key, registrykv->name, 0, REG_MULTI_SZ, (void *)value, value_size))
+    {
+        WINE_ERR("Failed to set registry key %s\n", debugstr_w(registrykv->name));
+        ret = FALSE;
+    }
+
+    heap_free(value);
+    return ret;
+}
+
+static BOOL install_registry_dword(struct assembly_entry *assembly, HKEY key, struct registrykv_entry *registrykv, BOOL dryrun)
+{
+    DWORD value = registrykv->value_type ? strtoulW(registrykv->value_type, NULL, 16) : 0;
+    BOOL ret = TRUE;
+
+    if (!dryrun && RegSetValueExW(key, registrykv->name, 0, REG_DWORD, (void *)&value, sizeof(value)))
+    {
+        WINE_ERR("Failed to set registry key %s\n", debugstr_w(registrykv->name));
+        ret = FALSE;
+    }
+
+    return ret;
+}
+
+static BYTE *parse_hex(const WCHAR *input, DWORD *size)
+{
+    WCHAR number[3] = {0, 0, 0};
+    BYTE *output, *p;
+    int length;
+
+    *size = 0;
+    if (!input)
+        return NULL;
+    length = strlenW(input);
+    if (length & 1)
+        return NULL;
+    length >>= 1;
+
+    if (!(output = heap_alloc(length)))
+        return NULL;
+    for (p = output; *input; input += 2)
+    {
+        number[0] = input[0];
+        number[1] = input[1];
+        *p++ = strtoulW(number, 0, 16);
+    }
+    *size = length;
+    return output;
+}
+
+static BOOL install_registry_binary(struct assembly_entry *assembly, HKEY key, struct registrykv_entry *registrykv, BOOL dryrun)
+{
+    DWORD value_size;
+    BYTE *value = parse_hex(registrykv->value, &value_size);
+    BOOL ret = TRUE;
+
+    if (registrykv->value && !value)
+        return FALSE;
+
+    if (!dryrun && RegSetValueExW(key, registrykv->name, 0, REG_BINARY, value, value_size))
+    {
+        WINE_ERR("Failed to set registry key %s\n", debugstr_w(registrykv->name));
+        ret = FALSE;
+    }
+
+    heap_free(value);
+    return ret;
+}
+
+static BOOL install_registry_value(struct assembly_entry *assembly, HKEY key, struct registrykv_entry *registrykv, BOOL dryrun)
+{
+    static const WCHAR reg_szW[] = {'R','E','G','_','S','Z',0};
+    static const WCHAR reg_expand_szW[] = {'R','E','G','_','E','X','P','A','N','D','_','S','Z',0};
+    static const WCHAR reg_multi_szW[] = {'R','E','G','_','M','U','L','T','I','_','S','Z',0};
+    static const WCHAR reg_dwordW[] = {'R','E','G','_','D','W','O','R','D',0};
+    static const WCHAR reg_binaryW[] = {'R','E','G','_','B','I','N','A','R','Y',0};
+
+    if (!strcmpW(registrykv->value_type, reg_szW))
+        return install_registry_string(assembly, key, registrykv, REG_SZ, dryrun);
+    if (!strcmpW(registrykv->value_type, reg_expand_szW))
+        return install_registry_string(assembly, key, registrykv, REG_EXPAND_SZ, dryrun);
+    if (!strcmpW(registrykv->value_type, reg_multi_szW))
+        return install_registry_multisz(assembly, key, registrykv, dryrun);
+    if (!strcmpW(registrykv->value_type, reg_dwordW))
+        return install_registry_dword(assembly, key, registrykv, dryrun);
+    if (!strcmpW(registrykv->value_type, reg_binaryW))
+        return install_registry_binary(assembly, key, registrykv, dryrun);
+
+    WINE_FIXME("Unsupported registry value type %s\n", debugstr_w(registrykv->value_type));
+    return FALSE;
+}
+
+static BOOL install_registry(struct assembly_entry *assembly, BOOL dryrun)
+{
+    struct registryop_entry *registryop;
+    struct registrykv_entry *registrykv;
+    HKEY root, subkey;
+    WCHAR *path;
+    BOOL ret = TRUE;
+
+    LIST_FOR_EACH_ENTRY(registryop, &assembly->registryops, struct registryop_entry, entry)
+    {
+        if (!(path = split_registry_key(registryop->key, &root)))
+        {
+            ret = FALSE;
+            break;
+        }
+
+        if (!dryrun && RegCreateKeyExW(root, path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &subkey, NULL))
+        {
+            WINE_ERR("Failed to open registry key %s\n", debugstr_w(registryop->key));
+            ret = FALSE;
+            break;
+        }
+
+        LIST_FOR_EACH_ENTRY(registrykv, &registryop->keyvalues, struct registrykv_entry, entry)
+        {
+            if (!(ret = install_registry_value(assembly, subkey, registrykv, dryrun))) break;
+        }
+
+        if (!dryrun)
+            RegCloseKey(subkey);
+        if (!ret)
+            break;
+    }
+
+    return ret;
+}
+
+static BOOL compare_assembly_string(const WCHAR *str1, const WCHAR *str2)
+{
+    static const WCHAR placeholderW[] = {'*',0};
+    return !strcmpW(str1, str2) || !strcmpW(str1, placeholderW) || !strcmpW(str2, placeholderW);
+}
+
+static struct assembly_entry *lookup_assembly(struct list *manifest_list, struct assembly_identity *identity)
+{
+    struct assembly_entry *assembly;
+
+    LIST_FOR_EACH_ENTRY(assembly, manifest_list, struct assembly_entry, entry)
+    {
+        if (strcmpiW(assembly->identity.name, identity->name))
+            continue;
+        if (!compare_assembly_string(assembly->identity.architecture, identity->architecture))
+            continue;
+        if (!compare_assembly_string(assembly->identity.language, identity->language))
+            continue;
+        if (!compare_assembly_string(assembly->identity.pubkey_token, identity->pubkey_token))
+            continue;
+        if (!compare_assembly_string(assembly->identity.version, identity->version))
+        {
+            WINE_WARN("Ignoring version difference for %s (expected %s, found %s)\n",
+                      debugstr_w(identity->name), debugstr_w(identity->version), debugstr_w(assembly->identity.version));
+        }
+        return assembly;
+    }
+
+    return NULL;
+}
+
+static BOOL install_assembly(struct list *manifest_list, struct assembly_identity *identity, BOOL dryrun)
+{
+    struct dependency_entry *dependency;
+    struct assembly_entry *assembly;
+    const WCHAR *name;
+
+    if (!(assembly = lookup_assembly(manifest_list, identity)))
+    {
+        WINE_FIXME("Assembly %s not found\n", debugstr_w(identity->name));
+        return FALSE;
+    }
+
+    name = assembly->identity.name;
+
+    if (assembly->status == ASSEMBLY_STATUS_INSTALLED)
+    {
+        WINE_TRACE("Assembly %s already installed\n", debugstr_w(name));
+        return TRUE;
+    }
+    if (assembly->status == ASSEMBLY_STATUS_IN_PROGRESS)
+    {
+        WINE_ERR("Assembly %s caused circular dependency\n", debugstr_w(name));
+        return FALSE;
+    }
+
+    assembly->status = ASSEMBLY_STATUS_IN_PROGRESS;
+
+    LIST_FOR_EACH_ENTRY(dependency, &assembly->dependencies, struct dependency_entry, entry)
+    {
+        if (!install_assembly(manifest_list, &dependency->identity, dryrun))
+            return FALSE;
+    }
+
+    if (!install_files(assembly, dryrun))
+    {
+        WINE_ERR("Failed to install all files for %s\n", debugstr_w(name));
+        return FALSE;
+    }
+
+    if (!install_registry(assembly, dryrun))
+    {
+        WINE_ERR("Failed to install registry keys for %s\n", debugstr_w(name));
+        return FALSE;
+    }
+
+    assembly->status = ASSEMBLY_STATUS_INSTALLED;
+    return TRUE;
+}
+
 static const WCHAR *create_temp_directory(struct installer_state *state)
 {
     static const WCHAR msuW[] = {'m','s','u',0};
@@ -433,6 +935,28 @@ static BOOL load_assemblies_from_cab(const WCHAR *filename, struct installer_sta
     return TRUE;
 }
 
+static BOOL install_updates(struct installer_state *state, BOOL dryrun)
+{
+    struct dependency_entry *dependency;
+    LIST_FOR_EACH_ENTRY(dependency, &state->updates, struct dependency_entry, entry)
+    {
+        if (!install_assembly(&state->assemblies, &dependency->identity, dryrun))
+        {
+            WINE_ERR("Failed to install update %s\n", debugstr_w(dependency->identity.name));
+            return FALSE;
+        }
+    }
+    return TRUE;
+}
+
+static void set_assembly_status(struct list *manifest_list, DWORD status)
+{
+    struct assembly_entry *assembly;
+    LIST_FOR_EACH_ENTRY(assembly, manifest_list, struct assembly_entry, entry)
+    {
+        assembly->status = status;
+    }
+}
 
 static BOOL install_msu(WCHAR *filename, struct installer_state *state)
 {
@@ -451,6 +975,7 @@ static BOOL install_msu(WCHAR *filename, struct installer_state *state)
     CoInitialize(NULL);
 
     WINE_TRACE("Processing msu file %s\n", wine_dbgstr_w(filename));
+
     if (!(temp_path = create_temp_directory(state)))
         return ret;
 
@@ -517,19 +1042,36 @@ static BOOL install_msu(WCHAR *filename, struct installer_state *state)
         WINE_TRACE("List of manifests (with dependencies):\n");
         LIST_FOR_EACH_ENTRY(assembly, &state->assemblies, struct assembly_entry, entry)
         {
-            WINE_TRACE(" * %s\n", debugstr_w(assembly->identity.name));
+            WINE_TRACE(" * %s\n", wine_dbgstr_w(assembly->identity.name));
             LIST_FOR_EACH_ENTRY(dependency, &assembly->dependencies, struct dependency_entry, entry)
                 WINE_TRACE("   -> %s\n", wine_dbgstr_w(dependency->identity.name));
         }
     }
-    ret = TRUE;
 
     if (list_empty(&state->updates))
     {
         WINE_ERR("No updates found, probably incompatible MSU file format?\n");
-        ret = FALSE;
+        goto done;
+    }
+
+    /* perform dryrun */
+    set_assembly_status(&state->assemblies, ASSEMBLY_STATUS_NONE);
+    if (!install_updates(state, TRUE))
+    {
+        WINE_ERR("Dryrun failed, aborting installation\n");
+        goto done;
+    }
+
+    /* installation */
+    set_assembly_status(&state->assemblies, ASSEMBLY_STATUS_NONE);
+    if (!install_updates(state, FALSE))
+    {
+        WINE_ERR("Installation failed\n");
+        goto done;
     }
 
+    ret = TRUE;
+
 done:
     installer_cleanup(state);
     return ret;
diff --git a/programs/wusa/wusa.h b/programs/wusa/wusa.h
index 6b4a2ed6ab8..061e47b8f76 100644
--- a/programs/wusa/wusa.h
+++ b/programs/wusa/wusa.h
@@ -117,3 +117,16 @@ static inline WCHAR *strdupW(const WCHAR *str)
         strcpyW(ret, str);
     return ret;
 }
+
+static inline WCHAR *strdupWn(const WCHAR *str, DWORD len)
+{
+    WCHAR *ret;
+    if (!str) return NULL;
+    ret = heap_alloc((len + 1) * sizeof(WCHAR));
+    if (ret)
+    {
+        memcpy(ret, str, len * sizeof(WCHAR));
+        ret[len] = 0;
+    }
+    return ret;
+}
-- 
2.16.2




More information about the wine-devel mailing list