Skip to content

Release M276.1#2019

Merged
tyrielv merged 5 commits into
releases/shippedfrom
milestones/m276.1
Jun 11, 2026
Merged

Release M276.1#2019
tyrielv merged 5 commits into
releases/shippedfrom
milestones/m276.1

Conversation

@tyrielv

@tyrielv tyrielv commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Changes:

  • Service
    • Fix regression in automount

github-actions Bot and others added 5 commits June 8, 2026 08:20
….vfs.0.2

Update default Microsoft Git version to v2.54.0.vfs.0.2
GVFS.Service uses CreateProcessAsUser to spawn 'GVFS.exe mount <repo>
--internal_use_only <json>' on each user logon.  The JSON arg
({"ServiceName":null,"StartedByService":true,...}) was concatenated
straight into the command line with no escaping.  Windows
CommandLineToArgvW strips the embedded " characters when re-parsing
argv in the child, so the receiving process sees
{ServiceName:null,StartedByService:true,...}.

Newtonsoft.Json was permissive enough to round-trip that corrupted
payload.  After commit c83beab ("Migrate from Newtonsoft.Json to
System.Text.Json"), System.Text.Json (strict — quoted property names
required) throws JsonException, the verb hits ReportErrorAndExit
*before* AddLogFileEventListener runs, and the child exits without ever
creating a mount log file.  The service then blocks for the full
60-second WaitUntilMounted timeout on each repo, fails automount, and
reports only "the GVFS.Mount process is not responding."

Fix:

  * Add CommandLineEscaping.EscapeArgument implementing the
    CommandLineToArgvW escaping rules (quote when needed, double
    backslashes that precede a quote terminator, escape literal "
    as \").

  * Replace CurrentUser.RunAs(string, string) with TryRunAs(string,
    string[], out int processId) so callers supply argv values and
    the escaping happens centrally.  Also surface the child PID so
    callers can track its liveness.

  * WindowsPlatform.StartBackgroundVFS4GProcess: route every arg
    through the new escaper (previously only space-containing args
    got naively quoted, embedded " was never escaped).

  * GVFSEnlistment.WaitUntilMounted: add an overload that takes a
    Func<MountProcessSnapshot>.  When supplied, the wait loop polls
    the snapshot between short (500 ms) connect attempts and fails
    fast with the exit code if the child has already terminated,
    instead of blocking for the full pipe timeout.

  * GVFSMountProcess: pass the spawned child's PID into the new
    WaitUntilMounted overload so logon automount detects an early
    GVFS.exe exit within seconds and logs a useful diagnostic
    ("GVFS.Mount process (Id N) exited with code X before the named
    pipe was ready") instead of a 60-second silent timeout.

Tests:

  * CommandLineEscapingTests covers the canonical edge cases
    (spaces, tabs, embedded quotes, trailing backslashes) and the
    exact InternalVerbParameters JSON shape that triggered the
    regression.  A round-trip test feeds the escaped form back
    through CommandLineToArgvW and verifies the original string
    pops out unchanged.

  * WaitUntilMountedProcessTrackingTests verifies the wait
    short-circuits on an exit reported by the first snapshot poll,
    and also catches a late exit reported on a later poll, both
    well under the legacy 60-second timeout.

Assisted-by: Claude Opus 4.7
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Address code-review feedback on the prior commit.  The wait loop used
the spawned mount process's PID and re-opened it via
Process.GetProcessById on every poll, which has two problems:

1. The lookup races with the very failure mode the fast-fail path is
   meant to catch.  If the child exits between CreateProcessAsUser
   returning and GVFSMountProcess looking it up, GetProcessById
   throws ArgumentException, TryGetProcessById returns null, and
   WaitUntilMounted silently falls back to the legacy single 60-second
   blocking Connect — defeating the whole purpose.

2. Querying by PID is vulnerable to PID reuse: if Windows recycles
   the PID quickly enough, a snapshot poll could observe an
   unrelated process's exit state.

Both problems disappear if we keep the process handle that
CreateProcessAsUser already returned.  The kernel keeps the process
object alive for as long as we hold the handle, so liveness and
exit-code queries remain valid even after the child has exited, and
PID reuse is irrelevant because we never look up by PID again.

Changes:

  * CurrentUser.TryRunAs returns a SafeProcessHandle (owned) instead
    of just a PID.  Internally, the thread handle is still closed
    immediately; only the process handle is retained.

  * New ProcessHandleHelper exposes HasExited (non-blocking
    WaitForSingleObject(handle, 0)) and TryGetExitCode
    (GetExitCodeProcess) directly against the handle.  This avoids
    the STILL_ACTIVE (259) ambiguity in GetExitCodeProcess alone —
    we only ask for the exit code once the wait says signaled.

  * GVFSMountProcess.MountRepository owns the SafeProcessHandle in a
    using block spanning the entire WaitUntilMounted call and uses
    the handle for snapshot queries.

  * Removed TryGetProcessById and the Process.GetProcessById /
    Process.HasExited path entirely — no PID lookup remains in the
    automount code path.

Tests:

  * Build clean, 0 errors.
  * All 842 unit tests still pass (24 new + 818 existing).  The
    existing WaitUntilMountedProcessTrackingTests don't need
    changes because they exercise the snapshot delegate, not the
    handle plumbing.

Assisted-by: Claude Opus 4.7
Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Service: Fix automount regression - escape JSON arg passed to child mount processes
@tyrielv tyrielv enabled auto-merge June 11, 2026 18:20
@tyrielv tyrielv merged commit 4849347 into releases/shipped Jun 11, 2026
111 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants