smb2: publish loaded settings to the SMB backend, harden the pool paths

Commit e9f9dd443b moved retroarch_smb_init() to the top of rarch_main()
so the SMB backend has a registered configuration before anything can
touch an smb:// path. That intent is kept, but the move exposed an
asymmetry in the published struct: the string fields alias
settings->arrays and therefore follow every configuration load on their
own, while timeout, num_contexts and auth_mode are copied by value.
Since configuration loading happens later, inside retroarch_main_init(),
the numeric fields froze at the calloc-zero settings struct: the user's
configured values were never applied, the zero-fallbacks in smb_init()
always fired, and auth_mode always resolved to SEC_UNDEFINED, forcing
the KRB5/NTLMSSP probe on every first connection regardless of the
configured mode.

Reading the settings from inside the backend is not an option -
vfs_implementation_smb.c lives in libretro-common and must not depend
on RetroArch's configuration - so the fix is on the publisher side:
config_load_file() now re-publishes at its single success return, which
covers the main configuration, the salamander pass, appends and
per-core overrides alike, and keeps working if startup ordering changes
again. retroarch_smb_init() gains a declaration in retroarch.h (it had
none; configuration.c needs one) and comments documenting the
alias-vs-snapshot contract at both the definition and the declaration.
The early registration call stays. The SMB backend initializes lazily
on first smb:// access, which in every startup path happens after the
configuration has been loaded and re-published, so the correct values
are in place by the time the context pool is built.

Two hardenings in the backend paths this touches:

- reset() was a non-static function in a file that griffin.c includes
  into the unity translation unit, i.e. a global symbol named 'reset'
  waiting to collide at link time. Now static as smb_reset_pool(), with
  the loop lowered to C89.

- smb_create_context(), used when healing a dead connection, applied
  smb_cfg->timeout raw, without the zero fallback smb_init() applies,
  so a healed context could be configured with timeout 0 where the
  original pool got the default. Both paths now agree.

Verified offline before commit:

- Full Makefile build with --enable-builtinsmbclient (compiles the
  backend, the vendored libsmb2 and the new hook), clean link, no new
  warnings outside vendored deps; 'reset' no longer an exported symbol.
- Sanitizer sweep of the backend lifecycle: the translation unit linked
  against scripted libsmb2 stubs (real vendored headers supply
  prototypes and struct layouts, so signature drift is a compile
  error), driven through nine scenarios via its public entry points:
  unregistered config, empty server, normal init with explicit values
  respected and round-robin use, zero num_contexts/timeout fallbacks,
  the auth-probe path, context-creation failure mid-loop, connect
  failure mid-loop, retry after failure, and re-publish with changed
  values between init/shutdown cycles (the config_load_file refresh
  this commit adds). ASan+UBSan+LSan clean, TSan clean, no leaks on
  any failure path.
- Settings table drift check and griffin-order unity compile both
  clean; the emitted configuration tables are unchanged.

Known limitations, unchanged by this commit: values only take effect
for contexts created after publication - the pool is not rebuilt when
settings change at runtime - and get_smb_context()'s round-robin index
is not synchronized, so concurrent VFS access from multiple threads can
hand the same non-thread-safe smb2_context to two callers. Both are
follow-up material.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-08-10 01:46:59 +02:00
1 parent 558b24a685
commit 36766173e8
4 files changed
+50 -8

No files matched your search

+8
View File
@@ -7104,6 +7104,14 @@ static bool config_load_file(global_t *global,
free(path_settings);
if (size_settings)
free(size_settings);
#ifdef HAVE_SMBCLIENT
/* Re-publish the SMB client settings: the numeric fields are
* copied by value at publish time, so the values this load just
* applied are not visible to the SMB backend until re-published.
* Covers the main configuration, appends and per-core overrides
* alike, since they all pass through here. */
retroarch_smb_init();
#endif
first_load = false;
return true;
}
+14 -8
View File
@@ -89,7 +89,9 @@ static struct smb2_context *smb_create_context(void)
smb2_set_password(ctx, smb_cfg->password);
if (smb_cfg->workgroup && *smb_cfg->workgroup)
smb2_set_domain(ctx, smb_cfg->workgroup);
smb2_set_timeout(ctx, smb_cfg->timeout);
/* Same fallback as smb_init(): 0 is not a valid timeout */
smb2_set_timeout(ctx, (smb_cfg->timeout == 0)
? RETRO_SMB2_DEFAULT_CLIENT_TIMEOUT : smb_cfg->timeout);
smb2_set_security_mode(ctx, resolved_auth_mode);
smb2_set_authentication(ctx, resolved_auth_mode);
@@ -131,11 +133,15 @@ static struct smb2_context *smb_heal_if_dead(struct smb2_context *ctx)
return smb_heal_context(ctx);
}
void reset(unsigned num_contexts)
/* Static: this file is compiled into the griffin unity translation
* unit, where a global named 'reset' is a link-time collision waiting
* to happen. */
static void smb_reset_pool(unsigned num_contexts)
{
for (unsigned i = 0; i < num_contexts; i++)
unsigned i;
for (i = 0; i < num_contexts; i++)
{
if(smb_context_pool[i])
if (smb_context_pool[i])
smb2_destroy_context(smb_context_pool[i]);
}
@@ -197,7 +203,7 @@ static bool smb_init(void)
if (!smb_context)
{
fprintf(stderr, "smb_init: error - no smb_context for %d\n", i);
reset(max_context_configured);
smb_reset_pool(max_context_configured);
return false;
}
@@ -264,7 +270,7 @@ static bool smb_init(void)
if (!smb_context)
{
fprintf(stderr, "smb_init - error - no context\n");
reset(max_context_configured);
smb_reset_pool(max_context_configured);
return false;
}
@@ -303,7 +309,7 @@ static bool smb_init(void)
{
fprintf(stderr, "smb_init: error - failed to connect - error_no: %d\n", error_no);
smb2_destroy_context(smb_context);
reset(max_context_configured);
smb_reset_pool(max_context_configured);
return false;
}
@@ -340,7 +346,7 @@ void smb_shutdown(void)
for (i = 0; i < max_context_configured; i++)
smb_close_context(i);
reset(max_context_configured);
smb_reset_pool(max_context_configured);
}
/* Build full SMB path from settings */
+13
View File
@@ -385,6 +385,19 @@ void retro_input_poll_null(void) { }
#ifdef HAVE_SMBCLIENT
static struct smb_settings smb_global_cfg;
/* Publishes the SMB client settings to the VFS SMB backend.
*
* Called once early in rarch_main() so the backend has a registered
* configuration before anything can touch an smb:// path, and again at
* the end of every successful config_load_file(). The second call is
* what makes the numeric fields correct: the string fields below alias
* settings->arrays and therefore follow every config load on their
* own, but timeout, num_contexts and auth_mode are copied by value and
* would otherwise keep whatever the settings struct held at the early
* call, which is zero - config loading happens later, inside
* retroarch_main_init(). The SMB backend reads the configuration
* lazily on first smb:// access, which in every startup path is after
* the configuration has been loaded and re-published. */
void retroarch_smb_init(void)
{
settings_t *settings = config_get_ptr();
+15
View File
@@ -158,6 +158,21 @@ void retroarch_favorites_init(void);
void retroarch_favorites_deinit(void);
#ifdef HAVE_SMBCLIENT
/**
* retroarch_smb_init:
*
* Publishes the SMB client settings to the VFS SMB backend. The string
* fields alias settings->arrays and therefore always read current
* values, but the numeric fields (timeout, num_contexts, auth_mode)
* are copied by value, so this must be called again whenever a
* configuration is loaded; config_load_file() does so. Safe to call
* repeatedly. Values only take effect for SMB contexts created after
* the call - the existing pool is not rebuilt.
**/
void retroarch_smb_init(void);
#endif
/* Audio */
/**