From: Michael Müller michael@fds-team.de
Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com --- programs/wusa/main.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 166 insertions(+), 5 deletions(-)
diff --git a/programs/wusa/main.c b/programs/wusa/main.c index aa7a38fe178..77dd46d8bd4 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 @@ -17,17 +19,176 @@ */
#include "wine/debug.h" +#include "wine/heap.h" +#include "wine/list.h" +#include "wine/unicode.h"
WINE_DEFAULT_DEBUG_CHANNEL(wusa);
+struct installer_tempdir { + struct list entry; + WCHAR *path; +}; + +struct installer_state { + BOOL norestart; + BOOL quiet; + struct list tempdirs; +}; + +static const 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 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 = (WCHAR *)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 = (WCHAR *)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(full_path); +} + +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; + + 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); + } +} + +static BOOL install_msu(WCHAR *filename, struct installer_state *state) +{ + const WCHAR *temp_path; + + list_init(&state->tempdirs); + + WINE_TRACE("Processing msu file %s\n", wine_dbgstr_w(filename)); + if (!(temp_path = create_temp_directory(state))) + return FALSE; + + installer_cleanup(state); + return TRUE; +} + 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"); + 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 0; + return !install_msu(filename, &state); }
From: Michael Müller michael@fds-team.de
Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com --- programs/wusa/Makefile.in | 1 + programs/wusa/main.c | 242 +++++++++++++++++++++++++++++++++++++++++++++- programs/wusa/wusa.h | 56 +++++++++++ 3 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 programs/wusa/wusa.h
diff --git a/programs/wusa/Makefile.in b/programs/wusa/Makefile.in index 5068456a20b..75e41295aea 100644 --- a/programs/wusa/Makefile.in +++ b/programs/wusa/Makefile.in @@ -1,5 +1,6 @@ MODULE = wusa.exe APPMODE = -mconsole -municode +IMPORTS = cabinet shlwapi
C_SRCS = \ main.c diff --git a/programs/wusa/main.c b/programs/wusa/main.c index 77dd46d8bd4..7a289315a4f 100644 --- a/programs/wusa/main.c +++ b/programs/wusa/main.c @@ -18,13 +18,31 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */
+#include <shlwapi.h> +#include <fdi.h> +#include "wusa.h" #include "wine/debug.h" -#include "wine/heap.h" #include "wine/list.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 installer_tempdir { struct list entry; WCHAR *path; @@ -55,6 +73,54 @@ static const WCHAR *path_combine(const WCHAR *path, const WCHAR *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}; @@ -91,6 +157,168 @@ static BOOL delete_directory(const WCHAR *path) return RemoveDirectoryW(full_path); }
+static WCHAR *get_uncompressed_path(PFDINOTIFICATION pfdin) +{ + WCHAR *file = strdupAtoW(pfdin->psz1); + WCHAR *path = (WCHAR *)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; + + WINE_TRACE("extracting %s -> %s\n", wine_dbgstr_a(pfdin->psz1), wine_dbgstr_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: + WINE_FIXME("fdintPARTIAL_FILE not implemented\n"); + return 0; + + case fdintNEXT_CABINET: + WINE_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 const WCHAR *create_temp_directory(struct installer_state *state) { static const WCHAR msuW[] = {'m','s','u',0}; @@ -140,15 +368,21 @@ static void installer_cleanup(struct installer_state *state) static BOOL install_msu(WCHAR *filename, struct installer_state *state) { const WCHAR *temp_path; + BOOL ret = FALSE;
list_init(&state->tempdirs);
WINE_TRACE("Processing msu file %s\n", wine_dbgstr_w(filename)); if (!(temp_path = create_temp_directory(state))) - return FALSE; + return ret; + + if (!extract_cabinet(filename, temp_path)) + WINE_ERR("Failed to extract %s\n", wine_dbgstr_w(filename)); + else + ret = TRUE;
installer_cleanup(state); - return TRUE; + return ret; }
int wmain(int argc, WCHAR *argv[]) diff --git a/programs/wusa/wusa.h b/programs/wusa/wusa.h new file mode 100644 index 00000000000..e9da7372678 --- /dev/null +++ b/programs/wusa/wusa.h @@ -0,0 +1,56 @@ +/* + * 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> +#include "wine/heap.h" +#include "wine/unicode.h" + +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; +}
From: Michael Müller michael@fds-team.de
Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com --- programs/wusa/Makefile.in | 5 +- programs/wusa/main.c | 156 +++++++++- programs/wusa/manifest.c | 730 ++++++++++++++++++++++++++++++++++++++++++++++ programs/wusa/wusa.h | 63 ++++ 4 files changed, 949 insertions(+), 5 deletions(-) create mode 100644 programs/wusa/manifest.c
diff --git a/programs/wusa/Makefile.in b/programs/wusa/Makefile.in index 75e41295aea..05c7bb2594f 100644 --- a/programs/wusa/Makefile.in +++ b/programs/wusa/Makefile.in @@ -1,6 +1,7 @@ MODULE = wusa.exe APPMODE = -mconsole -municode -IMPORTS = cabinet shlwapi +IMPORTS = cabinet shlwapi oleaut32 ole32
C_SRCS = \ - main.c + main.c \ + manfest.c diff --git a/programs/wusa/main.c b/programs/wusa/main.c index 7a289315a4f..4652e9ae081 100644 --- a/programs/wusa/main.c +++ b/programs/wusa/main.c @@ -22,7 +22,6 @@ #include <fdi.h> #include "wusa.h" #include "wine/debug.h" -#include "wine/list.h"
WINE_DEFAULT_DEBUG_CHANNEL(wusa);
@@ -52,8 +51,18 @@ struct installer_state { BOOL norestart; BOOL quiet; struct list tempdirs; + struct list assemblies; + struct list updates; };
+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 const WCHAR *path_combine(const WCHAR *path, const WCHAR *filename) { static const WCHAR backslashW[] = {'\',0}; @@ -355,6 +364,8 @@ static const WCHAR *create_temp_directory(struct installer_state *state) 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) { @@ -363,24 +374,163 @@ static void installer_cleanup(struct installer_state *state) heap_free(tempdir->path); heap_free(tempdir); } + LIST_FOR_EACH_ENTRY_SAFE(assembly, assembly2, &state->assemblies, struct assembly_entry, entry) + { + list_remove(&assembly->entry); + free_assembly(assembly); + } + 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", wine_dbgstr_w(filename)); + + if (!(temp_path = create_temp_directory(state))) + return FALSE; + if (!extract_cabinet(filename, temp_path)) + { + WINE_ERR("Failed to extract %s\n", wine_dbgstr_w(filename)); + return FALSE; + } + + if (!(path = (WCHAR *)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 = (WCHAR *)path_combine(temp_path, data.cFileName))) + continue; + if ((assembly = load_manifest(path))) + list_add_tail(&state->assemblies, &assembly->entry); + heap_free(path); + } while (FindNextFileW(search, &data)); + FindClose(search); + } + + return TRUE; }
+ static BOOL install_msu(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", wine_dbgstr_w(filename)); if (!(temp_path = create_temp_directory(state))) return ret;
if (!extract_cabinet(filename, temp_path)) + { WINE_ERR("Failed to extract %s\n", wine_dbgstr_w(filename)); - else - ret = TRUE; + goto done; + } + + /* load all manifests from contained cabinet archives */ + if (!(path = (WCHAR *)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 = (WCHAR *)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", wine_dbgstr_w(path)); + heap_free(path); + } while (FindNextFileW(search, &data)); + FindClose(search); + }
+ /* load all update descriptions */ + if (!(path = (WCHAR *)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 = (WCHAR *)path_combine(temp_path, data.cFileName))) + continue; + if (!load_update(path, &state->updates)) + WINE_ERR("Failed to load all updates from %s, ignoring\n", wine_dbgstr_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", wine_dbgstr_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", 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; + } + +done: installer_cleanup(state); return ret; } diff --git a/programs/wusa/manifest.c b/programs/wusa/manifest.c new file mode 100644 index 00000000000..ec7d06b0de1 --- /dev/null +++ b/programs/wusa/manifest.c @@ -0,0 +1,730 @@ +/* + * 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 + */ + +#define COBJMACROS +#include "initguid.h" +#include "msxml.h" +#include "wusa.h" +#include "wine/debug.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", wine_dbgstr_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; +} + +/* <assembly><dependency><dependentAssembly> */ +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", wine_dbgstr_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; +} + +/* <assembly><dependency> */ +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", wine_dbgstr_w(tagname)); + return FALSE; + } + + if ((entry = alloc_dependency())) + { + if (read_dependent_assembly(child, &entry->identity)) + { + WINE_TRACE("Found dependency %s\n", wine_dbgstr_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); +} + +/* <assembly><package><update><component> */ +/* <assembly><package><update><package> */ +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", wine_dbgstr_w(tagname)); + return TRUE; + } + + if ((entry = alloc_dependency())) + { + if (read_identity(child, &entry->identity)) + { + WINE_TRACE("Found identity %s\n", wine_dbgstr_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); +} + +/* <assembly><package><update> */ +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", wine_dbgstr_w(tagname)); + return FALSE; +} + +static BOOL iter_update(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_update, assembly); +} + +/* <assembly><package> */ +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", wine_dbgstr_w(tagname)); + return TRUE; +} + +static BOOL iter_package(IXMLDOMElement *root, struct assembly_entry *assembly) +{ + return call_xml_callbacks(root, read_package, assembly); +} + +/* <assembly><file> */ +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", wine_dbgstr_w(entry->source), wine_dbgstr_w(entry->target)); + list_add_tail(&assembly->fileops, &entry->entry); + return TRUE; + +error: + free_fileop(entry); + return FALSE; +} + +/* <assembly><registryKeys><registryKey> */ +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", wine_dbgstr_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", wine_dbgstr_w(entry->name), wine_dbgstr_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); +} + +/* <assembly><registryKeys> */ +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", wine_dbgstr_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", wine_dbgstr_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); +} + +/* <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", wine_dbgstr_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", wine_dbgstr_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; +} + +/* <unattend><servicing><package> */ +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", wine_dbgstr_w(tagname)); + return TRUE; + } + + if ((entry = alloc_dependency())) + { + if (read_identity(child, &entry->identity)) + { + WINE_TRACE("Found update %s\n", wine_dbgstr_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); +} + +/* <unattend><servicing> */ +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", wine_dbgstr_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", wine_dbgstr_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); +} + +/* <unattend> */ +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", wine_dbgstr_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", wine_dbgstr_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 index e9da7372678..6b4a2ed6ab8 100644 --- a/programs/wusa/wusa.h +++ b/programs/wusa/wusa.h @@ -19,8 +19,71 @@
#include <windows.h> #include "wine/heap.h" +#include "wine/list.h" #include "wine/unicode.h"
+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 inline char *strdupWtoA(const WCHAR *str) { char *ret = NULL;
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; +}
Vijay Kiran Kamuju infyquest@gmail.com writes:
From: Michael Müller michael@fds-team.de
Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com
programs/wusa/main.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 166 insertions(+), 5 deletions(-)
This is still an awful lot of code, and the way it's split doesn't seem to make much sense. I'd suggest to start by writing some tests.
Hi Alexander,
I will check with Alistair & Zebediah from wine-staging on how to go about writing tests for this and splitting this patch series from staging.
Hi Alistair/Zebediah,
Can you help me on this.
Thanks, Vijay
On Thu, Mar 29, 2018 at 6:06 PM, Alexandre Julliard julliard@winehq.org wrote:
Vijay Kiran Kamuju infyquest@gmail.com writes:
From: Michael Müller michael@fds-team.de
Signed-off-by: Vijay Kiran Kamuju infyquest@gmail.com
programs/wusa/main.c | 171 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 166 insertions(+), 5 deletions(-)
This is still an awful lot of code, and the way it's split doesn't seem to make much sense. I'd suggest to start by writing some tests.
-- Alexandre Julliard julliard@winehq.org
Hi Vijay,
I haven't look at this patchset directly, so here is how I would approach this one.
1. Look at the command line parameters of wusa.
2. Create the simplest MSU file as possible, with a directory and a file.
3. Pick "update /extract:" as first test
4. Check the files are extracted to where you think they should. 5. Cleanup.
(You can check how MSI does testing, wusa will be similar).
Alistair.