diff --git a/README.md b/README.md index a21c257b..f16af880 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,11 @@ Wine. All those differences are also documented on the Included bug fixes and improvements ----------------------------------- +**Bug fixes and features included in the next upcoming release [1]:** + +* Initial implementation of wusa.exe (MSU package installer) ([Wine Bug #26757](https://bugs.winehq.org/show_bug.cgi?id=26757)) + + **Bug fixes and features in Wine Staging 1.8-rc4 [267]:** *Note: The following list only contains features and bug fixes which are not diff --git a/patches/patchinstall.sh b/patches/patchinstall.sh index 95489ff5..7aaadf29 100755 --- a/patches/patchinstall.sh +++ b/patches/patchinstall.sh @@ -344,6 +344,7 @@ patch_enable_all () enable_ws2_32_WriteWatches="$1" enable_ws2_32_getaddrinfo="$1" enable_wtsapi32_EnumerateProcesses="$1" + enable_wusa_MSU_Package_Installer="$1" } # Enable or disable all categories @@ -1145,6 +1146,9 @@ patch_enable () wtsapi32-EnumerateProcesses) enable_wtsapi32_EnumerateProcesses="$2" ;; + wusa-MSU_Package_Installer) + enable_wusa_MSU_Package_Installer="$2" + ;; *) return 1 ;; @@ -6806,6 +6810,29 @@ if test "$enable_wtsapi32_EnumerateProcesses" -eq 1; then ) >> "$patchlist" fi +# Patchset wusa-MSU_Package_Installer +# | +# | This patchset fixes the following Wine bugs: +# | * [#26757] Initial implementation of wusa.exe (MSU package installer) +# | +# | Modified files: +# | * programs/wusa/Makefile.in, programs/wusa/main.c, programs/wusa/manifest.c, programs/wusa/wusa.h +# | +if test "$enable_wusa_MSU_Package_Installer" -eq 1; then + patch_apply wusa-MSU_Package_Installer/0001-wusa-Implement-basic-installation-logic.patch + patch_apply wusa-MSU_Package_Installer/0002-wusa-Ignore-systemProtection-subkey-of-registry-key.patch + patch_apply wusa-MSU_Package_Installer/0003-wusa-Treat-empty-update-list-as-error.patch + patch_apply wusa-MSU_Package_Installer/0004-wusa-Implement-WOW64-support.patch + patch_apply wusa-MSU_Package_Installer/0005-wusa-Add-workaround-to-be-compatible-with-Vista-pack.patch + ( + echo '+ { "Michael Müller", "wusa: Implement basic installation logic.", 1 },'; + echo '+ { "Michael Müller", "wusa: Ignore systemProtection subkey of registry key.", 1 },'; + echo '+ { "Michael Müller", "wusa: Treat empty update list as error.", 1 },'; + echo '+ { "Michael Müller", "wusa: Implement WOW64 support.", 1 },'; + echo '+ { "Sebastian Lackner", "wusa: Add workaround to be compatible with Vista packages.", 1 },'; + ) >> "$patchlist" +fi + if test "$enable_patchlist" -eq 1; then diff --git a/patches/wusa-MSU_Package_Installer/0001-wusa-Implement-basic-installation-logic.patch b/patches/wusa-MSU_Package_Installer/0001-wusa-Implement-basic-installation-logic.patch new file mode 100644 index 00000000..5686c985 --- /dev/null +++ b/patches/wusa-MSU_Package_Installer/0001-wusa-Implement-basic-installation-logic.patch @@ -0,0 +1,1974 @@ +From 08846f560480d776a99cf5ef5bd9661e13fa5a68 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michael=20M=C3=BCller?= +Date: Mon, 14 Dec 2015 00:39:54 +0100 +Subject: wusa: Implement basic installation logic. + +--- + programs/wusa/Makefile.in | 4 +- + programs/wusa/main.c | 1044 ++++++++++++++++++++++++++++++++++++++++++++- + programs/wusa/manifest.c | 704 ++++++++++++++++++++++++++++++ + programs/wusa/wusa.h | 160 +++++++ + 4 files changed, 1906 insertions(+), 6 deletions(-) + create mode 100644 programs/wusa/manifest.c + create mode 100644 programs/wusa/wusa.h + +diff --git a/programs/wusa/Makefile.in b/programs/wusa/Makefile.in +index 5068456..dbf424c 100644 +--- a/programs/wusa/Makefile.in ++++ b/programs/wusa/Makefile.in +@@ -1,5 +1,7 @@ + MODULE = wusa.exe + APPMODE = -mconsole -municode ++IMPORTS = cabinet shlwapi ole32 oleaut32 advapi32 + + C_SRCS = \ +- main.c ++ main.c \ ++ manifest.c +diff --git a/programs/wusa/main.c b/programs/wusa/main.c +index aa7a38f..7c1dfef 100644 +--- a/programs/wusa/main.c ++++ b/programs/wusa/main.c +@@ -1,5 +1,7 @@ + /* + * Copyright 2012 Austin English ++ * 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 +@@ -16,18 +18,1050 @@ + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + ++#include "windows.h" ++#include "shlwapi.h" ++#include "fdi.h" ++#include "wusa.h" + #include "wine/debug.h" ++#include "wine/unicode.h" + + WINE_DEFAULT_DEBUG_CHANNEL(wusa); + ++/* from msvcrt/fcntl.h */ ++#define _O_RDONLY 0 ++#define _O_WRONLY 1 ++#define _O_RDWR 2 ++#define _O_ACCMODE (_O_RDONLY|_O_WRONLY|_O_RDWR) ++#define _O_APPEND 0x0008 ++#define _O_RANDOM 0x0010 ++#define _O_SEQUENTIAL 0x0020 ++#define _O_TEMPORARY 0x0040 ++#define _O_NOINHERIT 0x0080 ++#define _O_CREAT 0x0100 ++#define _O_TRUNC 0x0200 ++#define _O_EXCL 0x0400 ++#define _O_SHORT_LIVED 0x1000 ++#define _O_TEXT 0x4000 ++#define _O_BINARY 0x8000 ++ ++struct strbuf ++{ ++ WCHAR *buf; ++ DWORD pos; ++ DWORD len; ++}; ++ ++struct installer_tempdir ++{ ++ struct list entry; ++ WCHAR *path; ++}; ++ ++struct installer_state ++{ ++ BOOL norestart; ++ BOOL quiet; ++ struct list tempdirs; ++ struct list assemblies; ++ 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); ++ if (suffix_len > str_len) return FALSE; ++ return !strcmpiW(str + str_len - suffix_len, suffix); ++} ++ ++static WCHAR *path_combine(const WCHAR *path, const WCHAR *filename) ++{ ++ static const WCHAR backslashW[] = {'\\',0}; ++ WCHAR *result; ++ DWORD length; ++ ++ if (!path || !filename) return NULL; ++ length = strlenW(path) + strlenW(filename) + 2; ++ if (!(result = heap_alloc(length * sizeof(WCHAR)))) return NULL; ++ ++ strcpyW(result, path); ++ if (result[0] && result[strlenW(result) - 1] != '\\') ++ strcatW(result, backslashW); ++ strcatW(result, filename); ++ return result; ++} ++ ++static BOOL is_directory(const WCHAR *path) ++{ ++ DWORD attrs = GetFileAttributesW(path); ++ if (attrs == INVALID_FILE_ATTRIBUTES) return FALSE; ++ return (attrs & FILE_ATTRIBUTE_DIRECTORY) != 0; ++} ++ ++static BOOL create_directory(const WCHAR *path) ++{ ++ if (is_directory(path)) return TRUE; ++ if (CreateDirectoryW(path, NULL)) return TRUE; ++ return (GetLastError() == ERROR_ALREADY_EXISTS); ++} ++ ++static BOOL create_parent_directory(const WCHAR *filename) ++{ ++ WCHAR *p, *path = strdupW(filename); ++ BOOL ret = FALSE; ++ ++ if (!path) return FALSE; ++ if (!PathRemoveFileSpecW(path)) goto done; ++ if (is_directory(path)) ++ { ++ ret = TRUE; ++ goto done; ++ } ++ ++ for (p = path; *p; p++) ++ { ++ if (*p != '\\') continue; ++ *p = 0; ++ if (!create_directory(path)) goto done; ++ *p = '\\'; ++ } ++ ret = create_directory(path); ++ ++done: ++ heap_free(path); ++ return ret; ++} ++ ++static BOOL delete_directory(const WCHAR *path) ++{ ++ static const WCHAR starW[] = {'*',0}; ++ static const WCHAR dotW[] = {'.', 0}; ++ static const WCHAR dotdotW[] = {'.','.', 0}; ++ WIN32_FIND_DATAW data; ++ WCHAR *full_path; ++ HANDLE search; ++ ++ if (!(full_path = path_combine(path, starW))) return FALSE; ++ search = FindFirstFileW(full_path, &data); ++ heap_free(full_path); ++ ++ if (search != INVALID_HANDLE_VALUE) ++ { ++ do ++ { ++ if (!strcmpW(data.cFileName, dotW)) continue; ++ if (!strcmpW(data.cFileName, dotdotW)) continue; ++ if (!(full_path = path_combine(path, data.cFileName))) continue; ++ if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ++ delete_directory(full_path); ++ else ++ DeleteFileW(full_path); ++ heap_free(full_path); ++ } ++ while (FindNextFileW(search, &data)); ++ FindClose(search); ++ } ++ ++ return RemoveDirectoryW(path); ++} ++ ++static WCHAR *get_uncompressed_path(PFDINOTIFICATION pfdin) ++{ ++ WCHAR *file = strdupAtoW(pfdin->psz1); ++ WCHAR *path = path_combine(pfdin->pv, file); ++ heap_free(file); ++ return path; ++} ++ ++static void * CDECL cabinet_alloc(ULONG cb) ++{ ++ return heap_alloc(cb); ++} ++ ++static void CDECL cabinet_free(void *pv) ++{ ++ heap_free(pv); ++} ++ ++static INT_PTR CDECL cabinet_open(char *pszFile, int oflag, int pmode) ++{ ++ DWORD dwAccess = 0; ++ DWORD dwShareMode = 0; ++ DWORD dwCreateDisposition = OPEN_EXISTING; ++ ++ switch (oflag & _O_ACCMODE) ++ { ++ case _O_RDONLY: ++ dwAccess = GENERIC_READ; ++ dwShareMode = FILE_SHARE_READ | FILE_SHARE_DELETE; ++ break; ++ case _O_WRONLY: ++ dwAccess = GENERIC_WRITE; ++ dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ++ break; ++ case _O_RDWR: ++ dwAccess = GENERIC_READ | GENERIC_WRITE; ++ dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; ++ break; ++ } ++ ++ if ((oflag & (_O_CREAT | _O_EXCL)) == (_O_CREAT | _O_EXCL)) ++ dwCreateDisposition = CREATE_NEW; ++ else if (oflag & _O_CREAT) ++ dwCreateDisposition = CREATE_ALWAYS; ++ ++ return (INT_PTR)CreateFileA(pszFile, dwAccess, dwShareMode, NULL, ++ dwCreateDisposition, 0, NULL); ++} ++ ++static UINT CDECL cabinet_read(INT_PTR hf, void *pv, UINT cb) ++{ ++ HANDLE handle = (HANDLE)hf; ++ DWORD read; ++ ++ if (ReadFile(handle, pv, cb, &read, NULL)) ++ return read; ++ ++ return 0; ++} ++ ++static UINT CDECL cabinet_write(INT_PTR hf, void *pv, UINT cb) ++{ ++ HANDLE handle = (HANDLE)hf; ++ DWORD written; ++ ++ if (WriteFile(handle, pv, cb, &written, NULL)) ++ return written; ++ ++ return 0; ++} ++ ++static int CDECL cabinet_close(INT_PTR hf) ++{ ++ HANDLE handle = (HANDLE)hf; ++ ++ return CloseHandle(handle) ? 0 : -1; ++} ++ ++static LONG CDECL cabinet_seek(INT_PTR hf, LONG dist, int seektype) ++{ ++ HANDLE handle = (HANDLE)hf; ++ /* flags are compatible and so are passed straight through */ ++ return SetFilePointer(handle, dist, NULL, seektype); ++} ++ ++static INT_PTR cabinet_copy_file(FDINOTIFICATIONTYPE fdint, ++ PFDINOTIFICATION pfdin) ++{ ++ HANDLE handle = INVALID_HANDLE_VALUE; ++ WCHAR *file; ++ DWORD attrs; ++ ++ if (!(file = get_uncompressed_path(pfdin))) ++ return -1; ++ ++ TRACE("extracting %s -> %s\n", debugstr_a(pfdin->psz1), debugstr_w(file)); ++ ++ if (create_parent_directory(file)) ++ { ++ attrs = pfdin->attribs; ++ if (!attrs) attrs = FILE_ATTRIBUTE_NORMAL; ++ handle = CreateFileW(file, GENERIC_READ | GENERIC_WRITE, 0, ++ NULL, CREATE_ALWAYS, attrs, NULL); ++ } ++ ++ heap_free(file); ++ return (handle != INVALID_HANDLE_VALUE) ? (INT_PTR)handle : -1; ++} ++ ++static INT_PTR cabinet_close_file_info(FDINOTIFICATIONTYPE fdint, ++ PFDINOTIFICATION pfdin) ++{ ++ HANDLE handle = (HANDLE)pfdin->hf; ++ CloseHandle(handle); ++ return 1; ++} ++ ++static INT_PTR CDECL cabinet_notify(FDINOTIFICATIONTYPE fdint, PFDINOTIFICATION pfdin) ++{ ++ switch (fdint) ++ { ++ case fdintPARTIAL_FILE: ++ FIXME("fdintPARTIAL_FILE not implemented\n"); ++ return 0; ++ ++ case fdintNEXT_CABINET: ++ FIXME("fdintNEXT_CABINET not implemented\n"); ++ return 0; ++ ++ case fdintCOPY_FILE: ++ return cabinet_copy_file(fdint, pfdin); ++ ++ case fdintCLOSE_FILE_INFO: ++ return cabinet_close_file_info(fdint, pfdin); ++ ++ default: ++ return 0; ++ } ++} ++ ++static BOOL extract_cabinet(const WCHAR *filename, const WCHAR *destination) ++{ ++ char *filenameA = NULL; ++ BOOL ret = FALSE; ++ HFDI hfdi; ++ ERF erf; ++ ++ hfdi = FDICreate(cabinet_alloc, cabinet_free, cabinet_open, cabinet_read, ++ cabinet_write, cabinet_close, cabinet_seek, 0, &erf); ++ if (!hfdi) return FALSE; ++ ++ if ((filenameA = strdupWtoA(filename))) ++ { ++ ret = FDICopy(hfdi, filenameA, NULL, 0, cabinet_notify, NULL, (void *)destination); ++ heap_free(filenameA); ++ } ++ ++ FDIDestroy(hfdi); ++ 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", debugstr_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", debugstr_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 = path_combine(target_path, fileop->source))) goto error; ++ if (!(source = path_combine(source_path, fileop->source))) goto error; ++ ++ if (dryrun) ++ { ++ if (!(ret = PathFileExistsW(source))) ++ { ++ WINE_ERR("Required file %s not found\n", debugstr_w(source)); ++ goto error; ++ } ++ } ++ else ++ { ++ if (!create_parent_directory(target)) ++ { ++ WINE_ERR("Failed to create parent directory for %s\n", debugstr_w(target)); ++ goto error; ++ } ++ if (!(ret = CopyFileExW(source, target, NULL, NULL, NULL, 0))) ++ { ++ WINE_ERR("Failed to copy %s to %s\n", debugstr_w(source), debugstr_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}; ++ static UINT id; ++ struct installer_tempdir *entry; ++ WCHAR tmp[MAX_PATH]; ++ ++ if (!GetTempPathW(sizeof(tmp)/sizeof(WCHAR), tmp)) return NULL; ++ if (!(entry = heap_alloc(sizeof(*entry)))) return NULL; ++ if (!(entry->path = heap_alloc((MAX_PATH + 20) * sizeof(WCHAR)))) ++ { ++ heap_free(entry); ++ return NULL; ++ } ++ for (;;) ++ { ++ if (!GetTempFileNameW(tmp, msuW, ++id, entry->path)) ++ { ++ heap_free(entry->path); ++ heap_free(entry); ++ return NULL; ++ } ++ if (CreateDirectoryW(entry->path, NULL)) break; ++ } ++ ++ list_add_tail(&state->tempdirs, &entry->entry); ++ return entry->path; ++} ++ ++static void installer_cleanup(struct installer_state *state) ++{ ++ struct installer_tempdir *tempdir, *tempdir2; ++ struct assembly_entry *assembly, *assembly2; ++ struct dependency_entry *dependency, *dependency2; ++ ++ LIST_FOR_EACH_ENTRY_SAFE(tempdir, tempdir2, &state->tempdirs, struct installer_tempdir, entry) ++ { ++ list_remove(&tempdir->entry); ++ delete_directory(tempdir->path); ++ 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); ++ } ++ LIST_FOR_EACH_ENTRY_SAFE(dependency, dependency2, &state->updates, struct dependency_entry, entry) ++ { ++ list_remove(&dependency->entry); ++ free_dependency(dependency); ++ } ++} ++ ++static BOOL load_assemblies_from_cab(const WCHAR *filename, struct installer_state *state) ++{ ++ static const WCHAR manifestW[] = {'.','m','a','n','i','f','e','s','t',0}; ++ static const WCHAR mumW[] = {'.','m','u','m',0}; ++ static const WCHAR starW[] = {'*',0}; ++ struct assembly_entry *assembly; ++ const WCHAR *temp_path; ++ WIN32_FIND_DATAW data; ++ HANDLE search; ++ WCHAR *path; ++ ++ WINE_TRACE("Processing cab file %s\n", debugstr_w(filename)); ++ ++ if (!(temp_path = create_temp_directory(state))) return FALSE; ++ if (!extract_cabinet(filename, temp_path)) ++ { ++ WINE_ERR("Failed to extract %s\n", debugstr_w(filename)); ++ return FALSE; ++ } ++ ++ if (!(path = path_combine(temp_path, starW))) 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, manifestW) && ++ !str_ends_with(data.cFileName, mumW)) 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_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) ++{ ++ static const WCHAR wsusscanW[] = {'W','S','U','S','S','C','A','N','.','c','a','b',0}; ++ static const WCHAR cabW[] = {'*','.','c','a','b',0}; ++ static const WCHAR xmlW[] = {'*','.','x','m','l',0}; ++ const WCHAR *temp_path; ++ WIN32_FIND_DATAW data; ++ HANDLE search; ++ WCHAR *path; ++ BOOL ret = FALSE; ++ ++ list_init(&state->tempdirs); ++ list_init(&state->assemblies); ++ list_init(&state->updates); ++ CoInitialize(NULL); ++ ++ WINE_TRACE("Processing msu file %s\n", debugstr_w(filename)); ++ ++ if (!(temp_path = create_temp_directory(state))) return 1; ++ if (!extract_cabinet(filename, temp_path)) ++ { ++ WINE_ERR("Failed to extract %s\n", debugstr_w(filename)); ++ goto done; ++ } ++ ++ /* load all manifests from contained cabinet archives */ ++ if (!(path = path_combine(temp_path, cabW))) goto done; ++ search = FindFirstFileW(path, &data); ++ heap_free(path); ++ ++ if (search != INVALID_HANDLE_VALUE) ++ { ++ do ++ { ++ if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; ++ if (!strcmpiW(data.cFileName, wsusscanW)) continue; ++ if (!(path = path_combine(temp_path, data.cFileName))) continue; ++ if (!load_assemblies_from_cab(path, state)) ++ WINE_ERR("Failed to load all manifests from %s, ignoring\n", debugstr_w(path)); ++ heap_free(path); ++ } ++ while (FindNextFileW(search, &data)); ++ FindClose(search); ++ } ++ ++ /* load all update descriptions */ ++ if (!(path = path_combine(temp_path, xmlW))) goto done; ++ search = FindFirstFileW(path, &data); ++ heap_free(path); ++ ++ if (search != INVALID_HANDLE_VALUE) ++ { ++ do ++ { ++ if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue; ++ if (!(path = path_combine(temp_path, data.cFileName))) continue; ++ if (!load_update(path, &state->updates)) ++ WINE_ERR("Failed to load all updates from %s, ignoring\n", debugstr_w(path)); ++ heap_free(path); ++ } ++ while (FindNextFileW(search, &data)); ++ FindClose(search); ++ } ++ ++ /* dump package information (for debugging) */ ++ if (WINE_TRACE_ON(wusa)) ++ { ++ struct dependency_entry *dependency; ++ struct assembly_entry *assembly; ++ ++ WINE_TRACE("List of updates:\n"); ++ LIST_FOR_EACH_ENTRY(dependency, &state->updates, struct dependency_entry, entry) ++ WINE_TRACE(" * %s\n", debugstr_w(dependency->identity.name)); ++ ++ 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)); ++ LIST_FOR_EACH_ENTRY(dependency, &assembly->dependencies, struct dependency_entry, entry) ++ WINE_TRACE(" -> %s\n", debugstr_w(dependency->identity.name)); ++ } ++ } ++ ++ /* 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; ++} ++ + int wmain(int argc, WCHAR *argv[]) + { ++ static const WCHAR norestartW[] = {'/','n','o','r','e','s','t','a','r','t',0}; ++ static const WCHAR quietW[] = {'/','q','u','i','e','t',0}; ++ struct installer_state state; ++ WCHAR *filename = NULL; + int i; + +- WINE_FIXME("stub:"); +- for (i = 0; i < argc; i++) +- WINE_FIXME(" %s", wine_dbgstr_w(argv[i])); +- WINE_FIXME("\n"); ++ state.norestart = FALSE; ++ state.quiet = FALSE; + +- return 0; ++ if (TRACE_ON(wusa)) ++ { ++ WINE_TRACE("Command line:"); ++ for (i = 0; i < argc; i++) ++ WINE_TRACE(" %s", wine_dbgstr_w(argv[i])); ++ WINE_TRACE("\n"); ++ } ++ ++ for (i = 1; i < argc; i++) ++ { ++ if (argv[i][0] == '/') ++ { ++ if (!strcmpW(argv[i], norestartW)) ++ state.norestart = TRUE; ++ else if (!strcmpW(argv[i], quietW)) ++ state.quiet = TRUE; ++ else ++ WINE_FIXME("Unknown option: %s\n", wine_dbgstr_w(argv[i])); ++ } ++ else if (!filename) ++ filename = argv[i]; ++ else ++ WINE_FIXME("Unknown option: %s\n", wine_dbgstr_w(argv[i])); ++ } ++ ++ if (!filename) ++ { ++ WINE_FIXME("Missing filename argument\n"); ++ return 1; ++ } ++ ++ return !install_msu(filename, &state); + } +diff --git a/programs/wusa/manifest.c b/programs/wusa/manifest.c +new file mode 100644 +index 0000000..fc1f65b +--- /dev/null ++++ b/programs/wusa/manifest.c +@@ -0,0 +1,704 @@ ++/* ++ * 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 "windows.h" ++#define COBJMACROS ++#include "initguid.h" ++#include "msxml.h" ++#include "wusa.h" ++#include "wine/debug.h" ++#include "wine/unicode.h" ++ ++WINE_DEFAULT_DEBUG_CHANNEL(wusa); ++ ++static struct dependency_entry *alloc_dependency(void) ++{ ++ struct dependency_entry *entry = heap_alloc_zero(sizeof(*entry)); ++ if (!entry) WINE_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) WINE_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) WINE_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) WINE_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) WINE_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 = !strcmpW(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; ++ ++ WINE_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) ++{ ++ static const WCHAR nameW[] = {'n','a','m','e',0}; ++ static const WCHAR versionW[] = {'v','e','r','s','i','o','n',0}; ++ static const WCHAR processorArchitectureW[] = {'p','r','o','c','e','s','s','o','r','A','r','c','h','i','t','e','c','t','u','r','e',0}; ++ static const WCHAR languageW[] = {'l','a','n','g','u','a','g','e',0}; ++ static const WCHAR publicKeyTokenW[] = {'p','u','b','l','i','c','K','e','y','T','o','k','e','n',0}; ++ ++ memset(identity, 0, sizeof(*identity)); ++ if (!(identity->name = get_xml_attribute(root, nameW))) goto error; ++ if (!(identity->version = get_xml_attribute(root, versionW))) goto error; ++ if (!(identity->architecture = get_xml_attribute(root, processorArchitectureW))) goto error; ++ if (!(identity->language = get_xml_attribute(root, languageW))) goto error; ++ if (!(identity->pubkey_token = get_xml_attribute(root, publicKeyTokenW))) goto error; ++ return TRUE; ++ ++error: ++ clear_identity(identity); ++ return FALSE; ++} ++ ++/* */ ++static BOOL read_dependent_assembly(IXMLDOMElement *root, struct assembly_identity *identity) ++{ ++ static const WCHAR dependencyTypeW[] = {'d','e','p','e','n','d','e','n','c','y','T','y','p','e',0}; ++ static const WCHAR installW[] = {'i','n','s','t','a','l','l',0}; ++ static const WCHAR select_assemblyIdentityW[] = {'.','/','/','a','s','s','e','m','b','l','y','I','d','e','n','t','i','t','y',0}; ++ IXMLDOMElement *child = NULL; ++ WCHAR *dependency_type; ++ BOOL ret = FALSE; ++ ++ if (!(dependency_type = get_xml_attribute(root, dependencyTypeW))) ++ { ++ WINE_ERR("Failed to get dependency type\n"); ++ return FALSE; ++ } ++ if (strcmpW(dependency_type, installW)) ++ { ++ WINE_FIXME("Unimplemented dependency type %s\n", debugstr_w(dependency_type)); ++ goto error; ++ } ++ if (!(child = select_xml_node(root, select_assemblyIdentityW))) ++ { ++ WINE_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) ++{ ++ static const WCHAR dependentAssemblyW[] = {'d','e','p','e','n','d','e','n','t','A','s','s','e','m','b','l','y',0}; ++ struct assembly_entry *assembly = context; ++ struct dependency_entry *entry; ++ ++ if (strcmpW(tagname, dependentAssemblyW)) ++ { ++ WINE_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)) ++ { ++ WINE_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) ++{ ++ static const WCHAR assemblyIdentityW[] = {'a','s','s','e','m','b','l','y','I','d','e','n','t','i','t','y',0}; ++ struct assembly_entry *assembly = context; ++ struct dependency_entry *entry; ++ ++ if (strcmpW(tagname, assemblyIdentityW)) ++ { ++ WINE_FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); ++ return TRUE; ++ } ++ ++ if ((entry = alloc_dependency())) ++ { ++ if (read_identity(child, &entry->identity)) ++ { ++ WINE_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) ++{ ++ static const WCHAR applicableW[] = {'a','p','p','l','i','c','a','b','l','e',0}; ++ static const WCHAR componentW[] = {'c','o','m','p','o','n','e','n','t',0}; ++ static const WCHAR packageW[] = {'p','a','c','k','a','g','e',0}; ++ struct assembly_entry *assembly = context; ++ ++ if (!strcmpW(tagname, componentW)) ++ return iter_components(child, assembly); ++ if (!strcmpW(tagname, packageW)) ++ return iter_components(child, assembly); ++ if (!strcmpW(tagname, applicableW)) ++ return TRUE; ++ ++ WINE_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) ++{ ++ static const WCHAR updateW[] = {'u','p','d','a','t','e',0}; ++ static const WCHAR parentW[] = {'p','a','r','e','n','t',0}; ++ struct assembly_entry *assembly = context; ++ ++ if (!strcmpW(tagname, updateW)) ++ return iter_update(child, assembly); ++ if (!strcmpW(tagname, parentW)) ++ return TRUE; ++ ++ WINE_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) ++{ ++ static const WCHAR sourceNameW[] = {'s','o','u','r','c','e','N','a','m','e',0}; ++ static const WCHAR destinationPathW[] = {'d','e','s','t','i','n','a','t','i','o','n','P','a','t','h',0}; ++ struct fileop_entry *entry; ++ ++ if (!(entry = alloc_fileop())) ++ return FALSE; ++ ++ if (!(entry->source = get_xml_attribute(root, sourceNameW))) goto error; ++ if (!(entry->target = get_xml_attribute(root, destinationPathW))) goto error; ++ ++ WINE_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) ++{ ++ static const WCHAR securityDescriptorW[] = {'s','e','c','u','r','i','t','y','D','e','s','c','r','i','p','t','o','r',0}; ++ static const WCHAR registryValueW[] = {'r','e','g','i','s','t','r','y','V','a','l','u','e',0}; ++ static const WCHAR nameW[] = {'n','a','m','e',0}; ++ static const WCHAR valueTypeW[] = {'v','a','l','u','e','T','y','p','e',0}; ++ static const WCHAR valueW[] = {'v','a','l','u','e',0}; ++ struct registryop_entry *registryop = context; ++ struct registrykv_entry *entry; ++ ++ if (!strcmpW(tagname, securityDescriptorW)) return TRUE; ++ if (strcmpW(tagname, registryValueW)) ++ { ++ WINE_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, valueTypeW))) goto error; ++ entry->name = get_xml_attribute(child, nameW); /* optional */ ++ entry->value = get_xml_attribute(child, valueW); /* optional */ ++ ++ WINE_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) ++{ ++ static const WCHAR registryKeyW[] = {'r','e','g','i','s','t','r','y','K','e','y',0}; ++ static const WCHAR keyNameW[] = {'k','e','y','N','a','m','e',0}; ++ struct assembly_entry *assembly = context; ++ struct registryop_entry *entry; ++ WCHAR *keyname; ++ ++ if (strcmpW(tagname, registryKeyW)) ++ { ++ WINE_FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); ++ return TRUE; ++ } ++ ++ if (!(keyname = get_xml_attribute(child, keyNameW))) ++ { ++ WINE_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; ++ WINE_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) ++{ ++ static const WCHAR assemblyIdentityW[] = {'a','s','s','e','m','b','l','y','I','d','e','n','t','i','t','y',0}; ++ static const WCHAR dependencyW[] = {'d','e','p','e','n','d','e','n','c','y',0}; ++ static const WCHAR packageW[] = {'p','a','c','k','a','g','e',0}; ++ static const WCHAR fileW[] = {'f','i','l','e',0}; ++ static const WCHAR registryKeysW[] = {'r','e','g','i','s','t','r','y','K','e','y','s',0}; ++ static const WCHAR trustInfoW[] = {'t','r','u','s','t','I','n','f','o',0}; ++ static const WCHAR configurationW[] = {'c','o','n','f','i','g','u','r','a','t','i','o','n',0}; ++ static const WCHAR deploymentW[] = {'d','e','p','l','o','y','m','e','n','t',0}; ++ struct assembly_entry *assembly = context; ++ ++ if (!strcmpW(tagname, assemblyIdentityW) && !assembly->identity.name) ++ return read_identity(child, &assembly->identity); ++ if (!strcmpW(tagname, dependencyW)) ++ return iter_dependency(child, assembly); ++ if (!strcmpW(tagname, packageW)) ++ return iter_package(child, assembly); ++ if (!strcmpW(tagname, fileW)) ++ return read_file(child, assembly); ++ if (!strcmpW(tagname, registryKeysW)) ++ return iter_registry_keys(child, assembly); ++ if (!strcmpW(tagname, trustInfoW)) ++ return TRUE; ++ if (!strcmpW(tagname, configurationW)) ++ return TRUE; ++ if (!strcmpW(tagname, deploymentW)) ++ return TRUE; ++ ++ WINE_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) ++{ ++ static const WCHAR assemblyW[] = {'a','s','s','e','m','b','l','y',0}; ++ static const WCHAR displaynameW[] = {'d','i','s','p','l','a','y','N','a','m','e',0}; ++ struct assembly_entry *entry = NULL; ++ IXMLDOMElement *root = NULL; ++ ++ WINE_TRACE("Loading manifest %s\n", debugstr_w(filename)); ++ ++ if (!(root = load_xml(filename))) return NULL; ++ if (!check_xml_tagname(root, assemblyW)) ++ { ++ WINE_FIXME("Didn't find assembly root node?\n"); ++ goto done; ++ } ++ ++ if ((entry = alloc_assembly())) ++ { ++ entry->filename = strdupW(filename); ++ entry->displayname = get_xml_attribute(root, displaynameW); ++ if (iter_assembly(root, entry)) goto done; ++ free_assembly(entry); ++ entry = NULL; ++ } ++ ++done: ++ IXMLDOMElement_Release(root); ++ return entry; ++} ++ ++/* */ ++static BOOL read_update_package(IXMLDOMElement *child, WCHAR *tagname, void *context) ++{ ++ static const WCHAR sourceW[] = {'s','o','u','r','c','e',0}; ++ static const WCHAR assemblyIdentityW[] = {'a','s','s','e','m','b','l','y','I','d','e','n','t','i','t','y',0}; ++ struct dependency_entry *entry; ++ struct list *update_list = context; ++ ++ if (!strcmpW(tagname, sourceW)) return TRUE; ++ if (strcmpW(tagname, assemblyIdentityW)) ++ { ++ WINE_TRACE("Ignoring unexpected tag %s\n", debugstr_w(tagname)); ++ return TRUE; ++ } ++ ++ if ((entry = alloc_dependency())) ++ { ++ if (read_identity(child, &entry->identity)) ++ { ++ WINE_TRACE("Found update %s\n", debugstr_w(entry->identity.name)); ++ list_add_tail(update_list, &entry->entry); ++ return TRUE; ++ } ++ free_dependency(entry); ++ } ++ ++ return FALSE; ++} ++static BOOL iter_update_package(IXMLDOMElement *root, struct list *update_list) ++{ ++ return call_xml_callbacks(root, read_update_package, update_list); ++} ++ ++/* */ ++static BOOL read_servicing(IXMLDOMElement *child, WCHAR *tagname, void *context) ++{ ++ static const WCHAR packageW[] = {'p','a','c','k','a','g','e',0}; ++ static const WCHAR installW[] = {'i','n','s','t','a','l','l',0}; ++ static const WCHAR actionW[] = {'a','c','t','i','o','n',0}; ++ struct list *update_list = context; ++ WCHAR *action; ++ BOOL ret = TRUE; ++ ++ if (strcmpW(tagname, packageW)) ++ { ++ WINE_FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); ++ return TRUE; ++ } ++ ++ if (!(action = get_xml_attribute(child, actionW))) ++ { ++ WINE_FIXME("Servicing tag doesn't specify action\n"); ++ return FALSE; ++ } ++ ++ if (!strcmpW(action, installW)) ++ ret = iter_update_package(child, update_list); ++ else ++ WINE_FIXME("action %s not supported\n", debugstr_w(action)); ++ ++ heap_free(action); ++ return ret; ++} ++static BOOL iter_servicing(IXMLDOMElement *root, struct list *update_list) ++{ ++ return call_xml_callbacks(root, read_servicing, update_list); ++} ++ ++/* */ ++static BOOL read_unattend(IXMLDOMElement *child, WCHAR *tagname, void *context) ++{ ++ static const WCHAR servicingW[] = {'s','e','r','v','i','c','i','n','g',0}; ++ struct list *update_list = context; ++ ++ if (strcmpW(tagname, servicingW)) ++ { ++ WINE_FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); ++ return TRUE; ++ } ++ ++ return iter_servicing(child, update_list); ++ ++} ++static BOOL iter_unattend(IXMLDOMElement *root, struct list *update_list) ++{ ++ return call_xml_callbacks(root, read_unattend, update_list); ++} ++ ++BOOL load_update(const WCHAR *filename, struct list *update_list) ++{ ++ static const WCHAR unattendW[] = {'u','n','a','t','t','e','n','d',0}; ++ IXMLDOMElement *root = NULL; ++ BOOL ret = FALSE; ++ ++ WINE_TRACE("Reading update %s\n", debugstr_w(filename)); ++ ++ if (!(root = load_xml(filename))) return FALSE; ++ if (!check_xml_tagname(root, unattendW)) ++ { ++ WINE_FIXME("Didn't find unattend root node?\n"); ++ goto done; ++ } ++ ++ ret = iter_unattend(root, update_list); ++ ++done: ++ IXMLDOMElement_Release(root); ++ return ret; ++} +diff --git a/programs/wusa/wusa.h b/programs/wusa/wusa.h +new file mode 100644 +index 0000000..eada6d9 +--- /dev/null ++++ b/programs/wusa/wusa.h +@@ -0,0 +1,160 @@ ++/* ++ * 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 "wine/unicode.h" ++#include "wine/list.h" ++#include ++ ++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; ++void free_dependency(struct dependency_entry *entry) DECLSPEC_HIDDEN; ++struct assembly_entry *load_manifest(const WCHAR *filename) DECLSPEC_HIDDEN; ++BOOL load_update(const WCHAR *filename, struct list *update_list) 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 void *heap_realloc(void *mem, size_t len) __WINE_ALLOC_SIZE(2); ++static inline void *heap_realloc(void *mem, size_t len) ++{ ++ return HeapReAlloc(GetProcessHeap(), 0, mem, len); ++} ++ ++static void *heap_realloc_zero(void *mem, size_t len) __WINE_ALLOC_SIZE(2); ++static inline void *heap_realloc_zero(void *mem, size_t len) ++{ ++ return HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, mem, len); ++} ++ ++static inline BOOL heap_free(void *mem) ++{ ++ return HeapFree(GetProcessHeap(), 0, mem); ++} ++ ++static inline char *strdupWtoA(const WCHAR *str) ++{ ++ char *ret = NULL; ++ DWORD len; ++ ++ if (!str) return ret; ++ len = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL); ++ if ((ret = heap_alloc(len))) ++ WideCharToMultiByte(CP_ACP, 0, str, -1, ret, len, NULL, NULL); ++ return ret; ++} ++ ++static inline WCHAR *strdupAtoW(const char *str) ++{ ++ WCHAR *ret = NULL; ++ DWORD len; ++ ++ if (!str) return ret; ++ len = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0); ++ if ((ret = heap_alloc(len * sizeof(WCHAR)))) ++ MultiByteToWideChar(CP_ACP, 0, str, -1, ret, len); ++ return ret; ++} ++ ++static inline WCHAR *strdupW(const WCHAR *str) ++{ ++ WCHAR *ret; ++ if (!str) return NULL; ++ ret = heap_alloc((strlenW(str) + 1) * sizeof(WCHAR)); ++ if (ret) ++ 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.6.4 + diff --git a/patches/wusa-MSU_Package_Installer/0002-wusa-Ignore-systemProtection-subkey-of-registry-key.patch b/patches/wusa-MSU_Package_Installer/0002-wusa-Ignore-systemProtection-subkey-of-registry-key.patch new file mode 100644 index 00000000..03ded3e2 --- /dev/null +++ b/patches/wusa-MSU_Package_Installer/0002-wusa-Ignore-systemProtection-subkey-of-registry-key.patch @@ -0,0 +1,32 @@ +From f4415437d092f621e2cb292cf290a7910165d05f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michael=20M=C3=BCller?= +Date: Mon, 21 Dec 2015 01:45:07 +0100 +Subject: wusa: Ignore systemProtection subkey of registry key. + +--- + programs/wusa/manifest.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/programs/wusa/manifest.c b/programs/wusa/manifest.c +index fc1f65b..63e56e0 100644 +--- a/programs/wusa/manifest.c ++++ b/programs/wusa/manifest.c +@@ -449,6 +449,7 @@ error: + static BOOL read_registry_key(IXMLDOMElement *child, WCHAR *tagname, void *context) + { + static const WCHAR securityDescriptorW[] = {'s','e','c','u','r','i','t','y','D','e','s','c','r','i','p','t','o','r',0}; ++ static const WCHAR systemProtectionW[] = {'s','y','s','t','e','m','P','r','o','t','e','c','t','i','o','n',0}; + static const WCHAR registryValueW[] = {'r','e','g','i','s','t','r','y','V','a','l','u','e',0}; + static const WCHAR nameW[] = {'n','a','m','e',0}; + static const WCHAR valueTypeW[] = {'v','a','l','u','e','T','y','p','e',0}; +@@ -457,6 +458,7 @@ static BOOL read_registry_key(IXMLDOMElement *child, WCHAR *tagname, void *conte + struct registrykv_entry *entry; + + if (!strcmpW(tagname, securityDescriptorW)) return TRUE; ++ if (!strcmpW(tagname, systemProtectionW)) return TRUE; + if (strcmpW(tagname, registryValueW)) + { + WINE_FIXME("Ignoring unexpected tag %s\n", debugstr_w(tagname)); +-- +2.6.4 + diff --git a/patches/wusa-MSU_Package_Installer/0003-wusa-Treat-empty-update-list-as-error.patch b/patches/wusa-MSU_Package_Installer/0003-wusa-Treat-empty-update-list-as-error.patch new file mode 100644 index 00000000..b2b205f5 --- /dev/null +++ b/patches/wusa-MSU_Package_Installer/0003-wusa-Treat-empty-update-list-as-error.patch @@ -0,0 +1,29 @@ +From e489fac14e207ba0973c145ede43587457cc0be1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michael=20M=C3=BCller?= +Date: Mon, 21 Dec 2015 01:46:34 +0100 +Subject: wusa: Treat empty update list as error. + +--- + programs/wusa/main.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/programs/wusa/main.c b/programs/wusa/main.c +index 7c1dfef..9cd8a2d 100644 +--- a/programs/wusa/main.c ++++ b/programs/wusa/main.c +@@ -998,6 +998,12 @@ static BOOL install_msu(WCHAR *filename, struct installer_state *state) + } + } + ++ if (list_empty(&state->updates)) ++ { ++ WINE_ERR("No updates found, probably incompatible MSU file format?\n"); ++ goto done; ++ } ++ + /* perform dryrun */ + set_assembly_status(&state->assemblies, ASSEMBLY_STATUS_NONE); + if (!install_updates(state, TRUE)) +-- +2.6.4 + diff --git a/patches/wusa-MSU_Package_Installer/0004-wusa-Implement-WOW64-support.patch b/patches/wusa-MSU_Package_Installer/0004-wusa-Implement-WOW64-support.patch new file mode 100644 index 00000000..57ea4895 --- /dev/null +++ b/patches/wusa-MSU_Package_Installer/0004-wusa-Implement-WOW64-support.patch @@ -0,0 +1,126 @@ +From 3bd8282421aad38c45c38d2f3510c46560a4902d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michael=20M=C3=BCller?= +Date: Mon, 21 Dec 2015 01:47:59 +0100 +Subject: wusa: Implement WOW64 support. + +--- + programs/wusa/main.c | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 58 insertions(+), 1 deletion(-) + +diff --git a/programs/wusa/main.c b/programs/wusa/main.c +index 9cd8a2d..5dae802 100644 +--- a/programs/wusa/main.c ++++ b/programs/wusa/main.c +@@ -66,6 +66,9 @@ struct installer_state + struct list updates; + }; + ++static const WCHAR x86W[] = {'x','8','6',0}; ++static const WCHAR amd64W[] = {'a','m','d','6','4',0}; ++ + static BOOL strbuf_init(struct strbuf *buf) + { + buf->pos = 0; +@@ -375,6 +378,14 @@ static WCHAR *lookup_expression(struct assembly_entry *assembly, const WCHAR *ke + + if (!strcmpW(key, runtime_system32)) + { ++ #if defined(__x86_64__) ++ if (!strcmpW(assembly->identity.architecture, x86W)) ++ { ++ GetSystemWow64DirectoryW(path, sizeof(path)/sizeof(path[0])); ++ return strdupW(path); ++ } ++ #endif ++ + GetSystemDirectoryW(path, sizeof(path)/sizeof(path[0])); + return strdupW(path); + } +@@ -691,8 +702,13 @@ static BOOL install_registry(struct assembly_entry *assembly, BOOL dryrun) + struct registrykv_entry *registrykv; + HKEY root, subkey; + WCHAR *path; ++ REGSAM sam = KEY_ALL_ACCESS; + BOOL ret = TRUE; + ++#if defined(__x86_64__) ++ if (!strcmpW(assembly->identity.architecture, x86W)) sam |= KEY_WOW64_32KEY; ++#endif ++ + LIST_FOR_EACH_ENTRY(registryop, &assembly->registryops, struct registryop_entry, entry) + { + if (!(path = split_registry_key(registryop->key, &root))) +@@ -701,7 +717,7 @@ static BOOL install_registry(struct assembly_entry *assembly, BOOL dryrun) + break; + } + +- if (!dryrun && RegCreateKeyExW(root, path, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &subkey, NULL)) ++ if (!dryrun && RegCreateKeyExW(root, path, 0, NULL, REG_OPTION_NON_VOLATILE, sam, NULL, &subkey, NULL)) + { + WINE_ERR("Failed to open registry key %s\n", debugstr_w(registryop->key)); + ret = FALSE; +@@ -772,6 +788,14 @@ static BOOL install_assembly(struct list *manifest_list, struct assembly_identit + return FALSE; + } + ++#if defined(__i386__) ++ if (!strcmpW(assembly->identity.architecture, amd64W)) ++ { ++ WINE_ERR("Can not install amd64 assembly in 32 bit prefix\n"); ++ return FALSE; ++ } ++#endif ++ + assembly->status = ASSEMBLY_STATUS_IN_PROGRESS; + + LIST_FOR_EACH_ENTRY(dependency, &assembly->dependencies, struct dependency_entry, entry) +@@ -1027,6 +1051,37 @@ done: + return ret; + } + ++static void restart_as_x86_64(void) ++{ ++ WCHAR filename[MAX_PATH]; ++ PROCESS_INFORMATION pi; ++ STARTUPINFOW si; ++ DWORD exit_code = 1; ++ BOOL is_wow64; ++ void *redir; ++ ++ if (!IsWow64Process(GetCurrentProcess(), &is_wow64) || !is_wow64) ++ return; ++ ++ memset(&si, 0, sizeof(si)); ++ si.cb = sizeof(si); ++ GetModuleFileNameW(0, filename, MAX_PATH); ++ ++ Wow64DisableWow64FsRedirection(&redir); ++ if (CreateProcessW(filename, GetCommandLineW(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) ++ { ++ WINE_TRACE("Restarting %s\n", wine_dbgstr_w(filename)); ++ WaitForSingleObject(pi.hProcess, INFINITE); ++ GetExitCodeProcess(pi.hProcess, &exit_code); ++ CloseHandle(pi.hProcess); ++ CloseHandle(pi.hThread); ++ } ++ else WINE_ERR("Failed to restart 64-bit %s, err %d\n", wine_dbgstr_w(filename), GetLastError()); ++ Wow64RevertWow64FsRedirection(redir); ++ ++ ExitProcess(exit_code); ++} ++ + int wmain(int argc, WCHAR *argv[]) + { + static const WCHAR norestartW[] = {'/','n','o','r','e','s','t','a','r','t',0}; +@@ -1035,6 +1090,8 @@ int wmain(int argc, WCHAR *argv[]) + WCHAR *filename = NULL; + int i; + ++ restart_as_x86_64(); ++ + state.norestart = FALSE; + state.quiet = FALSE; + +-- +2.6.4 + diff --git a/patches/wusa-MSU_Package_Installer/0005-wusa-Add-workaround-to-be-compatible-with-Vista-pack.patch b/patches/wusa-MSU_Package_Installer/0005-wusa-Add-workaround-to-be-compatible-with-Vista-pack.patch new file mode 100644 index 00000000..ff2bbab0 --- /dev/null +++ b/patches/wusa-MSU_Package_Installer/0005-wusa-Add-workaround-to-be-compatible-with-Vista-pack.patch @@ -0,0 +1,95 @@ +From c848126e160493a7baa717c96e663ef5ebddb542 Mon Sep 17 00:00:00 2001 +From: Sebastian Lackner +Date: Mon, 21 Dec 2015 03:01:05 +0100 +Subject: wusa: Add workaround to be compatible with Vista packages. + +--- + programs/wusa/main.c | 21 +++++++++++++++++++++ + programs/wusa/manifest.c | 22 ++++++++++++++++++++++ + programs/wusa/wusa.h | 1 + + 3 files changed, 44 insertions(+) + +diff --git a/programs/wusa/main.c b/programs/wusa/main.c +index 5dae802..834da33 100644 +--- a/programs/wusa/main.c ++++ b/programs/wusa/main.c +@@ -111,6 +111,13 @@ static BOOL strbuf_append(struct strbuf *buf, const WCHAR *str, DWORD len) + return TRUE; + } + ++static BOOL str_starts_with(const WCHAR *str, const WCHAR *prefix) ++{ ++ DWORD str_len = strlenW(str), prefix_len = strlenW(prefix); ++ if (prefix_len > str_len) return FALSE; ++ return !strncmpiW(str, prefix, prefix_len); ++} ++ + static BOOL str_ends_with(const WCHAR *str, const WCHAR *suffix) + { + DWORD str_len = strlenW(str), suffix_len = strlenW(suffix); +@@ -1003,6 +1010,20 @@ static BOOL install_msu(WCHAR *filename, struct installer_state *state) + FindClose(search); + } + ++ /* Windows Vista MSU files do not contain an xml file - what is the correct way to get ++ * the update list? For now just install all assemblies starting with "Package_for_KB". */ ++ if (list_empty(&state->updates)) ++ { ++ static const WCHAR package_for_kbW[] = {'P','a','c','k','a','g','e','_','f','o','r','_','K','B',0}; ++ struct assembly_entry *assembly; ++ LIST_FOR_EACH_ENTRY(assembly, &state->assemblies, struct assembly_entry, entry) ++ { ++ if (!assembly->identity.name) continue; ++ if (str_starts_with(assembly->identity.name, package_for_kbW)) ++ queue_update(assembly, &state->updates); ++ } ++ } ++ + /* dump package information (for debugging) */ + if (WINE_TRACE_ON(wusa)) + { +diff --git a/programs/wusa/manifest.c b/programs/wusa/manifest.c +index 63e56e0..0d6fae6 100644 +--- a/programs/wusa/manifest.c ++++ b/programs/wusa/manifest.c +@@ -704,3 +704,25 @@ done: + IXMLDOMElement_Release(root); + return ret; + } ++ ++BOOL queue_update(struct assembly_entry *assembly, struct list *update_list) ++{ ++ struct dependency_entry *entry; ++ ++ if (!(entry = alloc_dependency())) ++ return FALSE; ++ ++ if (!(entry->identity.name = strdupW(assembly->identity.name))) goto error; ++ if (!(entry->identity.version = strdupW(assembly->identity.version))) goto error; ++ if (!(entry->identity.architecture = strdupW(assembly->identity.architecture))) goto error; ++ if (!(entry->identity.language = strdupW(assembly->identity.language))) goto error; ++ if (!(entry->identity.pubkey_token = strdupW(assembly->identity.pubkey_token))) goto error; ++ ++ WINE_TRACE("Queued update %s\n", debugstr_w(entry->identity.name)); ++ list_add_tail(update_list, &entry->entry); ++ return TRUE; ++ ++error: ++ free_dependency(entry); ++ return FALSE; ++} +diff --git a/programs/wusa/wusa.h b/programs/wusa/wusa.h +index eada6d9..ef5793b 100644 +--- a/programs/wusa/wusa.h ++++ b/programs/wusa/wusa.h +@@ -82,6 +82,7 @@ void free_assembly(struct assembly_entry *entry) DECLSPEC_HIDDEN; + void free_dependency(struct dependency_entry *entry) DECLSPEC_HIDDEN; + struct assembly_entry *load_manifest(const WCHAR *filename) DECLSPEC_HIDDEN; + BOOL load_update(const WCHAR *filename, struct list *update_list) DECLSPEC_HIDDEN; ++BOOL queue_update(struct assembly_entry *assembly, struct list *update_list) DECLSPEC_HIDDEN; + + static void *heap_alloc(size_t len) __WINE_ALLOC_SIZE(1); + static inline void *heap_alloc(size_t len) +-- +2.6.4 + diff --git a/patches/wusa-MSU_Package_Installer/definition b/patches/wusa-MSU_Package_Installer/definition new file mode 100644 index 00000000..21ed4c82 --- /dev/null +++ b/patches/wusa-MSU_Package_Installer/definition @@ -0,0 +1 @@ +Fixes: [26757] Initial implementation of wusa.exe (MSU package installer) diff --git a/staging/changelog b/staging/changelog index 468e9250..bfc8e97d 100644 --- a/staging/changelog +++ b/staging/changelog @@ -1,6 +1,7 @@ wine-staging (1.8) UNRELEASED; urgency=low * Update nvencodeapi wrapper patchset to version 6.0 and fix Debian compatibility. + * Added patch to implement of wusa.exe (MSU package installer). -- Sebastian Lackner Sun, 20 Dec 2015 22:54:04 +0100 wine-staging (1.8~rc4) unstable; urgency=low