From 323bddb521907e8952820c3bcadd29c74f45bde1 Mon Sep 17 00:00:00 2001 From: FacuM Date: Sun, 1 Nov 2026 03:15:03 -0300 Subject: [PATCH] svc: make the failed-mount replacement test independent of other drives svc_autostart_failed_mount_is_replaced_by_a_new_mount checks that the new mount of a profile whose autostart mount failed gets the same auto letter back, which proves the failed record released it. The service picked that letter from the live GetLogicalDrives() mask, so a drive mounted by another gate test (dokany.mount, webdav.redirector) between the two picks moved the second mount to another letter ("P:" != "X:" once in x86-ia32). Add svc_opts.logical_drives, the drive mask the letter pick and the target_in_use check read (NULL = GetLogicalDrives). The boot cores of the tests use the fake backend, which maps no letter, and pass a fixed mask with only C: in use, so the autostart takes Z: and the replacement must take Z: again; the test now asserts that letter too. --- apps/shelldrive-host/service.c | 6 ++++-- apps/shelldrive-host/service.h | 4 ++++ tests/unit/sd_svc/test_service.c | 13 ++++++++++++- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/apps/shelldrive-host/service.c b/apps/shelldrive-host/service.c index 02a620f..3578ac4 100644 --- a/apps/shelldrive-host/service.c +++ b/apps/shelldrive-host/service.c @@ -162,6 +162,7 @@ static struct svc_state { const svc_scm_ops *scm_ops; /* svc_opts seams of service.status and service.repair_data */ const svc_repair_ops *repair_ops; const svc_sysproxy_ops *sysproxy; /* proxy = system seams (svc_opts.sysproxy_ops); the shipping ops refuse by policy */ + DWORD (WINAPI *logical_drives)(void); /* svc_opts.logical_drives; GetLogicalDrives when the opts give none */ const wchar_t *repair_base; int running, stopping; int test_all_admin; /* SD_SVC_TEST_HOOKS only, see caller_is_admin */ @@ -685,7 +686,7 @@ static int letter_taken_locked(wchar_t l) /* The drive letter an auto mount takes: the highest free letter from Z: down to D: (0 when none). */ static wchar_t pick_letter_locked(void) { - DWORD used = GetLogicalDrives(); + DWORD used = g.logical_drives(); wchar_t l; for (l = L'Z'; l >= L'D'; l--) { DWORD bit = 1ul << (l - L'A'); @@ -2613,7 +2614,7 @@ static sd_err svc_mount_queue(const char *key, const char *tgt, char out_id[16], } if (letter == 0) letter = pick_letter_locked(); - if (letter == 0 || letter_taken_locked(letter) || (GetLogicalDrives() & (1ul << (letter - L'A')))) { + if (letter == 0 || letter_taken_locked(letter) || (g.logical_drives() &(1ul << (letter - L'A')))) { sd_mutex_unlock(&g.lock); sd_free(copy); *code = "target_in_use"; @@ -3424,6 +3425,7 @@ int svc_run(const svc_opts *o) g.repair_ops = o ? o->repair_ops : NULL; g.repair_base = o ? o->repair_base : NULL; g.sysproxy = (o && o->sysproxy_ops) ? o->sysproxy_ops : &k_sysproxy_shipping; + g.logical_drives = (o && o->logical_drives) ? o->logical_drives : GetLogicalDrives; #ifdef SD_SVC_TEST_HOOKS g.test_all_admin = o ? o->test_all_admin : 0; g.dacl_extra = o ? o->dacl_extra_ace : NULL; diff --git a/apps/shelldrive-host/service.h b/apps/shelldrive-host/service.h index 376941f..62f9258 100644 --- a/apps/shelldrive-host/service.h +++ b/apps/shelldrive-host/service.h @@ -57,6 +57,10 @@ typedef struct svc_opts { const wchar_t *repair_base; /* proxy = system seams of service profiles (svc_sysproxy_ops above). NULL = refused (allow 0) with the real read. */ const svc_sysproxy_ops *sysproxy_ops; + /* The drive letters in use on the machine (GetLogicalDrives bits), read when a mount picks or checks its letter. + NULL = GetLogicalDrives. Tests with the fake backend pass a fixed mask, so the letter of an auto mount does not + depend on drives other processes mount meanwhile. */ + DWORD (WINAPI *logical_drives)(void); } svc_opts; /* Blocks until svc_request_stop (or a stop control). Returns 0, or 1 when the core cannot start (no pipe, no folder diff --git a/tests/unit/sd_svc/test_service.c b/tests/unit/sd_svc/test_service.c index b3792de..48cfc8e 100644 --- a/tests/unit/sd_svc/test_service.c +++ b/tests/unit/sd_svc/test_service.c @@ -2080,6 +2080,13 @@ static HANDLE g_boot_running; static HANDLE g_boot_thread; static int g_boot_rc = -1; +/* The drives the boot cores see in use: only C:. The fake backend maps no letter, so with this fixed mask the letter + of an auto mount depends only on the service's own records, not on drives other tests mount meanwhile. */ +static DWORD WINAPI boot_logical_drives(void) +{ + return 1ul << (L'C' - L'A'); +} + static void boot_core_running(void *ctx) { SetEvent((HANDLE)ctx); @@ -2103,6 +2110,7 @@ static int start_boot_core(svc_opts *o, const wchar_t *root) o->test_all_admin = 1; o->dacl_extra_ace = L"(A;OICI;FA;;;OW)"; o->on_running = boot_core_running; + o->logical_drives = boot_logical_drives; if (sd_ipc_current_identity(sid, SD_COUNTOF(sid), NULL) != SD_OK) return 0; _snwprintf_s(suffix, SD_COUNTOF(suffix), _TRUNCATE, L"svc%lu", (unsigned long)GetCurrentProcessId()); @@ -2337,7 +2345,10 @@ SD_TEST(svc_autostart_failed_mount_is_replaced_by_a_new_mount) sd_json_free(res); } SD_CHECK(mid2[0] != 0 && strcmp(mid2, mid) != 0); /* a new mount id */ - SD_CHECK_STR(tgt2, tgt1); /* the same letter comes back */ + /* the same letter comes back: the boot core sees only C: in use (boot_logical_drives), so the autostart took Z:, + and the new mount takes Z: again only if the failed record no longer holds it (it would get Y: otherwise) */ + SD_CHECK_STR(tgt1, "Z:"); + SD_CHECK_STR(tgt2, tgt1); SD_CHECK(wait_state(c, mid, "gone", 10000, NULL)); /* the failed record was released */ SD_CHECK(wait_state(c, mid2, "mounted", 20000, NULL)); /* the drive is up without an unmount */ /* a live mount of the profile is still refused */