Skip to content

Commit ef7c132

Browse files
committed
Service: add versioned install layout support to Configuration and PendingUpgradeHandler
Add CurrentVersionPath to Configuration that resolves a Current\ junction under the install root, falling back to the flat layout when no junction exists. GVFSLocation is now computed dynamically so junction re-targeting takes effect without restarting the service. Update PendingUpgradeHandler.GetInstalledMountProcesses to recognize mount processes from any Versions\ subdirectory, not just the flat install root. These changes are backward-compatible: on existing flat installs, CurrentVersionPath falls back to AssemblyPath and all behavior is identical. This is groundwork for a versioned install layout (Plan 002) where each version lives in its own directory and a Current junction points to the active version, enabling non-disruptive upgrades. Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent b65af3a commit ef7c132

2 files changed

Lines changed: 61 additions & 14 deletions

File tree

GVFS/GVFS.Service/Configuration.cs

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
using GVFS.Common;
2+
using System;
23
using System.IO;
34

45
namespace GVFS.Service
56
{
67
public class Configuration
78
{
9+
/// <summary>
10+
/// Subdirectory name for the junction that points to the active version.
11+
/// When a versioned install layout is present, <c>{app}\Current</c> is a
12+
/// junction pointing to <c>{app}\Versions\{version}</c>.
13+
/// </summary>
14+
public const string CurrentVersionDirName = "Current";
15+
16+
private static readonly Lazy<string> assemblyPath =
17+
new Lazy<string>(ProcessHelper.GetCurrentProcessLocation);
18+
819
private static Configuration instance = new Configuration();
9-
private static string assemblyPath = null;
1020

1121
private Configuration()
1222
{
13-
this.GVFSLocation = Path.Combine(AssemblyPath, GVFSPlatform.Instance.Constants.GVFSExecutableName);
1423
}
1524

1625
public static Configuration Instance
@@ -21,19 +30,39 @@ public static Configuration Instance
2130
}
2231
}
2332

24-
public static string AssemblyPath
33+
/// <summary>
34+
/// The directory containing <c>GVFS.Service.exe</c> (the install root).
35+
/// </summary>
36+
public static string AssemblyPath => assemblyPath.Value;
37+
38+
/// <summary>
39+
/// The directory containing the current version's binaries.
40+
/// With versioned layout this resolves through the <c>Current</c> junction
41+
/// (e.g. <c>{app}\Current</c> → <c>{app}\Versions\1.0.X</c>).
42+
/// Falls back to <see cref="AssemblyPath"/> when no <c>Current</c>
43+
/// junction exists (flat/legacy layout).
44+
/// Evaluated on each access so that junction re-targeting is picked up
45+
/// without restarting the service.
46+
/// </summary>
47+
public static string CurrentVersionPath
2548
{
2649
get
2750
{
28-
if (assemblyPath == null)
51+
string currentDir = Path.Combine(AssemblyPath, CurrentVersionDirName);
52+
if (Directory.Exists(currentDir))
2953
{
30-
assemblyPath = ProcessHelper.GetCurrentProcessLocation();
54+
return currentDir;
3155
}
3256

33-
return assemblyPath;
57+
// Legacy flat layout — binaries are siblings of the service.
58+
return AssemblyPath;
3459
}
3560
}
3661

37-
public string GVFSLocation { get; private set; }
62+
/// <summary>
63+
/// Full path to the current version's <c>GVFS.exe</c>. Resolved
64+
/// dynamically so that junction re-targeting takes effect immediately.
65+
/// </summary>
66+
public string GVFSLocation => Path.Combine(CurrentVersionPath, GVFSPlatform.Instance.Constants.GVFSExecutableName);
3867
}
3968
}

GVFS/GVFS.Service/PendingUpgradeHandler.cs

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,15 @@ public static bool IsPending()
8282

8383
/// <summary>
8484
/// Returns GVFS.Mount processes whose executable is in the install
85-
/// directory. Processes from dev builds or other installs are excluded
86-
/// so they don't block upgrades of the system install. If a process's
87-
/// path cannot be read (access denied, 32/64-bit mismatch), it is
88-
/// included conservatively.
85+
/// directory (or any versioned subdirectory). Processes from dev builds
86+
/// or other installs are excluded so they don't block upgrades of the
87+
/// system install. If a process's path cannot be read (access denied,
88+
/// 32/64-bit mismatch), it is included conservatively.
8989
/// Caller must dispose the returned Process objects.
9090
/// </summary>
9191
public static List<Process> GetInstalledMountProcesses(ITracer tracer)
9292
{
9393
string installDir = Configuration.AssemblyPath;
94-
string expectedPath = Path.Combine(installDir, MountExeName);
9594
Process[] allMountProcesses = Process.GetProcessesByName(MountProcessName);
9695
List<Process> installed = new List<Process>();
9796

@@ -101,8 +100,7 @@ public static List<Process> GetInstalledMountProcesses(ITracer tracer)
101100
try
102101
{
103102
string processPath = process.MainModule?.FileName;
104-
if (processPath != null &&
105-
!PathComparer.Equals(processPath, expectedPath))
103+
if (processPath != null && !IsInstalledMountPath(installDir, processPath))
106104
{
107105
include = false;
108106
tracer.RelatedInfo(
@@ -326,6 +324,26 @@ private static bool IsMarkerFile(string relativePath)
326324
string.Equals(relativePath, Phase1CompleteMarkerFileName, StringComparison.OrdinalIgnoreCase);
327325
}
328326

327+
/// <summary>
328+
/// Returns true if the given GVFS.Mount.exe path is under the install
329+
/// directory — either in the flat layout (<c>{app}\GVFS.Mount.exe</c>)
330+
/// or in a versioned subdirectory (<c>{app}\Versions\*\GVFS.Mount.exe</c>
331+
/// or <c>{app}\Current\GVFS.Mount.exe</c>).
332+
/// </summary>
333+
private static bool IsInstalledMountPath(string installDir, string processPath)
334+
{
335+
// Verify filename is actually GVFS.Mount.exe (not some other exe
336+
// under the install dir).
337+
if (!PathComparer.Equals(Path.GetFileName(processPath), MountExeName))
338+
{
339+
return false;
340+
}
341+
342+
// Verify the exe lives under the install directory.
343+
string normalizedInstallDir = installDir.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
344+
return processPath.StartsWith(normalizedInstallDir, StringComparison.OrdinalIgnoreCase);
345+
}
346+
329347
/// <summary>
330348
/// Moves a file, retrying once after killing any process that has the
331349
/// source file locked (e.g. a GVFS process that started mid-upgrade).

0 commit comments

Comments
 (0)