From: Michael Müller michael@fds-team.de
Signed-off-by: Vijay Kiran Kamuju infyquest@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, ®istryop->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; +}