Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions GVFS/GVFS.Common/DiskLayoutUpgrades/DiskLayoutUpgrade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ public abstract class DiskLayoutUpgrade
protected abstract int SourceMinorVersion { get; }
protected abstract bool IsMajorUpgrade { get; }

public static bool TryRunAllUpgrades(string enlistmentRoot)
public static bool TryRunAllUpgrades(string enlistmentRoot, out string error, out ReturnCode returnCode)
{
majorVersionUpgrades = new Dictionary<int, MajorUpgrade>();
minorVersionUpgrades = new Dictionary<int, Dictionary<int, MinorUpgrade>>();
error = null;
returnCode = ReturnCode.Success;

foreach (DiskLayoutUpgrade upgrade in GVFSPlatform.Instance.DiskLayoutUpgrade.Upgrades)
{
Expand All @@ -31,7 +33,7 @@ public static bool TryRunAllUpgrades(string enlistmentRoot)
try
{
DiskLayoutUpgrade upgrade = null;
while (TryFindUpgrade(tracer, enlistmentRoot, out upgrade))
while (TryFindUpgrade(tracer, enlistmentRoot, out upgrade, out error, out returnCode))
{
if (upgrade == null)
{
Expand All @@ -40,11 +42,13 @@ public static bool TryRunAllUpgrades(string enlistmentRoot)

if (!upgrade.TryUpgrade(tracer, enlistmentRoot))
{
returnCode = ReturnCode.GenericError;
return false;
}

if (!CheckLayoutVersionWasIncremented(tracer, enlistmentRoot, upgrade))
{
returnCode = ReturnCode.GenericError;
return false;
}
}
Expand All @@ -55,6 +59,8 @@ public static bool TryRunAllUpgrades(string enlistmentRoot)
{
StartLogFile(enlistmentRoot, tracer);
tracer.RelatedError(e.ToString());
error = e.ToString();
returnCode = ReturnCode.GenericError;
return false;
}
finally
Expand All @@ -64,17 +70,19 @@ public static bool TryRunAllUpgrades(string enlistmentRoot)
}
}

public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRoot, out string error)
public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRoot, out string error, out ReturnCode returnCode)
{
error = string.Empty;
returnCode = ReturnCode.Success;
int majorVersion;
int minorVersion;
try
{
if (TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error))
if (TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error, out returnCode))
{
if (majorVersion < GVFSPlatform.Instance.DiskLayoutUpgrade.Version.MinimumSupportedMajorVersion)
{
returnCode = ReturnCode.GenericError;
error = string.Format(
"Breaking change to GVFS disk layout has been made since cloning. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1} \r\nMinimum supported version: {2}",
majorVersion,
Expand All @@ -85,6 +93,7 @@ public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRo
}
else if (majorVersion > GVFSPlatform.Instance.DiskLayoutUpgrade.Version.CurrentMajorVersion)
{
returnCode = ReturnCode.GenericError;
error = string.Format(
"Changes to GVFS disk layout do not allow mounting after downgrade. Try mounting again using a more recent version of GVFS. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1}",
majorVersion,
Expand All @@ -94,6 +103,7 @@ public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRo
}
else if (majorVersion != GVFSPlatform.Instance.DiskLayoutUpgrade.Version.CurrentMajorVersion)
{
returnCode = ReturnCode.GenericError;
error = string.Format(
"GVFS disk layout version doesn't match current version. Try running 'gvfs mount' to upgrade. \r\nEnlistment disk layout version: {0}.{1} \r\nGVFS disk layout version: {2}.{3}",
majorVersion,
Expand All @@ -112,7 +122,12 @@ public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRo
RepoMetadata.Shutdown();
}

error = "Failed to read disk layout version. " + ConsoleHelper.GetGVFSLogMessage(enlistmentRoot);
if (returnCode != ReturnCode.MissingDiskLayoutVersion)
{
returnCode = ReturnCode.GenericError;
error = "Failed to read disk layout version. " + ConsoleHelper.GetGVFSLogMessage(enlistmentRoot);
}

return false;
}

Expand Down Expand Up @@ -256,13 +271,12 @@ private static bool CheckLayoutVersionWasIncremented(JsonTracer tracer, string e
return true;
}

private static bool TryFindUpgrade(JsonTracer tracer, string enlistmentRoot, out DiskLayoutUpgrade upgrade)
private static bool TryFindUpgrade(JsonTracer tracer, string enlistmentRoot, out DiskLayoutUpgrade upgrade, out string error, out ReturnCode returnCode)
{
int majorVersion;
int minorVersion;

string error;
if (!TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error))
if (!TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error, out returnCode))
{
StartLogFile(enlistmentRoot, tracer);
tracer.RelatedError(error);
Expand Down Expand Up @@ -300,6 +314,8 @@ private static bool TryFindUpgrade(JsonTracer tracer, string enlistmentRoot, out

// return true to indicate that we succeeded, and no upgrader was found
upgrade = null;
error = null;
returnCode = ReturnCode.Success;
return true;
}

Expand All @@ -309,9 +325,22 @@ private static bool TryGetDiskLayoutVersion(
out int majorVersion,
out int minorVersion,
out string error)
{
ReturnCode returnCode;
return TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error, out returnCode);
}

private static bool TryGetDiskLayoutVersion(
ITracer tracer,
string enlistmentRoot,
out int majorVersion,
out int minorVersion,
out string error,
out ReturnCode returnCode)
{
majorVersion = 0;
minorVersion = 0;
returnCode = ReturnCode.GenericError;

string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSPlatform.Instance.Constants.DotGVFSRoot);

Expand All @@ -323,13 +352,19 @@ private static bool TryGetDiskLayoutVersion(
return false;
}

if (!RepoMetadata.Instance.TryGetOnDiskLayoutVersion(out majorVersion, out minorVersion, out error))
if (!RepoMetadata.Instance.TryGetOnDiskLayoutVersion(out majorVersion, out minorVersion, out error, out returnCode))
{
if (returnCode == ReturnCode.MissingDiskLayoutVersion)
{
error += " " + ConsoleHelper.GetGVFSLogMessage(enlistmentRoot);
}

return false;
}
}

error = null;
returnCode = ReturnCode.Success;
return true;
}

Expand Down
13 changes: 12 additions & 1 deletion GVFS/GVFS.Common/RepoMetadata.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace GVFS.Common
{
public class RepoMetadata
{
public const string MissingDiskLayoutVersionMessage = "Enlistment disk layout version not found. The .gvfs metadata is incomplete. Make sure your work is pushed or backed up, then reclone this enlistment.";

private FileBasedDictionary<string, string> repoMetadata;
private ITracer tracer;

Expand Down Expand Up @@ -90,16 +92,24 @@ public static void Shutdown()
}

public bool TryGetOnDiskLayoutVersion(out int majorVersion, out int minorVersion, out string error)
{
ReturnCode returnCode;
return this.TryGetOnDiskLayoutVersion(out majorVersion, out minorVersion, out error, out returnCode);
}

public bool TryGetOnDiskLayoutVersion(out int majorVersion, out int minorVersion, out string error, out ReturnCode returnCode)
{
majorVersion = 0;
minorVersion = 0;
returnCode = ReturnCode.GenericError;

try
{
string value;
if (!this.repoMetadata.TryGetValue(Keys.DiskLayoutMajorVersion, out value))
{
error = "Enlistment disk layout version not found, check if a breaking change has been made to GVFS since cloning this enlistment.";
error = MissingDiskLayoutVersionMessage;
returnCode = ReturnCode.MissingDiskLayoutVersion;
return false;
}

Expand All @@ -125,6 +135,7 @@ public bool TryGetOnDiskLayoutVersion(out int majorVersion, out int minorVersion
}

error = null;
returnCode = ReturnCode.Success;
return true;
}

Expand Down
1 change: 1 addition & 0 deletions GVFS/GVFS.Common/ReturnCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ public enum ReturnCode
AuthenticationError = 9,
CredentialTimeout = 10,
RemoteGvfsConfigError = 11,
MissingDiskLayoutVersion = 12,
}
}
44 changes: 42 additions & 2 deletions GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MountTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
public class MountTests : TestsWithEnlistmentPerFixture
{
private const int GVFSGenericError = 3;
// These values are a shipped command-line contract. Do not bind them to product constants.
private const int GVFSMissingDiskLayoutVersion = 12;
private const string MissingDiskLayoutVersionMessage = "The .gvfs metadata is incomplete";
private const string GVFSLogMessage = "Run 'gvfs log";
private const uint GenericRead = 2147483648;
private const uint FileFlagBackupSemantics = 3355443;
private readonly int fileDeletedBackgroundOperationCode;
Expand Down Expand Up @@ -99,6 +103,34 @@ public void MountChangesMountId()
.ShouldNotEqual(mountId, "gvfs.mount-id should change on every mount");
}

[TestCase]
public void MountFailsWithUniqueExitCodeWhenDiskLayoutVersionIsMissing()
{
this.Enlistment.UnmountGVFS();

string majorVersion;
string minorVersion;
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
majorVersion.ShouldNotBeNull();
minorVersion.ShouldNotBeNull();

try
{
GVFSHelpers.DeletePersistedDiskLayoutMajorVersion(this.Enlistment.DotGVFSRoot);

this.MountShouldFail(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium — Both tests assert only on MissingDiskLayoutVersionMessage. Nothing pins the gvfs log pointer that was added in response to the earlier review, so removing that append would leave CI fully green.

This is the same regression shape I flagged last pass — the valuable half of the message is the half that isn't asserted. A ShouldContain on the log-pointer fragment would close it.

GVFSMissingDiskLayoutVersion,
MissingDiskLayoutVersionMessage,
additionalExpectedErrorMessage: GVFSLogMessage);
}
finally
{
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersion, minorVersion);
}

this.Enlistment.MountGVFS();
}

[TestCase]
public void MountFailsWhenNoOnDiskVersion()
{
Expand Down Expand Up @@ -126,7 +158,10 @@ public void MountFailsWhenNoOnDiskVersion()
this.fileSystem.MoveFile(versionDatabasePath, tempDatabasePath);
versionDatabasePath.ShouldNotExistOnDisk(this.fileSystem);

this.MountShouldFail("Failed to upgrade repo disk layout");
this.MountShouldFail(
GVFSMissingDiskLayoutVersion,
MissingDiskLayoutVersionMessage,
additionalExpectedErrorMessage: GVFSLogMessage);
}
finally
{
Expand Down Expand Up @@ -359,7 +394,7 @@ public void ProjFS_CMDHangNoneActiveInstance()
this.Enlistment.MountGVFS();
}

private void MountShouldFail(int expectedExitCode, string expectedErrorMessage, string mountWorkingDirectory = null)
private void MountShouldFail(int expectedExitCode, string expectedErrorMessage, string mountWorkingDirectory = null, string additionalExpectedErrorMessage = null)
{
string enlistmentRoot = this.Enlistment.EnlistmentRoot;

Expand All @@ -379,6 +414,11 @@ private void MountShouldFail(int expectedExitCode, string expectedErrorMessage,
{
string combinedOutput = result.Output + "\n" + result.Errors;
combinedOutput.ShouldContain(expectedErrorMessage);

if (additionalExpectedErrorMessage != null)
{
combinedOutput.ShouldContain(additionalExpectedErrorMessage);
}
}
}

Expand Down
49 changes: 28 additions & 21 deletions GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public static void SaveDiskLayoutVersion(string dotGVFSRoot, string majorVersion
SavePersistedValue(dotGVFSRoot, DiskLayoutMinorVersionKey, minorVersion);
}

public static void DeletePersistedDiskLayoutMajorVersion(string dotGVFSRoot)
{
DeletePersistedValue(dotGVFSRoot, DiskLayoutMajorVersionKey);
}

public static void GetPersistedDiskLayoutVersion(string dotGVFSRoot, out string majorVersion, out string minorVersion)
{
majorVersion = GetPersistedValue(dotGVFSRoot, DiskLayoutMajorVersionKey);
Expand Down Expand Up @@ -314,31 +319,29 @@ private static byte CharToByte(char c)

private static string GetPersistedValue(string dotGVFSRoot, string key)
{
string metadataPath = Path.Combine(dotGVFSRoot, RepoMetadataName);
string json;
using (FileStream fs = new FileStream(metadataPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
using (StreamReader reader = new StreamReader(fs))
{
while (!reader.EndOfStream)
{
json = reader.ReadLine();
json.Substring(0, 2).ShouldEqual("A ");
Dictionary<string, string> repoMetadata = ReadPersistedValues(dotGVFSRoot);
string value;
repoMetadata.TryGetValue(key, out value);
return value;
}

KeyValuePair<string, string> kvp = GVFSJsonOptions.Deserialize<KeyValuePair<string, string>>(json.Substring(2));
if (kvp.Key == key)
{
return kvp.Value;
}
}
}
private static void SavePersistedValue(string dotGVFSRoot, string key, string value)
{
Dictionary<string, string> repoMetadata = ReadPersistedValues(dotGVFSRoot);
repoMetadata[key] = value;
WritePersistedValues(dotGVFSRoot, repoMetadata);
}

return null;
private static void DeletePersistedValue(string dotGVFSRoot, string key)
{
Dictionary<string, string> repoMetadata = ReadPersistedValues(dotGVFSRoot);
repoMetadata.Remove(key);
WritePersistedValues(dotGVFSRoot, repoMetadata);
}

private static void SavePersistedValue(string dotGVFSRoot, string key, string value)
private static Dictionary<string, string> ReadPersistedValues(string dotGVFSRoot)
{
string metadataPath = Path.Combine(dotGVFSRoot, RepoMetadataName);

Dictionary<string, string> repoMetadata = new Dictionary<string, string>();
string json;
using (FileStream fs = new FileStream(metadataPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
Expand All @@ -350,12 +353,16 @@ private static void SavePersistedValue(string dotGVFSRoot, string key, string va
json.Substring(0, 2).ShouldEqual("A ");

KeyValuePair<string, string> kvp = GVFSJsonOptions.Deserialize<KeyValuePair<string, string>>(json.Substring(2));
repoMetadata.Add(kvp.Key, kvp.Value);
repoMetadata[kvp.Key] = kvp.Value;
}
}

repoMetadata[key] = value;
return repoMetadata;
}

private static void WritePersistedValues(string dotGVFSRoot, Dictionary<string, string> repoMetadata)
{
string metadataPath = Path.Combine(dotGVFSRoot, RepoMetadataName);
string newRepoMetadataContents = string.Empty;

foreach (KeyValuePair<string, string> kvp in repoMetadata)
Expand Down
Loading
Loading