Subject: [PATCH 2/3] [...] ntdll: Support loading system dynamic libraries.

From: Zebediah Figura <[email-masked]>
Date: Tue, 14 Sep 2021 16:02:18 -0500

This patch leaves a couple of unanswered questions:

* Do we need to copy DLLs to somewhere in the wineprefix, say
  C:/windows/system32/ext, instead of loading them directly from the Z: drive?
  This shouldn't be too hard if so, though. In setupapi, when copying builtin
  DLLs to the prefix, we'd presumably want to trawl their import tables, looking
  for imported modules in %WINESYSTEMDLLDIR% (which this patch already
  introduces), and so on recursively for system DLLs. And then we'd use that
  path instead of %WINESYSTEMDLLDIR% in ntdll.

* Should we add a list of fallback paths for SYSTEMDLLDIR? Or try to autodetect
  it in configure somehow? Unfortunately distributions are not consistent with
  where they put their files.

* Should we allow overriding the system DLL path at runtime? This would be a
  nice feature to have for development. (Should we use WINEDLLPATH or introduce
  some other variable? Note that we probably shouldn't set the SYSTEM flag on
  builtin DLLs if so.)
---
 configure.ac                   |  4 +++
 dlls/ntdll/loader.c            | 49 +++++++++++++++++++++-------------
 dlls/ntdll/unix/env.c          |  1 +
 dlls/ntdll/unix/loader.c       |  5 ++++
 dlls/ntdll/unix/unix_private.h |  1 +
 include/winternl.h             |  3 ++-
 6 files changed, 44 insertions(+), 19 deletions(-)

diff --git a/configure.ac b/configure.ac
index eb970972936..79b84e2319a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -36,6 +36,8 @@ AC_ARG_ENABLE(maintainer-mode, AS_HELP_STRING([--enable-maintainer-mode],[enable
 AC_ARG_ENABLE(silent-rules, AS_HELP_STRING([--enable-silent-rules],[use silent build rules (override: "make V=1")]))
 AC_ARG_ENABLE(werror, AS_HELP_STRING([--enable-werror],[treat compilation warnings as errors]))
 
+AC_ARG_VAR([SYSTEMDLLDIR], [path containing system dependency shared libraries])
+
 AC_ARG_WITH(alsa,      AS_HELP_STRING([--without-alsa],[do not use the Alsa sound support]))
 AC_ARG_WITH(capi,      AS_HELP_STRING([--without-capi],[do not use CAPI (ISDN support)]))
 AC_ARG_WITH(cms,       AS_HELP_STRING([--without-cms],[do not use CMS (color management support)]))
@@ -119,6 +121,8 @@ AC_ARG_WITH(wine64,    AS_HELP_STRING([--with-wine64=DIR],[use the 64-bit Wine i
 
 AC_CANONICAL_HOST
 
+AS_VAR_IF([SYSTEMDLLDIR],[],[],[AC_DEFINE_UNQUOTED([SYSTEMDLLDIR],["$SYSTEMDLLDIR"],[Define to the path containing system dependency shared libraries.])])
+
 dnl **** Check for some programs ****
 
 AC_PROG_MAKE_SET
diff --git a/dlls/ntdll/loader.c b/dlls/ntdll/loader.c
index a861acaaf38..acd99fa5977 100644
--- a/dlls/ntdll/loader.c
+++ b/dlls/ntdll/loader.c
@@ -95,6 +95,7 @@ static int free_lib_count;   /* recursion depth of LdrUnloadDll calls */
 static ULONG path_safe_mode;  /* path mode set by RtlSetSearchPathMode */
 static ULONG dll_safe_mode = 1;  /* dll search mode */
 static UNICODE_STRING dll_directory;  /* extra path for LdrSetDllDirectory */
+static UNICODE_STRING system_dll_path; /* path to search for system dependency dlls */
 static DWORD default_search_flags;  /* default flags set by LdrSetDefaultDllDirectories */
 static WCHAR *default_load_path;    /* default dll search path */
 
@@ -185,7 +186,7 @@ static WINE_MODREF *current_modref;
 static WINE_MODREF *last_failed_modref;
 
 static NTSTATUS load_dll( const WCHAR *load_path, const WCHAR *libname, const WCHAR *default_ext,
-                          DWORD flags, WINE_MODREF** pwm );
+                          DWORD flags, WINE_MODREF **pwm, BOOL system );
 static NTSTATUS process_attach( WINE_MODREF *wm, LPVOID lpReserved );
 static FARPROC find_ordinal_export( HMODULE module, const IMAGE_EXPORT_DIRECTORY *exports,
                                     DWORD exp_size, DWORD ordinal, LPCWSTR load_path );
@@ -503,21 +504,23 @@ static WINE_MODREF *get_modref( HMODULE hmod )
  * Find a module from its base name.
  * The loader_section must be locked while calling this function
  */
-static WINE_MODREF *find_basename_module( LPCWSTR name )
+static WINE_MODREF *find_basename_module( LPCWSTR name, BOOL system )
 {
     PLIST_ENTRY mark, entry;
     UNICODE_STRING name_str;
 
     RtlInitUnicodeString( &name_str, name );
 
-    if (cached_modref && RtlEqualUnicodeString( &name_str, &cached_modref->ldr.BaseDllName, TRUE ))
+    if (cached_modref && RtlEqualUnicodeString( &name_str, &cached_modref->ldr.BaseDllName, TRUE )
+            && system == !!(cached_modref->ldr.Flags & LDR_WINE_SYSTEM))
         return cached_modref;
 
     mark = &NtCurrentTeb()->Peb->LdrData->InLoadOrderModuleList;
     for (entry = mark->Flink; entry != mark; entry = entry->Flink)
     {
         LDR_DATA_TABLE_ENTRY *mod = CONTAINING_RECORD(entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
-        if (RtlEqualUnicodeString( &name_str, &mod->BaseDllName, TRUE ))
+        if (RtlEqualUnicodeString( &name_str, &mod->BaseDllName, TRUE )
+                && system == !!(mod->Flags & LDR_WINE_SYSTEM))
         {
             cached_modref = CONTAINING_RECORD(mod, WINE_MODREF, ldr);
             return cached_modref;
@@ -616,6 +619,7 @@ static WINE_MODREF **grow_module_deps( WINE_MODREF *wm, int count )
  */
 static FARPROC find_forwarded_export( HMODULE module, const char *forward, LPCWSTR load_path )
 {
+    BOOL system = !!(get_modref( module )->ldr.Flags & (LDR_WINE_SYSTEM | LDR_WINE_BUILTIN));
     const IMAGE_EXPORT_DIRECTORY *exports;
     DWORD exp_size;
     WINE_MODREF *wm;
@@ -635,10 +639,10 @@ static FARPROC find_forwarded_export( HMODULE module, const char *forward, LPCWS
     if (!wcschr( mod_name, '.' ))
         memcpy( mod_name + (end - forward), L".dll", sizeof(L".dll") );
 
-    if (!(wm = find_basename_module( mod_name )))
+    if (!(wm = find_basename_module( mod_name, system )))
     {
         TRACE( "delay loading %s for '%s'\n", debugstr_w(mod_name), forward );
-        if (load_dll( load_path, mod_name, L".dll", 0, &wm ) == STATUS_SUCCESS &&
+        if (load_dll( load_path, mod_name, L".dll", 0, &wm, system ) == STATUS_SUCCESS &&
             !(wm->ldr.Flags & LDR_DONT_RESOLVE_REFS))
         {
             if (!imports_fixup_done && current_modref)
@@ -806,6 +810,7 @@ void * WINAPI RtlFindExportedRoutineByName( HMODULE module, const char *name )
  */
 static BOOL import_dll( HMODULE module, const IMAGE_IMPORT_DESCRIPTOR *descr, LPCWSTR load_path, WINE_MODREF **pwm )
 {
+    BOOL system = !!(current_modref->ldr.Flags & (LDR_WINE_SYSTEM | LDR_WINE_BUILTIN));
     NTSTATUS status;
     WINE_MODREF *wmImp;
     HMODULE imp_mod;
@@ -839,7 +844,7 @@ static BOOL import_dll( HMODULE module, const IMAGE_IMPORT_DESCRIPTOR *descr, LP
     {
         ascii_to_unicode( buffer, name, len );
         buffer[len] = 0;
-        status = load_dll( load_path, buffer, L".dll", 0, &wmImp );
+        status = load_dll( load_path, buffer, L".dll", 0, &wmImp, system );
     }
     else  /* need to allocate a larger buffer */
     {
@@ -847,7 +852,7 @@ static BOOL import_dll( HMODULE module, const IMAGE_IMPORT_DESCRIPTOR *descr, LP
         if (!ptr) return FALSE;
         ascii_to_unicode( ptr, name, len );
         ptr[len] = 0;
-        status = load_dll( load_path, ptr, L".dll", 0, &wmImp );
+        status = load_dll( load_path, ptr, L".dll", 0, &wmImp, system );
         RtlFreeHeap( GetProcessHeap(), 0, ptr );
     }
 
@@ -1128,7 +1133,7 @@ static NTSTATUS fixup_imports_ilonly( WINE_MODREF *wm, LPCWSTR load_path, void *
 
     prev = current_modref;
     current_modref = wm;
-    if (!(status = load_dll( load_path, L"mscoree.dll", NULL, 0, &imp ))) wm->deps[0] = imp;
+    if (!(status = load_dll( load_path, L"mscoree.dll", NULL, 0, &imp, FALSE ))) wm->deps[0] = imp;
     current_modref = prev;
     if (status)
     {
@@ -2798,7 +2803,7 @@ done:
  */
 static NTSTATUS find_dll_file( const WCHAR *load_path, const WCHAR *libname, const WCHAR *default_ext,
                                UNICODE_STRING *nt_name, WINE_MODREF **pwm, HANDLE *mapping,
-                               SECTION_IMAGE_INFORMATION *image_info, struct file_id *id )
+                               SECTION_IMAGE_INFORMATION *image_info, struct file_id *id, BOOL system )
 {
     WCHAR *ext, *dllname;
     NTSTATUS status;
@@ -2839,7 +2844,7 @@ static NTSTATUS find_dll_file( const WCHAR *load_path, const WCHAR *libname, con
         else
         {
             if (status != STATUS_SXS_KEY_NOT_FOUND) goto done;
-            if ((*pwm = find_basename_module( libname )) != NULL)
+            if ((*pwm = find_basename_module( libname, system )) != NULL)
             {
                 status = STATUS_SUCCESS;
                 goto done;
@@ -2872,18 +2877,21 @@ done:
  * The loader_section must be locked while calling this function.
  */
 static NTSTATUS load_dll( const WCHAR *load_path, const WCHAR *libname, const WCHAR *default_ext,
-                          DWORD flags, WINE_MODREF** pwm )
+                          DWORD flags, WINE_MODREF **pwm, BOOL system )
 {
     UNICODE_STRING nt_name;
     struct file_id id;
     HANDLE mapping = 0;
     SECTION_IMAGE_INFORMATION image_info;
-    NTSTATUS nts;
+    NTSTATUS nts = STATUS_DLL_NOT_FOUND;
     ULONG64 prev;
 
     TRACE( "looking for %s in %s\n", debugstr_w(libname), debugstr_w(load_path) );
 
-    nts = find_dll_file( load_path, libname, default_ext, &nt_name, pwm, &mapping, &image_info, &id );
+    if (system && system_dll_path.Buffer)
+        nts = find_dll_file( system_dll_path.Buffer, libname, default_ext, &nt_name, pwm, &mapping, &image_info, &id, TRUE );
+    if (nts)
+        nts = find_dll_file( load_path, libname, default_ext, &nt_name, pwm, &mapping, &image_info, &id, FALSE );
 
     if (*pwm)  /* found already loaded module */
     {
@@ -2920,6 +2928,9 @@ static NTSTATUS load_dll( const WCHAR *load_path, const WCHAR *libname, const WC
         break;
     }
 
+    if (system)
+        (*pwm)->ldr.Flags |= LDR_WINE_SYSTEM;
+
     if (NtCurrentTeb64())
         NtCurrentTeb64()->Tib.ArbitraryUserPointer = prev;
     else
@@ -2977,7 +2988,7 @@ NTSTATUS WINAPI DECLSPEC_HOTPATCH LdrLoadDll(LPCWSTR path_name, DWORD flags,
 
     RtlEnterCriticalSection( &loader_section );
 
-    nts = load_dll( path_name, libname->Buffer, L".dll", flags, &wm );
+    nts = load_dll( path_name, libname->Buffer, L".dll", flags, &wm, FALSE );
 
     if (nts == STATUS_SUCCESS && !(wm->ldr.Flags & LDR_DONT_RESOLVE_REFS))
     {
@@ -3009,7 +3020,7 @@ NTSTATUS WINAPI LdrGetDllHandle( LPCWSTR load_path, ULONG flags, const UNICODE_S
 
     RtlEnterCriticalSection( &loader_section );
 
-    status = find_dll_file( load_path, name->Buffer, L".dll", &nt_name, &wm, &mapping, &image_info, &id );
+    status = find_dll_file( load_path, name->Buffer, L".dll", &nt_name, &wm, &mapping, &image_info, &id, FALSE );
 
     if (wm) *base = wm->ldr.DllBase;
     else
@@ -3673,7 +3684,7 @@ static void init_wow64( CONTEXT *context )
         NTSTATUS status;
         static const WCHAR wow64_path[] = L"C:\\windows\\system32\\wow64.dll";
 
-        if ((status = load_dll( NULL, wow64_path, NULL, 0, &wm )))
+        if ((status = load_dll( NULL, wow64_path, NULL, 0, &wm, FALSE )))
         {
             ERR( "could not load %s, status %x\n", debugstr_w(wow64_path), status );
             NtTerminateProcess( GetCurrentProcess(), status );
@@ -3815,6 +3826,8 @@ void WINAPI LdrInitializeThunk( CONTEXT *context, ULONG_PTR unknown2, ULONG_PTR
         load_global_options();
         version_init();
 
+        get_env_var( L"WINESYSTEMDLLDIR", 0, &system_dll_path );
+
         wm = build_main_module();
         wm->ldr.LoadCount = -1;
 
@@ -3822,7 +3835,7 @@ void WINAPI LdrInitializeThunk( CONTEXT *context, ULONG_PTR unknown2, ULONG_PTR
 
         if (NtCurrentTeb()->WowTebOffset) init_wow64( context );
 
-        if ((status = load_dll( NULL, L"kernel32.dll", NULL, 0, &kernel32 )) != STATUS_SUCCESS)
+        if ((status = load_dll( NULL, L"kernel32.dll", NULL, 0, &kernel32, FALSE )) != STATUS_SUCCESS)
         {
             MESSAGE( "wine: could not load kernel32.dll, status %x\n", status );
             NtTerminateProcess( GetCurrentProcess(), status );
diff --git a/dlls/ntdll/unix/env.c b/dlls/ntdll/unix/env.c
index ae1afb2797b..9d4c0be4e38 100644
--- a/dlls/ntdll/unix/env.c
+++ b/dlls/ntdll/unix/env.c
@@ -1303,6 +1303,7 @@ static void add_dynamic_environment( WCHAR **env, SIZE_T *pos, SIZE_T *size )
     add_path_var( env, pos, size, "WINEHOMEDIR", home_dir );
     add_path_var( env, pos, size, "WINEBUILDDIR", build_dir );
     add_path_var( env, pos, size, "WINECONFIGDIR", config_dir );
+    add_path_var( env, pos, size, "WINESYSTEMDLLDIR", system_dll_path );
     for (i = 0; dll_paths[i]; i++)
     {
         sprintf( str, "WINEDLLDIR%u", i );
diff --git a/dlls/ntdll/unix/loader.c b/dlls/ntdll/unix/loader.c
index 6fbd112be01..53b5f6dc1a0 100644
--- a/dlls/ntdll/unix/loader.c
+++ b/dlls/ntdll/unix/loader.c
@@ -379,6 +379,7 @@ const char *data_dir = NULL;
 const char *build_dir = NULL;
 const char *config_dir = NULL;
 const char **dll_paths = NULL;
+const char *system_dll_path = NULL;
 const char *user_name = NULL;
 SECTION_IMAGE_INFORMATION main_image_info = { NULL };
 static HMODULE ntdll_module;
@@ -603,6 +604,10 @@ static void init_paths( char *argv[] )
         data_dir = build_path( bin_dir, BIN_TO_DATADIR );
     }
 
+#ifdef SYSTEMDLLDIR
+    system_dll_path = SYSTEMDLLDIR;
+#endif
+
     set_dll_path();
     set_home_dir();
     set_config_dir();
diff --git a/dlls/ntdll/unix/unix_private.h b/dlls/ntdll/unix/unix_private.h
index 6b8835bcba0..15a3da9ce8e 100644
--- a/dlls/ntdll/unix/unix_private.h
+++ b/dlls/ntdll/unix/unix_private.h
@@ -127,6 +127,7 @@ extern const char *build_dir DECLSPEC_HIDDEN;
 extern const char *config_dir DECLSPEC_HIDDEN;
 extern const char *user_name DECLSPEC_HIDDEN;
 extern const char **dll_paths DECLSPEC_HIDDEN;
+extern const char *system_dll_path DECLSPEC_HIDDEN;
 extern PEB *peb DECLSPEC_HIDDEN;
 extern USHORT *uctable DECLSPEC_HIDDEN;
 extern USHORT *lctable DECLSPEC_HIDDEN;
diff --git a/include/winternl.h b/include/winternl.h
index 24021601480..447144daf75 100644
--- a/include/winternl.h
+++ b/include/winternl.h
@@ -3374,7 +3374,8 @@ typedef void (CALLBACK *PLDR_DLL_NOTIFICATION_FUNCTION)(ULONG, LDR_DLL_NOTIFICAT
 #define LDR_COR_ILONLY                  0x01000000
 
 /* these ones is Wine specific */
-#define LDR_DONT_RESOLVE_REFS           0x40000000
+#define LDR_DONT_RESOLVE_REFS           0x20000000
+#define LDR_WINE_SYSTEM                 0x40000000
 #define LDR_WINE_BUILTIN                0x80000000
 
 /* flag for LdrAddRefDll */
-- 
2.33.0