Skip to content

Commit

Permalink
Fix problems with application path and installation directory resolut…
Browse files Browse the repository at this point in the history
…ion (#951)

When we introduced the rename warning in #551, there was broken logic
for resolving the actual invoked program name on Mac and Linux.

**Windows**
We continue to use `CommandLineToArgvW(GetCommandLine(), ..)` to return
the _absolute, full path_ to the entry executable.

**macOS**
Switch from `_NSGetArgv()` to `_NSGetExecutablePath(..)` which, like the
Windows APIs above, returns the full path to the entry executable (or
symlink).

**Linux**
As there is no equivalent to `CommandLineToArgvW` or
`_NSGetExecutablePath` here, we instead do some path computation based
on the form of `argv[0]` that we get from `/proc/self/cmdline`.
- If the value is an absolute path, just use that.
- If the value is relative to the current directory (`./name`) then
combine this with the current directory.
- If the value contains a directory separator (`dir/name`) then also
resolve this from the current directory.
- Otherwise, this `argv[0]` value must have been a file name (`name`)
resolved from the `$PATH`.

If we still don't manage to resolve from the `$PATH`, try and resolve
the symlink `/proc/self/exe` that points to the executable image that
was loaded from disk. Note that we may miss any intermediate link names
here, but it's better than nothing.

---

In addition, we also never tested the .NET Tool scenario after updating
the `ICommandContext.ApplicationPath` to use the entry executable name,
whereas previously this was computed from the .NET assembly path.

Introduce a separate concept, the `InstallationDirectory` that always
points to the home of the core assemblies. This is used for resolving
things like in-box UI helpers.
  • Loading branch information
mjcheetham authored Nov 8, 2022
2 parents 73654ae + 322ef98 commit df9a165
Show file tree
Hide file tree
Showing 39 changed files with 646 additions and 92 deletions.
2 changes: 1 addition & 1 deletion build/GCM.MSBuild.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

Expand Down
3 changes: 2 additions & 1 deletion src/shared/Atlassian.Bitbucket.UI.Avalonia/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ private static void AppMain(object o)
string[] args = (string[]) o;

string appPath = ApplicationBase.GetEntryApplicationPath();
using (var context = new CommandContext(appPath))
string installDir = ApplicationBase.GetInstallationDirectory();
using (var context = new CommandContext(appPath, installDir))
using (var app = new HelperApplication(context))
{
app.RegisterCommand(new CredentialsCommandImpl(context));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net6.0;net472</TargetFrameworks>
<RootNamespace>Atlassian.Bitbucket.UI</RootNamespace>
<AssemblyName>Atlassian.Bitbucket.UI.Shared</AssemblyName>
</PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">netstandard2.0;net472</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net6.0;net472</TargetFrameworks>
<AssemblyName>Atlassian.Bitbucket</AssemblyName>
<RootNamespace>Atlassian.Bitbucket</RootNamespace>
<IsTestProject>false</IsTestProject>
Expand Down
90 changes: 90 additions & 0 deletions src/shared/Core.Tests/Interop/Linux/LinuxFileSystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.IO;
using GitCredentialManager.Interop.Linux;
using Xunit;
using static GitCredentialManager.Tests.TestUtils;

namespace GitCredentialManager.Tests.Interop.Linux
{
public class LinuxFileSystemTests
{
[PlatformFact(Platforms.Linux)]
public static void LinuxFileSystem_IsSamePath_SamePath_ReturnsTrue()
{
var fs = new LinuxFileSystem();

string baseDir = GetTempDirectory();
string fileA = CreateFile(baseDir, "a.file");

Assert.True(fs.IsSamePath(fileA, fileA));
}

[PlatformFact(Platforms.Linux)]
public static void LinuxFileSystem_IsSamePath_DifferentFile_ReturnsFalse()
{
var fs = new LinuxFileSystem();

string baseDir = GetTempDirectory();
string fileA = CreateFile(baseDir, "a.file");
string fileB = CreateFile(baseDir, "b.file");

Assert.False(fs.IsSamePath(fileA, fileB));
Assert.False(fs.IsSamePath(fileB, fileA));
}

[PlatformFact(Platforms.Linux)]
public static void LinuxFileSystem_IsSamePath_SameFileDifferentCase_ReturnsFalse()
{
var fs = new LinuxFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = Path.Combine(baseDir, "A.file");

Assert.False(fs.IsSamePath(fileA1, fileA2));
Assert.False(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.Linux)]
public static void LinuxFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue()
{
var fs = new LinuxFileSystem();

string baseDir = GetTempDirectory();
string subDir = CreateDirectory(baseDir, "subDir1", "subDir2");
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = Path.Combine(subDir, "..", "..", "a.file");

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.Linux)]
public static void LinuxFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue()
{
var fs = new LinuxFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = CreateFileSymlink(baseDir, "a.link", fileA1);

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.Linux)]
public static void LinuxFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue()
{
var fs = new LinuxFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = "./a.file";

using (ChangeDirectory(baseDir))
{
Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}
}
}
}
90 changes: 90 additions & 0 deletions src/shared/Core.Tests/Interop/MacOS/MacOSFileSystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.IO;
using GitCredentialManager.Interop.MacOS;
using Xunit;
using static GitCredentialManager.Tests.TestUtils;

namespace GitCredentialManager.Tests.Interop.MacOS
{
public class MacOSFileSystemTests
{
[PlatformFact(Platforms.MacOS)]
public static void MacOSFileSystem_IsSamePath_SamePath_ReturnsTrue()
{
var fs = new MacOSFileSystem();

string baseDir = GetTempDirectory();
string fileA = CreateFile(baseDir, "a.file");

Assert.True(fs.IsSamePath(fileA, fileA));
}

[PlatformFact(Platforms.MacOS)]
public static void MacOSFileSystem_IsSamePath_DifferentFile_ReturnsFalse()
{
var fs = new MacOSFileSystem();

string baseDir = GetTempDirectory();
string fileA = CreateFile(baseDir, "a.file");
string fileB = CreateFile(baseDir, "b.file");

Assert.False(fs.IsSamePath(fileA, fileB));
Assert.False(fs.IsSamePath(fileB, fileA));
}

[PlatformFact(Platforms.MacOS)]
public static void MacOSFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue()
{
var fs = new MacOSFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = Path.Combine(baseDir, "A.file");

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.MacOS)]
public static void MacOSFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue()
{
var fs = new MacOSFileSystem();

string baseDir = GetTempDirectory();
string subDir = CreateDirectory(baseDir, "subDir1", "subDir2");
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = Path.Combine(subDir, "..", "..", "a.file");

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.MacOS)]
public static void MacOSFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue()
{
var fs = new MacOSFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = CreateFileSymlink(baseDir, "a.link", fileA1);

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.MacOS)]
public static void MacOSFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue()
{
var fs = new MacOSFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = "./a.file";

using (ChangeDirectory(baseDir))
{
Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}
}
}
}
43 changes: 43 additions & 0 deletions src/shared/Core.Tests/Interop/Posix/PosixFileSystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.IO;
using GitCredentialManager.Interop.Posix;
using Xunit;
using static GitCredentialManager.Tests.TestUtils;

namespace GitCredentialManager.Tests.Interop.Posix
{
public class PosixFileSystemTests
{
[PlatformFact(Platforms.Posix)]
public void PosixFileSystem_ResolveSymlinks_FileLinks()
{
string baseDir = GetTempDirectory();
string realPath = CreateFile(baseDir, "realFile.txt");
string linkPath = CreateFileSymlink(baseDir, "linkFile.txt", realPath);

string actual = PosixFileSystem.ResolveSymbolicLinks(linkPath);

Assert.Equal(realPath, actual);
}

[PlatformFact(Platforms.Posix)]
public void PosixFileSystem_ResolveSymlinks_DirectoryLinks()
{
//
// Create a real file inside of a directory that is a symlink
// to another directory.
//
// /tmp/{uuid}/linkDir/ -> /tmp/{uuid}/realDir/
//
string baseDir = GetTempDirectory();
string realDir = CreateDirectory(baseDir, "realDir");
string linkDir = CreateDirectorySymlink(baseDir, "linkDir", realDir);
string filePath = CreateFile(linkDir, "file.txt");

string actual = PosixFileSystem.ResolveSymbolicLinks(filePath);

string expected = Path.Combine(realDir, "file.txt");

Assert.Equal(expected, actual);
}
}
}
77 changes: 77 additions & 0 deletions src/shared/Core.Tests/Interop/Windows/WindowsFileSystemTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.IO;
using GitCredentialManager.Interop.Windows;
using Xunit;
using static GitCredentialManager.Tests.TestUtils;

namespace GitCredentialManager.Tests.Interop.Windows
{
public class WindowsFileSystemTests
{
[PlatformFact(Platforms.Windows)]
public static void WindowsFileSystem_IsSamePath_SamePath_ReturnsTrue()
{
var fs = new WindowsFileSystem();

string baseDir = GetTempDirectory();
string fileA = CreateFile(baseDir, "a.file");

Assert.True(fs.IsSamePath(fileA, fileA));
}

[PlatformFact(Platforms.Windows)]
public static void WindowsFileSystem_IsSamePath_DifferentFile_ReturnsFalse()
{
var fs = new WindowsFileSystem();

string baseDir = GetTempDirectory();
string fileA = CreateFile(baseDir, "a.file");
string fileB = CreateFile(baseDir, "b.file");

Assert.False(fs.IsSamePath(fileA, fileB));
Assert.False(fs.IsSamePath(fileB, fileA));
}

[PlatformFact(Platforms.Windows)]
public static void WindowsFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue()
{
var fs = new WindowsFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = Path.Combine(baseDir, "A.file");

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.Windows)]
public static void WindowsFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue()
{
var fs = new WindowsFileSystem();

string baseDir = GetTempDirectory();
string subDir = CreateDirectory(baseDir, "subDir1", "subDir2");
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = Path.Combine(subDir, "..", "..", "a.file");

Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}

[PlatformFact(Platforms.Windows)]
public static void WindowsFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue()
{
var fs = new WindowsFileSystem();

string baseDir = GetTempDirectory();
string fileA1 = CreateFile(baseDir, "a.file");
string fileA2 = @".\a.file";

using (ChangeDirectory(baseDir))
{
Assert.True(fs.IsSamePath(fileA1, fileA2));
Assert.True(fs.IsSamePath(fileA2, fileA1));
}
}
}
}
3 changes: 2 additions & 1 deletion src/shared/Core.UI/Core.UI.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net6.0;net472</TargetFrameworks>
<AssemblyName>gcmcoreui</AssemblyName>
<RootNamespace>GitCredentialManager.UI</RootNamespace>
</PropertyGroup>
Expand Down
17 changes: 3 additions & 14 deletions src/shared/Core/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ protected override async Task<int> RunInternalAsync(string[] args)
Context.Trace.WriteLine($"Platform: {info.OperatingSystemType} ({info.CpuArchitecture})");
Context.Trace.WriteLine($"OSVersion: {info.OperatingSystemVersion}");
Context.Trace.WriteLine($"AppPath: {Context.ApplicationPath}");
Context.Trace.WriteLine($"InstallDir: {Context.InstallationDirectory}");
Context.Trace.WriteLine($"Arguments: {string.Join(" ", args)}");

var parser = new CommandLineBuilder(rootCommand)
Expand Down Expand Up @@ -252,25 +253,13 @@ Task IConfigurableComponent.UnconfigureAsync(ConfigurationTarget target)
}

// Clear app entry
config.UnsetAll(configLevel, helperKey, Regex.Escape(appPath));
string appEntryValue = currentValues[appIndex];
config.UnsetAll(configLevel, helperKey, Regex.Escape(appEntryValue));
}

return Task.CompletedTask;
}

private string GetGitConfigAppName()
{
const string gitCredentialPrefix = "git-credential-";

string appName = Path.GetFileNameWithoutExtension(Context.ApplicationPath);
if (appName != null && appName.StartsWith(gitCredentialPrefix, StringComparison.OrdinalIgnoreCase))
{
return appName.Substring(gitCredentialPrefix.Length);
}

return Context.ApplicationPath;
}

private string GetGitConfigAppPath()
{
string path = Context.ApplicationPath;
Expand Down
Loading

0 comments on commit df9a165

Please sign in to comment.