diff --git a/GVFS.sln b/GVFS.sln
index 2cee10493b..48bc59b3eb 100644
--- a/GVFS.sln
+++ b/GVFS.sln
@@ -131,6 +131,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GVFS.VirtualFileSystemHook.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Virtualization", "GVFS\GVFS.Virtualization\GVFS.Virtualization.csproj", "{F468B05A-95E5-46BC-8C67-B80A78527B7D}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PrjFSLib.Linux.Managed", "ProjFS.Linux\PrjFSLib.Linux.Managed\PrjFSLib.Linux.Managed.csproj", "{63E57526-9825-44C8-9ACB-DC3296ADEE97}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Platform.Linux", "GVFS\GVFS.Platform.Linux\GVFS.Platform.Linux.csproj", "{B4593252-9FB4-431D-A4E1-634D09DE09DE}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PrjFSLib.Mac.Managed", "ProjFS.Mac\PrjFSLib.Mac.Managed\PrjFSLib.Mac.Managed.csproj", "{FAC6EFC5-A890-4CB2-8C80-6358E358C637}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Platform.Mac", "GVFS\GVFS.Platform.Mac\GVFS.Platform.Mac.csproj", "{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}"
@@ -141,6 +145,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.UnitTests", "GVFS\GVFS
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.Platform.Windows", "GVFS\GVFS.Platform.Windows\GVFS.Platform.Windows.csproj", "{4CE404E7-D3FC-471C-993C-64615861EA63}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Linux", "GVFS\GVFS\GVFS.Linux.csproj", "{A1A1A31A-A76E-4F12-92A7-91755320C9A4}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Mac", "GVFS\GVFS\GVFS.Mac.csproj", "{28939122-7263-41E7-A7E2-CBFB01AD6A04}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.Windows", "GVFS\GVFS\GVFS.Windows.csproj", "{32220664-594C-4425-B9A0-88E0BE2F3D2A}"
@@ -160,6 +166,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.Mount.Windows", "GVFS\
{24D161E9-D1F0-4299-BBD3-5D940BEDD535} = {24D161E9-D1F0-4299-BBD3-5D940BEDD535}
EndProjectSection
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Mount.Linux", "GVFS\GVFS.Mount\GVFS.Mount.Linux.csproj", "{B9F62A3B-72F5-4703-A5FE-3879FF930410}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Mount.Mac", "GVFS\GVFS.Mount\GVFS.Mount.Mac.csproj", "{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.FunctionalTests.Windows", "GVFS\GVFS.FunctionalTests.Windows\GVFS.FunctionalTests.Windows.csproj", "{0F0A008E-AB12-40EC-A671-37A541B08C7F}"
@@ -176,6 +184,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.FunctionalTests", "GVF
ProjectSection(ProjectDependencies) = postProject
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5} = {17498502-AEFF-4E70-90CC-1D0B56A8ADF5}
{07F2A520-2AB7-46DD-97C0-75D8E988D55B} = {07F2A520-2AB7-46DD-97C0-75D8E988D55B}
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4} = {A1A1A31A-A76E-4F12-92A7-91755320C9A4}
{28939122-7263-41E7-A7E2-CBFB01AD6A04} = {28939122-7263-41E7-A7E2-CBFB01AD6A04}
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5} = {3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}
{32220664-594C-4425-B9A0-88E0BE2F3D2A} = {32220664-594C-4425-B9A0-88E0BE2F3D2A}
@@ -187,6 +196,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.FunctionalTests", "GVF
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.FunctionalTests.LockHolder", "GVFS\GVFS.FunctionalTests.LockHolder\GVFS.FunctionalTests.LockHolder.csproj", "{FA273F69-5762-43D8-AEA1-B4F08090D624}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Hooks.Linux", "GVFS\GVFS.Hooks\GVFS.Hooks.Linux.csproj", "{E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}"
+ ProjectSection(ProjectDependencies) = postProject
+ {A4984251-840E-4622-AD0C-66DFCE2B2574} = {A4984251-840E-4622-AD0C-66DFCE2B2574}
+ EndProjectSection
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GVFS.Hooks.Mac", "GVFS\GVFS.Hooks\GVFS.Hooks.Mac.csproj", "{4CC2A90D-D240-4382-B4BF-5E175515E492}"
ProjectSection(ProjectDependencies) = postProject
{A4984251-840E-4622-AD0C-66DFCE2B2574} = {A4984251-840E-4622-AD0C-66DFCE2B2574}
@@ -201,243 +215,389 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GVFS.PostIndexChangedHook.W
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug.Linux|x64 = Debug.Linux|x64
Debug.Mac|x64 = Debug.Mac|x64
Debug.Windows|x64 = Debug.Windows|x64
+ Release.Linux|x64 = Release.Linux|x64
Release.Mac|x64 = Release.Mac|x64
Release.Windows|x64 = Release.Windows|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1118B427-7063-422F-83B9-5023C8EC5A7A}.Debug.Linux|x64.ActiveCfg = Debug|x64
{1118B427-7063-422F-83B9-5023C8EC5A7A}.Debug.Mac|x64.ActiveCfg = Debug|x64
{1118B427-7063-422F-83B9-5023C8EC5A7A}.Debug.Windows|x64.ActiveCfg = Debug|x64
{1118B427-7063-422F-83B9-5023C8EC5A7A}.Debug.Windows|x64.Build.0 = Debug|x64
+ {1118B427-7063-422F-83B9-5023C8EC5A7A}.Release.Linux|x64.ActiveCfg = Release|x64
{1118B427-7063-422F-83B9-5023C8EC5A7A}.Release.Mac|x64.ActiveCfg = Release|x64
{1118B427-7063-422F-83B9-5023C8EC5A7A}.Release.Windows|x64.ActiveCfg = Release|x64
{1118B427-7063-422F-83B9-5023C8EC5A7A}.Release.Windows|x64.Build.0 = Release|x64
+ {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug.Linux|x64.Build.0 = Debug|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug.Mac|x64.ActiveCfg = Debug|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug.Mac|x64.Build.0 = Debug|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug.Windows|x64.ActiveCfg = Debug|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug.Windows|x64.Build.0 = Debug|x64
+ {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release.Linux|x64.ActiveCfg = Release|x64
+ {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release.Linux|x64.Build.0 = Release|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release.Mac|x64.ActiveCfg = Release|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release.Mac|x64.Build.0 = Release|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release.Windows|x64.ActiveCfg = Release|x64
{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release.Windows|x64.Build.0 = Release|x64
+ {07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Debug.Linux|x64.Build.0 = Debug|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Debug.Mac|x64.ActiveCfg = Debug|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Debug.Mac|x64.Build.0 = Debug|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Debug.Windows|x64.ActiveCfg = Debug|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Debug.Windows|x64.Build.0 = Debug|x64
+ {07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Release.Linux|x64.ActiveCfg = Release|x64
+ {07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Release.Linux|x64.Build.0 = Release|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Release.Mac|x64.ActiveCfg = Release|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Release.Mac|x64.Build.0 = Release|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Release.Windows|x64.ActiveCfg = Release|x64
{07F2A520-2AB7-46DD-97C0-75D8E988D55B}.Release.Windows|x64.Build.0 = Release|x64
+ {72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Debug.Linux|x64.Build.0 = Debug|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Debug.Mac|x64.ActiveCfg = Debug|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Debug.Mac|x64.Build.0 = Debug|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Debug.Windows|x64.ActiveCfg = Debug|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Debug.Windows|x64.Build.0 = Debug|x64
+ {72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Release.Linux|x64.ActiveCfg = Release|x64
+ {72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Release.Linux|x64.Build.0 = Release|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Release.Mac|x64.ActiveCfg = Release|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Release.Mac|x64.Build.0 = Release|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Release.Windows|x64.ActiveCfg = Release|x64
{72701BC3-5DA9-4C7A-BF10-9E98C9FC8EAC}.Release.Windows|x64.Build.0 = Release|x64
+ {8E0D0989-21F6-4DD8-946C-39F992523CC6}.Debug.Linux|x64.ActiveCfg = Debug|x64
{8E0D0989-21F6-4DD8-946C-39F992523CC6}.Debug.Mac|x64.ActiveCfg = Debug|x64
{8E0D0989-21F6-4DD8-946C-39F992523CC6}.Debug.Windows|x64.ActiveCfg = Debug|x64
{8E0D0989-21F6-4DD8-946C-39F992523CC6}.Debug.Windows|x64.Build.0 = Debug|x64
+ {8E0D0989-21F6-4DD8-946C-39F992523CC6}.Release.Linux|x64.ActiveCfg = Release|x64
{8E0D0989-21F6-4DD8-946C-39F992523CC6}.Release.Mac|x64.ActiveCfg = Release|x64
{8E0D0989-21F6-4DD8-946C-39F992523CC6}.Release.Windows|x64.ActiveCfg = Release|x64
{8E0D0989-21F6-4DD8-946C-39F992523CC6}.Release.Windows|x64.Build.0 = Release|x64
+ {3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Debug.Linux|x64.ActiveCfg = Debug|x64
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Debug.Mac|x64.ActiveCfg = Debug|x64
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Debug.Windows|x64.ActiveCfg = Debug|x64
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Debug.Windows|x64.Build.0 = Debug|x64
+ {3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Release.Linux|x64.ActiveCfg = Release|x64
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Release.Mac|x64.ActiveCfg = Release|x64
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Release.Windows|x64.ActiveCfg = Release|x64
{3771C555-B5C1-45E2-B8B7-2CEF1619CDC5}.Release.Windows|x64.Build.0 = Release|x64
+ {BDA91EE5-C684-4FC5-A90A-B7D677421917}.Debug.Linux|x64.ActiveCfg = Debug|x64
{BDA91EE5-C684-4FC5-A90A-B7D677421917}.Debug.Mac|x64.ActiveCfg = Debug|x64
{BDA91EE5-C684-4FC5-A90A-B7D677421917}.Debug.Windows|x64.ActiveCfg = Debug|x64
{BDA91EE5-C684-4FC5-A90A-B7D677421917}.Debug.Windows|x64.Build.0 = Debug|x64
+ {BDA91EE5-C684-4FC5-A90A-B7D677421917}.Release.Linux|x64.ActiveCfg = Release|x64
{BDA91EE5-C684-4FC5-A90A-B7D677421917}.Release.Mac|x64.ActiveCfg = Release|x64
{BDA91EE5-C684-4FC5-A90A-B7D677421917}.Release.Windows|x64.ActiveCfg = Release|x64
{BDA91EE5-C684-4FC5-A90A-B7D677421917}.Release.Windows|x64.Build.0 = Release|x64
+ {B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Debug.Linux|x64.ActiveCfg = Debug|x64
{B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Debug.Mac|x64.ActiveCfg = Debug|x64
{B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Debug.Windows|x64.ActiveCfg = Debug|x64
{B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Debug.Windows|x64.Build.0 = Debug|x64
+ {B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Release.Linux|x64.ActiveCfg = Release|x64
{B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Release.Mac|x64.ActiveCfg = Release|x64
{B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Release.Windows|x64.ActiveCfg = Release|x64
{B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B}.Release.Windows|x64.Build.0 = Release|x64
+ {03769A07-F216-456B-886B-E07CAF6C5E81}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {03769A07-F216-456B-886B-E07CAF6C5E81}.Debug.Linux|x64.Build.0 = Debug|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Debug.Mac|x64.ActiveCfg = Debug|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Debug.Mac|x64.Build.0 = Debug|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Debug.Windows|x64.ActiveCfg = Debug|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Debug.Windows|x64.Build.0 = Debug|x64
+ {03769A07-F216-456B-886B-E07CAF6C5E81}.Release.Linux|x64.ActiveCfg = Release|x64
+ {03769A07-F216-456B-886B-E07CAF6C5E81}.Release.Linux|x64.Build.0 = Release|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Release.Mac|x64.ActiveCfg = Release|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Release.Mac|x64.Build.0 = Release|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Release.Windows|x64.ActiveCfg = Release|x64
{03769A07-F216-456B-886B-E07CAF6C5E81}.Release.Windows|x64.Build.0 = Release|x64
+ {5A6656D5-81C7-472C-9DC8-32D071CB2258}.Debug.Linux|x64.ActiveCfg = Debug|x64
{5A6656D5-81C7-472C-9DC8-32D071CB2258}.Debug.Mac|x64.ActiveCfg = Debug|x64
{5A6656D5-81C7-472C-9DC8-32D071CB2258}.Debug.Windows|x64.ActiveCfg = Debug|x64
{5A6656D5-81C7-472C-9DC8-32D071CB2258}.Debug.Windows|x64.Build.0 = Debug|x64
+ {5A6656D5-81C7-472C-9DC8-32D071CB2258}.Release.Linux|x64.ActiveCfg = Release|x64
{5A6656D5-81C7-472C-9DC8-32D071CB2258}.Release.Mac|x64.ActiveCfg = Release|x64
{5A6656D5-81C7-472C-9DC8-32D071CB2258}.Release.Windows|x64.ActiveCfg = Release|x64
{5A6656D5-81C7-472C-9DC8-32D071CB2258}.Release.Windows|x64.Build.0 = Release|x64
+ {C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Debug.Linux|x64.ActiveCfg = Debug|x64
{C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Debug.Mac|x64.ActiveCfg = Debug|x64
{C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Debug.Windows|x64.ActiveCfg = Debug|x64
{C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Debug.Windows|x64.Build.0 = Debug|x64
+ {C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Release.Linux|x64.ActiveCfg = Release|x64
{C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Release.Mac|x64.ActiveCfg = Release|x64
{C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Release.Windows|x64.ActiveCfg = Release|x64
{C5D3CA26-562F-4CA4-A378-B93E97A730E3}.Release.Windows|x64.Build.0 = Release|x64
+ {93B403FD-DAFB-46C5-9636-B122792A548A}.Debug.Linux|x64.ActiveCfg = Debug|x64
{93B403FD-DAFB-46C5-9636-B122792A548A}.Debug.Mac|x64.ActiveCfg = Debug|x64
{93B403FD-DAFB-46C5-9636-B122792A548A}.Debug.Windows|x64.ActiveCfg = Debug|x64
{93B403FD-DAFB-46C5-9636-B122792A548A}.Debug.Windows|x64.Build.0 = Debug|x64
+ {93B403FD-DAFB-46C5-9636-B122792A548A}.Release.Linux|x64.ActiveCfg = Release|x64
{93B403FD-DAFB-46C5-9636-B122792A548A}.Release.Mac|x64.ActiveCfg = Release|x64
{93B403FD-DAFB-46C5-9636-B122792A548A}.Release.Windows|x64.ActiveCfg = Release|x64
{93B403FD-DAFB-46C5-9636-B122792A548A}.Release.Windows|x64.Build.0 = Release|x64
+ {A4984251-840E-4622-AD0C-66DFCE2B2574}.Debug.Linux|x64.ActiveCfg = Debug|x64
{A4984251-840E-4622-AD0C-66DFCE2B2574}.Debug.Mac|x64.ActiveCfg = Debug|x64
{A4984251-840E-4622-AD0C-66DFCE2B2574}.Debug.Windows|x64.ActiveCfg = Debug|x64
{A4984251-840E-4622-AD0C-66DFCE2B2574}.Debug.Windows|x64.Build.0 = Debug|x64
+ {A4984251-840E-4622-AD0C-66DFCE2B2574}.Release.Linux|x64.ActiveCfg = Release|x64
{A4984251-840E-4622-AD0C-66DFCE2B2574}.Release.Mac|x64.ActiveCfg = Release|x64
{A4984251-840E-4622-AD0C-66DFCE2B2574}.Release.Windows|x64.ActiveCfg = Release|x64
{A4984251-840E-4622-AD0C-66DFCE2B2574}.Release.Windows|x64.Build.0 = Release|x64
+ {798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Debug.Linux|x64.ActiveCfg = Debug|x64
{798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Debug.Mac|x64.ActiveCfg = Debug|x64
{798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Debug.Windows|x64.ActiveCfg = Debug|x64
{798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Debug.Windows|x64.Build.0 = Debug|x64
+ {798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Release.Linux|x64.ActiveCfg = Release|x64
{798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Release.Mac|x64.ActiveCfg = Release|x64
{798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Release.Windows|x64.ActiveCfg = Release|x64
{798DE293-6EDA-4DC4-9395-BE7A71C563E3}.Release.Windows|x64.Build.0 = Release|x64
+ {3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Debug.Linux|x64.ActiveCfg = Debug|x64
{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Debug.Mac|x64.ActiveCfg = Debug|x64
{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Debug.Windows|x64.ActiveCfg = Debug|x64
{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Debug.Windows|x64.Build.0 = Debug|x64
+ {3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Release.Linux|x64.ActiveCfg = Release|x64
{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Release.Mac|x64.ActiveCfg = Release|x64
{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Release.Windows|x64.ActiveCfg = Release|x64
{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Release.Windows|x64.Build.0 = Release|x64
+ {25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Debug.Linux|x64.ActiveCfg = Debug|x64
{25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Debug.Mac|x64.ActiveCfg = Debug|x64
{25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Debug.Mac|x64.Build.0 = Debug|x64
{25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Debug.Windows|x64.ActiveCfg = Debug|x64
+ {25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Release.Linux|x64.ActiveCfg = Release|x64
{25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Release.Mac|x64.ActiveCfg = Release|x64
{25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Release.Mac|x64.Build.0 = Release|x64
{25229A04-6554-49B1-A95A-3F3B76C5B0C8}.Release.Windows|x64.ActiveCfg = Release|x64
+ {2F63B22B-EE26-4266-BF17-28A9146483A1}.Debug.Linux|x64.ActiveCfg = Debug|x64
{2F63B22B-EE26-4266-BF17-28A9146483A1}.Debug.Mac|x64.ActiveCfg = Debug|x64
{2F63B22B-EE26-4266-BF17-28A9146483A1}.Debug.Windows|x64.ActiveCfg = Debug|x64
{2F63B22B-EE26-4266-BF17-28A9146483A1}.Debug.Windows|x64.Build.0 = Debug|x64
+ {2F63B22B-EE26-4266-BF17-28A9146483A1}.Release.Linux|x64.ActiveCfg = Release|x64
{2F63B22B-EE26-4266-BF17-28A9146483A1}.Release.Mac|x64.ActiveCfg = Release|x64
{2F63B22B-EE26-4266-BF17-28A9146483A1}.Release.Windows|x64.ActiveCfg = Release|x64
{2F63B22B-EE26-4266-BF17-28A9146483A1}.Release.Windows|x64.Build.0 = Release|x64
+ {2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Debug.Linux|x64.ActiveCfg = Debug|x64
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Debug.Mac|x64.ActiveCfg = Debug|x64
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Debug.Windows|x64.ActiveCfg = Debug|x64
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Debug.Windows|x64.Build.0 = Debug|x64
+ {2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Release.Linux|x64.ActiveCfg = Release|x64
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Release.Mac|x64.ActiveCfg = Release|x64
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Release.Windows|x64.ActiveCfg = Release|x64
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB}.Release.Windows|x64.Build.0 = Release|x64
+ {F468B05A-95E5-46BC-8C67-B80A78527B7D}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {F468B05A-95E5-46BC-8C67-B80A78527B7D}.Debug.Linux|x64.Build.0 = Debug|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Debug.Mac|x64.ActiveCfg = Debug|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Debug.Mac|x64.Build.0 = Debug|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Debug.Windows|x64.ActiveCfg = Debug|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Debug.Windows|x64.Build.0 = Debug|x64
+ {F468B05A-95E5-46BC-8C67-B80A78527B7D}.Release.Linux|x64.ActiveCfg = Release|x64
+ {F468B05A-95E5-46BC-8C67-B80A78527B7D}.Release.Linux|x64.Build.0 = Release|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Release.Mac|x64.ActiveCfg = Release|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Release.Mac|x64.Build.0 = Release|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Release.Windows|x64.ActiveCfg = Release|x64
{F468B05A-95E5-46BC-8C67-B80A78527B7D}.Release.Windows|x64.Build.0 = Release|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Debug.Linux|x64.Build.0 = Debug|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Debug.Mac|x64.ActiveCfg = Debug|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Debug.Mac|x64.Build.0 = Debug|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Debug.Windows|x64.ActiveCfg = Debug|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Debug.Windows|x64.Build.0 = Debug|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Release.Linux|x64.ActiveCfg = Release|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Release.Linux|x64.Build.0 = Release|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Release.Mac|x64.ActiveCfg = Release|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Release.Mac|x64.Build.0 = Release|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Release.Windows|x64.ActiveCfg = Release|x64
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97}.Release.Windows|x64.Build.0 = Release|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Debug.Linux|x64.Build.0 = Debug|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Debug.Mac|x64.ActiveCfg = Debug|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Debug.Mac|x64.Build.0 = Debug|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Debug.Windows|x64.ActiveCfg = Debug|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Debug.Windows|x64.Build.0 = Debug|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Release.Linux|x64.ActiveCfg = Release|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Release.Linux|x64.Build.0 = Release|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Release.Mac|x64.ActiveCfg = Release|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Release.Mac|x64.Build.0 = Release|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Release.Windows|x64.ActiveCfg = Release|x64
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE}.Release.Windows|x64.Build.0 = Release|x64
+ {FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Debug.Linux|x64.Build.0 = Debug|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Debug.Mac|x64.ActiveCfg = Debug|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Debug.Mac|x64.Build.0 = Debug|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Debug.Windows|x64.ActiveCfg = Debug|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Debug.Windows|x64.Build.0 = Debug|x64
+ {FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Release.Linux|x64.ActiveCfg = Release|x64
+ {FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Release.Linux|x64.Build.0 = Release|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Release.Mac|x64.ActiveCfg = Release|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Release.Mac|x64.Build.0 = Release|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Release.Windows|x64.ActiveCfg = Release|x64
{FAC6EFC5-A890-4CB2-8C80-6358E358C637}.Release.Windows|x64.Build.0 = Release|x64
+ {1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Debug.Linux|x64.Build.0 = Debug|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Debug.Mac|x64.ActiveCfg = Debug|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Debug.Mac|x64.Build.0 = Debug|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Debug.Windows|x64.ActiveCfg = Debug|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Debug.Windows|x64.Build.0 = Debug|x64
+ {1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Release.Linux|x64.ActiveCfg = Release|x64
+ {1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Release.Linux|x64.Build.0 = Release|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Release.Mac|x64.ActiveCfg = Release|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Release.Mac|x64.Build.0 = Release|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Release.Windows|x64.ActiveCfg = Release|x64
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A}.Release.Windows|x64.Build.0 = Release|x64
+ {15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Debug.Linux|x64.Build.0 = Debug|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Debug.Mac|x64.ActiveCfg = Debug|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Debug.Mac|x64.Build.0 = Debug|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Debug.Windows|x64.ActiveCfg = Debug|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Debug.Windows|x64.Build.0 = Debug|x64
+ {15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Release.Linux|x64.ActiveCfg = Release|x64
+ {15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Release.Linux|x64.Build.0 = Release|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Release.Mac|x64.ActiveCfg = Release|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Release.Mac|x64.Build.0 = Release|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Release.Windows|x64.ActiveCfg = Release|x64
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6}.Release.Windows|x64.Build.0 = Release|x64
+ {0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Debug.Linux|x64.Build.0 = Debug|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Debug.Mac|x64.ActiveCfg = Debug|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Debug.Mac|x64.Build.0 = Debug|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Debug.Windows|x64.ActiveCfg = Debug|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Debug.Windows|x64.Build.0 = Debug|x64
+ {0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Release.Linux|x64.ActiveCfg = Release|x64
+ {0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Release.Linux|x64.Build.0 = Release|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Release.Mac|x64.ActiveCfg = Release|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Release.Mac|x64.Build.0 = Release|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Release.Windows|x64.ActiveCfg = Release|x64
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53}.Release.Windows|x64.Build.0 = Release|x64
+ {4CE404E7-D3FC-471C-993C-64615861EA63}.Debug.Linux|x64.ActiveCfg = Debug|x64
{4CE404E7-D3FC-471C-993C-64615861EA63}.Debug.Mac|x64.ActiveCfg = Debug|x64
{4CE404E7-D3FC-471C-993C-64615861EA63}.Debug.Windows|x64.ActiveCfg = Debug|x64
{4CE404E7-D3FC-471C-993C-64615861EA63}.Debug.Windows|x64.Build.0 = Debug|x64
+ {4CE404E7-D3FC-471C-993C-64615861EA63}.Release.Linux|x64.ActiveCfg = Release|x64
{4CE404E7-D3FC-471C-993C-64615861EA63}.Release.Mac|x64.ActiveCfg = Release|x64
{4CE404E7-D3FC-471C-993C-64615861EA63}.Release.Windows|x64.ActiveCfg = Release|x64
{4CE404E7-D3FC-471C-993C-64615861EA63}.Release.Windows|x64.Build.0 = Release|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Debug.Linux|x64.Build.0 = Debug|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Debug.Mac|x64.ActiveCfg = Debug|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Debug.Windows|x64.ActiveCfg = Debug|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Debug.Windows|x64.Build.0 = Debug|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Release.Linux|x64.ActiveCfg = Release|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Release.Linux|x64.Build.0 = Release|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Release.Mac|x64.ActiveCfg = Release|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Release.Windows|x64.ActiveCfg = Release|x64
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4}.Release.Windows|x64.Build.0 = Release|x64
+ {28939122-7263-41E7-A7E2-CBFB01AD6A04}.Debug.Linux|x64.ActiveCfg = Debug|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Debug.Mac|x64.ActiveCfg = Debug|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Debug.Mac|x64.Build.0 = Debug|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Debug.Windows|x64.ActiveCfg = Debug|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Debug.Windows|x64.Build.0 = Debug|x64
+ {28939122-7263-41E7-A7E2-CBFB01AD6A04}.Release.Linux|x64.ActiveCfg = Release|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Release.Mac|x64.ActiveCfg = Release|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Release.Mac|x64.Build.0 = Release|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Release.Windows|x64.ActiveCfg = Release|x64
{28939122-7263-41E7-A7E2-CBFB01AD6A04}.Release.Windows|x64.Build.0 = Release|x64
+ {32220664-594C-4425-B9A0-88E0BE2F3D2A}.Debug.Linux|x64.ActiveCfg = Debug|x64
{32220664-594C-4425-B9A0-88E0BE2F3D2A}.Debug.Mac|x64.ActiveCfg = Debug|x64
{32220664-594C-4425-B9A0-88E0BE2F3D2A}.Debug.Windows|x64.ActiveCfg = Debug|x64
{32220664-594C-4425-B9A0-88E0BE2F3D2A}.Debug.Windows|x64.Build.0 = Debug|x64
+ {32220664-594C-4425-B9A0-88E0BE2F3D2A}.Release.Linux|x64.ActiveCfg = Release|x64
{32220664-594C-4425-B9A0-88E0BE2F3D2A}.Release.Mac|x64.ActiveCfg = Release|x64
{32220664-594C-4425-B9A0-88E0BE2F3D2A}.Release.Windows|x64.ActiveCfg = Release|x64
{32220664-594C-4425-B9A0-88E0BE2F3D2A}.Release.Windows|x64.Build.0 = Release|x64
+ {17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Debug.Linux|x64.ActiveCfg = Debug|x64
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Debug.Mac|x64.ActiveCfg = Debug|x64
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Debug.Windows|x64.ActiveCfg = Debug|x64
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Debug.Windows|x64.Build.0 = Debug|x64
+ {17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Release.Linux|x64.ActiveCfg = Release|x64
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Release.Mac|x64.ActiveCfg = Release|x64
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Release.Windows|x64.ActiveCfg = Release|x64
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5}.Release.Windows|x64.Build.0 = Release|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Debug.Linux|x64.Build.0 = Debug|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Debug.Mac|x64.ActiveCfg = Debug|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Debug.Windows|x64.ActiveCfg = Debug|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Debug.Windows|x64.Build.0 = Debug|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Release.Linux|x64.ActiveCfg = Release|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Release.Linux|x64.Build.0 = Release|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Release.Mac|x64.ActiveCfg = Release|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Release.Windows|x64.ActiveCfg = Release|x64
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410}.Release.Windows|x64.Build.0 = Release|x64
+ {35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Debug.Linux|x64.ActiveCfg = Debug|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Debug.Mac|x64.ActiveCfg = Debug|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Debug.Mac|x64.Build.0 = Debug|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Debug.Windows|x64.ActiveCfg = Debug|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Debug.Windows|x64.Build.0 = Debug|x64
+ {35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Release.Linux|x64.ActiveCfg = Release|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Release.Mac|x64.ActiveCfg = Release|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Release.Mac|x64.Build.0 = Release|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Release.Windows|x64.ActiveCfg = Release|x64
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0}.Release.Windows|x64.Build.0 = Release|x64
+ {0F0A008E-AB12-40EC-A671-37A541B08C7F}.Debug.Linux|x64.ActiveCfg = Debug|x64
{0F0A008E-AB12-40EC-A671-37A541B08C7F}.Debug.Mac|x64.ActiveCfg = Debug|x64
{0F0A008E-AB12-40EC-A671-37A541B08C7F}.Debug.Windows|x64.ActiveCfg = Debug|x64
{0F0A008E-AB12-40EC-A671-37A541B08C7F}.Debug.Windows|x64.Build.0 = Debug|x64
+ {0F0A008E-AB12-40EC-A671-37A541B08C7F}.Release.Linux|x64.ActiveCfg = Release|x64
{0F0A008E-AB12-40EC-A671-37A541B08C7F}.Release.Mac|x64.ActiveCfg = Release|x64
{0F0A008E-AB12-40EC-A671-37A541B08C7F}.Release.Windows|x64.ActiveCfg = Release|x64
{0F0A008E-AB12-40EC-A671-37A541B08C7F}.Release.Windows|x64.Build.0 = Release|x64
+ {BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Debug.Linux|x64.Build.0 = Debug|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Debug.Mac|x64.ActiveCfg = Debug|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Debug.Mac|x64.Build.0 = Debug|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Debug.Windows|x64.ActiveCfg = Debug|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Debug.Windows|x64.Build.0 = Debug|x64
+ {BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Release.Linux|x64.ActiveCfg = Release|x64
+ {BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Release.Linux|x64.Build.0 = Release|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Release.Mac|x64.ActiveCfg = Release|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Release.Mac|x64.Build.0 = Release|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Release.Windows|x64.ActiveCfg = Release|x64
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF}.Release.Windows|x64.Build.0 = Release|x64
+ {FA273F69-5762-43D8-AEA1-B4F08090D624}.Debug.Linux|x64.ActiveCfg = Debug|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Debug.Mac|x64.ActiveCfg = Debug|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Debug.Mac|x64.Build.0 = Debug|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Debug.Windows|x64.ActiveCfg = Debug|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Debug.Windows|x64.Build.0 = Debug|x64
+ {FA273F69-5762-43D8-AEA1-B4F08090D624}.Release.Linux|x64.ActiveCfg = Release|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Release.Mac|x64.ActiveCfg = Release|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Release.Mac|x64.Build.0 = Release|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Release.Windows|x64.ActiveCfg = Release|x64
{FA273F69-5762-43D8-AEA1-B4F08090D624}.Release.Windows|x64.Build.0 = Release|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Debug.Linux|x64.Build.0 = Debug|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Debug.Mac|x64.ActiveCfg = Debug|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Debug.Windows|x64.ActiveCfg = Debug|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Debug.Windows|x64.Build.0 = Debug|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Release.Linux|x64.ActiveCfg = Release|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Release.Linux|x64.Build.0 = Release|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Release.Mac|x64.ActiveCfg = Release|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Release.Windows|x64.ActiveCfg = Release|x64
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C}.Release.Windows|x64.Build.0 = Release|x64
+ {4CC2A90D-D240-4382-B4BF-5E175515E492}.Debug.Linux|x64.ActiveCfg = Debug|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Debug.Mac|x64.ActiveCfg = Debug|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Debug.Mac|x64.Build.0 = Debug|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Debug.Windows|x64.ActiveCfg = Debug|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Debug.Windows|x64.Build.0 = Debug|x64
+ {4CC2A90D-D240-4382-B4BF-5E175515E492}.Release.Linux|x64.ActiveCfg = Release|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Release.Mac|x64.ActiveCfg = Release|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Release.Mac|x64.Build.0 = Release|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Release.Windows|x64.ActiveCfg = Release|x64
{4CC2A90D-D240-4382-B4BF-5E175515E492}.Release.Windows|x64.Build.0 = Release|x64
+ {AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Linux|x64.ActiveCfg = Debug|x64
+ {AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Linux|x64.Build.0 = Debug|x64
{AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Mac|x64.ActiveCfg = Debug|x64
+ {AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Mac|x64.Build.0 = Debug|x64
{AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Windows|x64.ActiveCfg = Debug|x64
{AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Windows|x64.Build.0 = Debug|x64
+ {AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Linux|x64.ActiveCfg = Release|x64
+ {AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Linux|x64.Build.0 = Release|x64
{AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Mac|x64.ActiveCfg = Release|x64
+ {AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Mac|x64.Build.0 = Release|x64
{AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Windows|x64.ActiveCfg = Release|x64
{AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Windows|x64.Build.0 = Release|x64
- {AECEC217-2499-403D-B0BB-2962B9BE5970}.Debug.Mac|x64.Build.0 = Debug|x64
- {AECEC217-2499-403D-B0BB-2962B9BE5970}.Release.Mac|x64.Build.0 = Release|x64
+ {24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Debug.Linux|x64.ActiveCfg = Debug|x64
{24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Debug.Mac|x64.ActiveCfg = Debug|x64
{24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Debug.Windows|x64.ActiveCfg = Debug|x64
{24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Debug.Windows|x64.Build.0 = Debug|x64
+ {24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Release.Linux|x64.ActiveCfg = Release|x64
{24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Release.Mac|x64.ActiveCfg = Release|x64
{24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Release.Windows|x64.ActiveCfg = Release|x64
{24D161E9-D1F0-4299-BBD3-5D940BEDD535}.Release.Windows|x64.Build.0 = Release|x64
@@ -465,18 +625,23 @@ Global
{2F63B22B-EE26-4266-BF17-28A9146483A1} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{2D23AB54-541F-4ABC-8DCA-08C199E97ABB} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{F468B05A-95E5-46BC-8C67-B80A78527B7D} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
+ {63E57526-9825-44C8-9ACB-DC3296ADEE97} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
+ {B4593252-9FB4-431D-A4E1-634D09DE09DE} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{FAC6EFC5-A890-4CB2-8C80-6358E358C637} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{1DAC3DA6-3D21-4917-B9A8-D60C8712252A} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{15FAE44C-0D21-4312-9FD3-28F05A5AB7A6} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{0D434FA7-6D8C-481E-B0CE-779B59EAEF53} = {C41F10F9-1163-4CFA-A465-EA728F75E9FA}
{4CE404E7-D3FC-471C-993C-64615861EA63} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
+ {A1A1A31A-A76E-4F12-92A7-91755320C9A4} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{28939122-7263-41E7-A7E2-CBFB01AD6A04} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{32220664-594C-4425-B9A0-88E0BE2F3D2A} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{17498502-AEFF-4E70-90CC-1D0B56A8ADF5} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
+ {B9F62A3B-72F5-4703-A5FE-3879FF930410} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{35CA4DFB-1320-4055-B8F6-F12E0F252FF0} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{0F0A008E-AB12-40EC-A671-37A541B08C7F} = {C41F10F9-1163-4CFA-A465-EA728F75E9FA}
{BD7C5776-82F2-40C6-AF01-B3CC1E2D83AF} = {C41F10F9-1163-4CFA-A465-EA728F75E9FA}
{FA273F69-5762-43D8-AEA1-B4F08090D624} = {C41F10F9-1163-4CFA-A465-EA728F75E9FA}
+ {E6E1BC4C-1CF6-4FFA-84A0-74E02AE4096C} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{4CC2A90D-D240-4382-B4BF-5E175515E492} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{AECEC217-2499-403D-B0BB-2962B9BE5970} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
{24D161E9-D1F0-4299-BBD3-5D940BEDD535} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}
diff --git a/GVFS/FastFetch/FastFetch.csproj b/GVFS/FastFetch/FastFetch.csproj
index 90aac7cc48..bfd499b9a9 100644
--- a/GVFS/FastFetch/FastFetch.csproj
+++ b/GVFS/FastFetch/FastFetch.csproj
@@ -19,6 +19,11 @@
$(GVFSVersion)
+
+ true
+ true
+
+
@@ -35,14 +40,22 @@
-
+
+
+
+
+ PlatformLoader.Linux.cs
+
+
+
+
PlatformLoader.Mac.cs
-
+
diff --git a/GVFS/GVFS.Hooks/GVFS.Hooks.Linux.csproj b/GVFS/GVFS.Hooks/GVFS.Hooks.Linux.csproj
new file mode 100644
index 0000000000..a084420005
--- /dev/null
+++ b/GVFS/GVFS.Hooks/GVFS.Hooks.Linux.csproj
@@ -0,0 +1,113 @@
+
+
+
+ Exe
+ GVFS.Hooks
+ netcoreapp2.1
+ x64
+ linux-x64
+ false
+
+
+
+ GVFS.Hooks
+ GVFS.Hooks
+
+
+ $(GVFSVersion)
+
+
+ $(GVFSVersion)
+
+
+
+
+
+
+
+
+
+ Common\ConsoleHelper.cs
+
+
+ Common\Git\GitConfigHelper.cs
+
+
+ Common\Git\GitConfigSetting.cs
+
+
+ Common\Git\GitVersion.cs
+
+
+ Common\GVFSConstants.cs
+
+
+ Common\GVFSEnlistment.Shared.cs
+
+
+ Common\GVFSLock.Shared.cs
+
+
+ Common\NamedPipes\BrokenPipeException.cs
+
+
+ Common\NamedPipes\LockNamedPipeMessages.cs
+
+
+ Common\NamedPipes\NamedPipeClient.cs
+
+
+ Common\NamedPipes\NamedPipeStreamReader.cs
+
+
+ Common\NamedPipes\NamedPipeStreamWriter.cs
+
+
+ Common\NativeMethods.Shared.cs
+
+
+ Common\Paths.Shared.cs
+
+
+ Common\ProcessHelper.cs
+
+
+ Common\ProcessResult.cs
+
+
+
+ Common\Tracing\EventLevel.cs
+
+
+ Common\Tracing\EventMetadata.cs
+
+
+ Common\Tracing\EventOpcode.cs
+
+
+ Common\Tracing\ITracer.cs
+
+
+ Common\Tracing\Keywords.cs
+
+
+ "LinuxPlatform.Shared.cs"
+
+
+ POSIX\POSIXFileSystem.Shared.cs
+
+
+ POSIX\POSIXPlatform.Shared.cs
+
+
+
+
+
+ all
+
+
+
diff --git a/GVFS/GVFS.Hooks/GVFS.Hooks.Mac.csproj b/GVFS/GVFS.Hooks/GVFS.Hooks.Mac.csproj
index 9cf4b76519..44da545fa5 100644
--- a/GVFS/GVFS.Hooks/GVFS.Hooks.Mac.csproj
+++ b/GVFS/GVFS.Hooks/GVFS.Hooks.Mac.csproj
@@ -20,6 +20,7 @@
$(GVFSVersion)
+
diff --git a/GVFS/GVFS.Hooks/HooksPlatform/GVFSHooksPlatform.Linux.cs b/GVFS/GVFS.Hooks/HooksPlatform/GVFSHooksPlatform.Linux.cs
new file mode 100644
index 0000000000..213632e84e
--- /dev/null
+++ b/GVFS/GVFS.Hooks/HooksPlatform/GVFSHooksPlatform.Linux.cs
@@ -0,0 +1,32 @@
+using GVFS.Platform.Linux;
+
+namespace GVFS.Hooks.HooksPlatform
+{
+ public static partial class GVFSHooksPlatform
+ {
+ public static string GetUpgradeHighestAvailableVersionDirectory()
+ {
+ return LinuxPlatform.GetUpgradeHighestAvailableVersionDirectoryImplementation();
+ }
+
+ public static bool TryGetGVFSEnlistmentRoot(string directory, out string enlistmentRoot, out string errorMessage)
+ {
+ return LinuxPlatform.TryGetGVFSEnlistmentRootImplementation(directory, out enlistmentRoot, out errorMessage);
+ }
+
+ public static string GetNamedPipeName(string enlistmentRoot)
+ {
+ return LinuxPlatform.GetNamedPipeNameImplementation(enlistmentRoot);
+ }
+
+ public static string GetGitGuiBlockedMessage()
+ {
+ return "git gui is not supported in VFS for Git repos on Linux";
+ }
+
+ public static string GetUpgradeReminderNotification()
+ {
+ return LinuxPlatform.GetUpgradeReminderNotificationImplementation();
+ }
+ }
+}
diff --git a/GVFS/GVFS.Mount/GVFS.Mount.Linux.csproj b/GVFS/GVFS.Mount/GVFS.Mount.Linux.csproj
new file mode 100644
index 0000000000..8cbb003712
--- /dev/null
+++ b/GVFS/GVFS.Mount/GVFS.Mount.Linux.csproj
@@ -0,0 +1,35 @@
+
+
+
+ Exe
+ GVFS.Mount
+ netcoreapp2.1
+ x64
+ linux-x64
+
+
+
+ $(GVFSVersion)
+
+
+ $(GVFSVersion)
+
+
+
+
+ PlatformLoader.Linux.cs
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
diff --git a/GVFS/GVFS.Platform.Linux/GVFS.Platform.Linux.csproj b/GVFS/GVFS.Platform.Linux/GVFS.Platform.Linux.csproj
new file mode 100644
index 0000000000..8f7bfb7758
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/GVFS.Platform.Linux.csproj
@@ -0,0 +1,34 @@
+
+
+
+ netcoreapp2.1;netstandard2.0
+ x64
+ true
+ true
+
+
+
+ $(GVFSVersion)
+
+
+ $(GVFSVersion)
+
+
+
+ $(GVFSVersion)
+
+
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
diff --git a/GVFS/GVFS.Platform.Linux/LinuxDiskLayoutUpgradeData.cs b/GVFS/GVFS.Platform.Linux/LinuxDiskLayoutUpgradeData.cs
new file mode 100644
index 0000000000..bca7601edc
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxDiskLayoutUpgradeData.cs
@@ -0,0 +1,27 @@
+using GVFS.Common;
+using GVFS.DiskLayoutUpgrades;
+
+namespace GVFS.Platform.Linux
+{
+ public class LinuxDiskLayoutUpgradeData : IDiskLayoutUpgradeData
+ {
+ public DiskLayoutUpgrade[] Upgrades
+ {
+ get
+ {
+ return new DiskLayoutUpgrade[0];
+ }
+ }
+
+ public DiskLayoutVersion Version => new DiskLayoutVersion(
+ currentMajorVersion: 19,
+ currentMinorVersion: 0,
+ minimumSupportedMajorVersion: 19);
+
+ public bool TryParseLegacyDiskLayoutVersion(string dotGVFSPath, out int majorVersion)
+ {
+ majorVersion = 0;
+ return false;
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/LinuxFileBasedLock.cs b/GVFS/GVFS.Platform.Linux/LinuxFileBasedLock.cs
new file mode 100644
index 0000000000..03a1bfcadb
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxFileBasedLock.cs
@@ -0,0 +1,133 @@
+using GVFS.Common;
+using GVFS.Common.FileSystem;
+using GVFS.Common.Tracing;
+using System;
+using System.IO;
+using System.Runtime.InteropServices;
+
+namespace GVFS.Platform.Linux
+{
+ public class LinuxFileBasedLock : FileBasedLock
+ {
+ private int lockFileDescriptor;
+
+ public LinuxFileBasedLock(
+ PhysicalFileSystem fileSystem,
+ ITracer tracer,
+ string lockPath)
+ : base(fileSystem, tracer, lockPath)
+ {
+ this.lockFileDescriptor = NativeMethods.InvalidFileDescriptor;
+ }
+
+ public override bool TryAcquireLock()
+ {
+ if (this.lockFileDescriptor == NativeMethods.InvalidFileDescriptor)
+ {
+ this.FileSystem.CreateDirectory(Path.GetDirectoryName(this.LockPath));
+
+ this.lockFileDescriptor = NativeMethods.Open(
+ this.LockPath,
+ NativeMethods.OpenCreate | NativeMethods.OpenWriteOnly,
+ NativeMethods.FileMode644);
+
+ if (this.lockFileDescriptor == NativeMethods.InvalidFileDescriptor)
+ {
+ int errno = Marshal.GetLastWin32Error();
+ EventMetadata metadata = this.CreateEventMetadata(errno);
+ this.Tracer.RelatedWarning(
+ metadata,
+ $"{nameof(LinuxFileBasedLock)}.{nameof(this.TryAcquireLock)}: Failed to open lock file");
+
+ return false;
+ }
+ }
+
+ if (NativeMethods.FLock(this.lockFileDescriptor, NativeMethods.LockEx | NativeMethods.LockNb) != 0)
+ {
+ int errno = Marshal.GetLastWin32Error();
+ if (errno != NativeMethods.EIntr && errno != NativeMethods.EWouldBlock)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(errno);
+ this.Tracer.RelatedWarning(
+ metadata,
+ $"{nameof(LinuxFileBasedLock)}.{nameof(this.TryAcquireLock)}: Unexpected error when locking file");
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ public override void Dispose()
+ {
+ if (this.lockFileDescriptor != NativeMethods.InvalidFileDescriptor)
+ {
+ if (NativeMethods.Close(this.lockFileDescriptor) != 0)
+ {
+ // Failures of close() are logged for diagnostic purposes only.
+ // It's possible that errors from a previous operation (e.g. write(2))
+ // are only reported in close(). We should *not* retry the close() if
+ // it fails since it may cause a re-used file descriptor from another
+ // thread to be closed.
+
+ int errno = Marshal.GetLastWin32Error();
+ EventMetadata metadata = this.CreateEventMetadata(errno);
+ this.Tracer.RelatedWarning(
+ metadata,
+ $"{nameof(LinuxFileBasedLock)}.{nameof(this.Dispose)}: Error when closing lock fd");
+ }
+
+ this.lockFileDescriptor = NativeMethods.InvalidFileDescriptor;
+ }
+ }
+
+ private EventMetadata CreateEventMetadata(int errno = 0)
+ {
+ EventMetadata metadata = new EventMetadata();
+ metadata.Add("Area", nameof(LinuxFileBasedLock));
+ metadata.Add(nameof(this.LockPath), this.LockPath);
+ if (errno != 0)
+ {
+ metadata.Add(nameof(errno), errno);
+ }
+
+ return metadata;
+ }
+
+ private static class NativeMethods
+ {
+ // #define O_WRONLY 0x0001 /* open for writing only */
+ public const int OpenWriteOnly = 0x0001;
+
+ // #define O_CREAT 0x0040 /* create if nonexistant */
+ public const int OpenCreate = 0x0040;
+
+ // #define EINTR 4 /* Interrupted system call */
+ public const int EIntr = 4;
+
+ // #define EAGAIN 11 /* Resource temporarily unavailable */
+ // #define EWOULDBLOCK EAGAIN /* Operation would block */
+ public const int EWouldBlock = 11;
+
+ public const int LockSh = 1; // #define LOCK_SH 1 /* shared lock */
+ public const int LockEx = 2; // #define LOCK_EX 2 /* exclusive lock */
+ public const int LockNb = 4; // #define LOCK_NB 4 /* don't block when locking */
+ public const int LockUn = 8; // #define LOCK_UN 8 /* unlock */
+
+ public const int InvalidFileDescriptor = -1;
+
+ public static readonly uint FileMode644 = Convert.ToUInt32("644", 8);
+
+ [DllImport("libc", EntryPoint = "open", SetLastError = true)]
+ public static extern int Open(string pathname, int flags, uint mode);
+
+ [DllImport("libc", EntryPoint = "close", SetLastError = true)]
+ public static extern int Close(int fd);
+
+ [DllImport("libc", EntryPoint = "flock", SetLastError = true)]
+ public static extern int FLock(int fd, int operation);
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/LinuxFileSystem.cs b/GVFS/GVFS.Platform.Linux/LinuxFileSystem.cs
new file mode 100644
index 0000000000..b2919bff99
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxFileSystem.cs
@@ -0,0 +1,162 @@
+using GVFS.Common;
+using GVFS.Platform.POSIX;
+using System;
+using System.Runtime.InteropServices;
+
+namespace GVFS.Platform.Linux
+{
+ public class LinuxFileSystem : POSIXFileSystem
+ {
+ public override void ChangeMode(string path, ushort mode)
+ {
+ Chmod(path, mode);
+ }
+
+ public override bool HydrateFile(string fileName, byte[] buffer)
+ {
+ return NativeFileReader.TryReadFirstByteOfFile(fileName, buffer);
+ }
+
+ public override bool IsExecutable(string fileName)
+ {
+ NativeStat.StatBuffer statBuffer = this.StatFile(fileName);
+ return NativeStat.IsExecutable(statBuffer.Mode);
+ }
+
+ public override bool IsSocket(string fileName)
+ {
+ NativeStat.StatBuffer statBuffer = this.StatFile(fileName);
+ return NativeStat.IsSock(statBuffer.Mode);
+ }
+
+ [DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
+ private static extern int Chmod(string pathname, uint mode);
+
+ private NativeStat.StatBuffer StatFile(string fileName)
+ {
+ if (NativeStat.Stat(fileName, out NativeStat.StatBuffer statBuffer) != 0)
+ {
+ NativeMethods.ThrowLastWin32Exception($"Failed to stat {fileName}");
+ }
+
+ return statBuffer;
+ }
+
+ private static class NativeStat
+ {
+ // #define S_IFMT 0170000 /* [XSI] type of file mask */
+ private static readonly uint IFMT = Convert.ToUInt32("170000", 8);
+
+ // #define S_IFSOCK 0140000 /* [XSI] socket */
+ private static readonly uint IFSOCK = Convert.ToUInt32("0140000", 8);
+
+ // #define S_IXUSR 0000100 /* [XSI] X for owner */
+ private static readonly uint IXUSR = Convert.ToUInt32("100", 8);
+
+ // #define S_IXGRP 0000010 /* [XSI] X for group */
+ private static readonly uint IXGRP = Convert.ToUInt32("10", 8);
+
+ // #define S_IXOTH 0000001 /* [XSI] X for other */
+ private static readonly uint IXOTH = Convert.ToUInt32("1", 8);
+
+ // #define _STAT_VER 1
+ private static readonly int STAT_VER = 1;
+
+ public static bool IsSock(uint mode)
+ {
+ // #define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
+ return (mode & IFMT) == IFSOCK;
+ }
+
+ public static bool IsExecutable(uint mode)
+ {
+ return (mode & (IXUSR | IXGRP | IXOTH)) != 0;
+ }
+
+ public static int Stat(string path, [Out] out StatBuffer buf)
+ {
+ return __XStat64(STAT_VER, path, out buf);
+ }
+
+ // TODO(Linux): assumes recent GNU libc or ABI-compatible libc
+ [DllImport("libc", EntryPoint = "__xstat64", SetLastError = true)]
+ public static extern int __XStat64(int vers, string path, [Out] out StatBuffer buf);
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct TimeSpec
+ {
+ public long Sec;
+ public long Nsec;
+ }
+
+ // TODO(Linux): assumes stat64 field layout of x86-64 architecture
+ [StructLayout(LayoutKind.Sequential)]
+ public struct StatBuffer
+ {
+ public ulong Dev; /* ID of device containing file */
+ public ulong Ino; /* File serial number */
+ public ulong NLink; /* Number of hard links */
+ public uint Mode; /* Mode of file (see below) */
+ public uint UID; /* User ID of the file */
+ public uint GID; /* Group ID of the file */
+ public uint Padding; /* RESERVED: DO NOT USE! */
+ public ulong RDev; /* Device ID if special file */
+ public long Size; /* file size, in bytes */
+ public long BlkSize; /* optimal blocksize for I/O */
+ public long Blocks; /* blocks allocated for file */
+ public TimeSpec ATimespec; /* time of last access */
+ public TimeSpec MTimespec; /* time of last data modification */
+ public TimeSpec CTimespec; /* time of last status change */
+
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
+ public long[] Reserved; /* RESERVED: DO NOT USE! */
+ }
+ }
+
+ private static class NativeFileReader
+ {
+ private const int ReadOnly = 0x0000;
+
+ internal static bool TryReadFirstByteOfFile(string fileName, byte[] buffer)
+ {
+ int fileDescriptor = -1;
+ bool readStatus = false;
+ try
+ {
+ fileDescriptor = Open(fileName, ReadOnly);
+ if (fileDescriptor != -1)
+ {
+ readStatus = TryReadOneByte(fileDescriptor, buffer);
+ }
+ }
+ finally
+ {
+ Close(fileDescriptor);
+ }
+
+ return readStatus;
+ }
+
+ [DllImport("libc", EntryPoint = "open", SetLastError = true)]
+ private static extern int Open(string path, int flag);
+
+ [DllImport("libc", EntryPoint = "close", SetLastError = true)]
+ private static extern int Close(int fd);
+
+ [DllImport("libc", EntryPoint = "read", SetLastError = true)]
+ private static extern long Read(int fd, [Out] byte[] buf, ulong count);
+
+ private static bool TryReadOneByte(int fileDescriptor, byte[] buffer)
+ {
+ long numBytes = Read(fileDescriptor, buffer, 1);
+
+ if (numBytes == -1)
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/LinuxFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Linux/LinuxFileSystemVirtualizer.cs
new file mode 100644
index 0000000000..716620aceb
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxFileSystemVirtualizer.cs
@@ -0,0 +1,763 @@
+using GVFS.Common;
+using GVFS.Common.Git;
+using GVFS.Common.Tracing;
+using GVFS.Virtualization.BlobSize;
+using GVFS.Virtualization.FileSystem;
+using GVFS.Virtualization.Projection;
+using PrjFSLib.Linux;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+namespace GVFS.Platform.Linux
+{
+ public class LinuxFileSystemVirtualizer : FileSystemVirtualizer
+ {
+ public static readonly byte[] PlaceholderVersionId = ToVersionIdByteArray(new byte[] { PlaceholderVersion });
+
+ private const int SymLinkTargetBufferSize = 4096;
+
+ private const string ClassName = nameof(LinuxFileSystemVirtualizer);
+
+ private VirtualizationInstance virtualizationInstance;
+
+ public LinuxFileSystemVirtualizer(GVFSContext context, GVFSGitObjects gitObjects)
+ : this(context, gitObjects, virtualizationInstance: null)
+ {
+ }
+
+ public LinuxFileSystemVirtualizer(
+ GVFSContext context,
+ GVFSGitObjects gitObjects,
+ VirtualizationInstance virtualizationInstance)
+ : base(context, gitObjects)
+ {
+ this.virtualizationInstance = virtualizationInstance ?? new VirtualizationInstance();
+ }
+
+ protected override string EtwArea => ClassName;
+
+ public static FSResult ResultToFSResult(Result result)
+ {
+ switch (result)
+ {
+ case Result.Invalid:
+ return FSResult.IOError;
+
+ case Result.Success:
+ return FSResult.Ok;
+
+ case Result.EFileNotFound:
+ case Result.EPathNotFound:
+ return FSResult.FileOrPathNotFound;
+
+ case Result.EDirectoryNotEmpty:
+ return FSResult.DirectoryNotEmpty;
+
+ case Result.EVirtualizationInvalidOperation:
+ return FSResult.VirtualizationInvalidOperation;
+
+ default:
+ return FSResult.IOError;
+ }
+ }
+
+ public override FileSystemResult ClearNegativePathCache(out uint totalEntryCount)
+ {
+ totalEntryCount = 0;
+ return new FileSystemResult(FSResult.Ok, rawResult: unchecked((int)Result.Success));
+ }
+
+ public override FileSystemResult DeleteFile(string relativePath, UpdatePlaceholderType updateFlags, out UpdateFailureReason failureReason)
+ {
+ UpdateFailureCause failureCause;
+ Result result = this.virtualizationInstance.DeleteFile(relativePath, (UpdateType)updateFlags, out failureCause);
+ failureReason = (UpdateFailureReason)failureCause;
+ return new FileSystemResult(ResultToFSResult(result), unchecked((int)result));
+ }
+
+ public override void Stop()
+ {
+ this.virtualizationInstance.StopVirtualizationInstance();
+ this.Context.Tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.Stop)}_StopRequested", metadata: null);
+ }
+
+ ///
+ /// Writes a placeholder file.
+ ///
+ /// Placeholder's path relative to the root of the repo
+ /// Length of the file (ignored on this platform)
+ /// The SHA of the placeholder's contents, stored as the content ID in the placeholder
+ public override FileSystemResult WritePlaceholderFile(
+ string relativePath,
+ long endOfFile,
+ string sha)
+ {
+ // TODO(#223): Add functional tests that validate file mode is set correctly
+ GitIndexProjection.FileType fileType;
+ ushort fileMode;
+ this.FileSystemCallbacks.GitIndexProjection.GetFileTypeAndMode(relativePath, out fileType, out fileMode);
+
+ if (fileType == GitIndexProjection.FileType.Regular)
+ {
+ Result result = this.virtualizationInstance.WritePlaceholderFile(
+ relativePath,
+ PlaceholderVersionId,
+ ToVersionIdByteArray(FileSystemVirtualizer.ConvertShaToContentId(sha)),
+ (ulong)endOfFile,
+ fileMode);
+
+ return new FileSystemResult(ResultToFSResult(result), unchecked((int)result));
+ }
+ else if (fileType == GitIndexProjection.FileType.SymLink)
+ {
+ string symLinkTarget;
+ if (this.TryGetSymLinkTarget(sha, out symLinkTarget))
+ {
+ Result result = this.virtualizationInstance.WriteSymLink(relativePath, symLinkTarget);
+
+ this.FileSystemCallbacks.OnFileSymLinkCreated(relativePath);
+
+ return new FileSystemResult(ResultToFSResult(result), unchecked((int)result));
+ }
+
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(sha), sha);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.WritePlaceholderFile)}: Failed to read contents of symlink object");
+ return new FileSystemResult(FSResult.IOError, 0);
+ }
+ else
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(fileType), fileType);
+ metadata.Add(nameof(fileMode), fileMode);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.WritePlaceholderFile)}: Unsupported fileType");
+ return new FileSystemResult(FSResult.IOError, 0);
+ }
+ }
+
+ public override FileSystemResult WritePlaceholderDirectory(string relativePath)
+ {
+ Result result = this.virtualizationInstance.WritePlaceholderDirectory(relativePath);
+ return new FileSystemResult(ResultToFSResult(result), unchecked((int)result));
+ }
+
+ public override FileSystemResult UpdatePlaceholderIfNeeded(
+ string relativePath,
+ DateTime creationTime,
+ DateTime lastAccessTime,
+ DateTime lastWriteTime,
+ DateTime changeTime,
+ FileAttributes fileAttributes,
+ long endOfFile,
+ string shaContentId,
+ UpdatePlaceholderType updateFlags,
+ out UpdateFailureReason failureReason)
+ {
+ UpdateFailureCause failureCause = UpdateFailureCause.NoFailure;
+
+ // TODO(#223): Add functional tests that include:
+ // - Mode + content changes between commits
+ // - Mode only changes (without any change to content, see issue #223)
+ GitIndexProjection.FileType fileType;
+ ushort fileMode;
+ this.FileSystemCallbacks.GitIndexProjection.GetFileTypeAndMode(relativePath, out fileType, out fileMode);
+
+ if (fileType == GitIndexProjection.FileType.Regular)
+ {
+ Result result = this.virtualizationInstance.UpdatePlaceholderIfNeeded(
+ relativePath,
+ PlaceholderVersionId,
+ ToVersionIdByteArray(ConvertShaToContentId(shaContentId)),
+ (ulong)endOfFile,
+ fileMode,
+ (UpdateType)updateFlags,
+ out failureCause);
+
+ failureReason = (UpdateFailureReason)failureCause;
+ return new FileSystemResult(ResultToFSResult(result), unchecked((int)result));
+ }
+ else if (fileType == GitIndexProjection.FileType.SymLink)
+ {
+ string symLinkTarget;
+ if (this.TryGetSymLinkTarget(shaContentId, out symLinkTarget))
+ {
+ Result result = this.virtualizationInstance.ReplacePlaceholderFileWithSymLink(
+ relativePath,
+ symLinkTarget,
+ (UpdateType)updateFlags,
+ out failureCause);
+
+ this.FileSystemCallbacks.OnFileSymLinkCreated(relativePath);
+
+ failureReason = (UpdateFailureReason)failureCause;
+ return new FileSystemResult(ResultToFSResult(result), unchecked((int)result));
+ }
+
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(shaContentId), shaContentId);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.UpdatePlaceholderIfNeeded)}: Failed to read contents of symlink object");
+ failureReason = UpdateFailureReason.NoFailure;
+ return new FileSystemResult(FSResult.IOError, 0);
+ }
+ else
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(fileType), fileType);
+ metadata.Add(nameof(fileMode), fileMode);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.UpdatePlaceholderIfNeeded)}: Unsupported fileType");
+ failureReason = UpdateFailureReason.NoFailure;
+ return new FileSystemResult(FSResult.IOError, 0);
+ }
+ }
+
+ public override FileSystemResult DehydrateFolder(string relativePath)
+ {
+ FileSystemResult result = new FileSystemResult(FSResult.Ok, 0);
+ GitIndexProjection.PathSparseState sparseState = this.FileSystemCallbacks.GitIndexProjection.GetFolderPathSparseState(relativePath);
+
+ if (sparseState == GitIndexProjection.PathSparseState.Included)
+ {
+ // When the folder is included we need to create the placeholder to make sure it is on disk for enumeration
+ result = this.WritePlaceholderDirectory(relativePath);
+ if (result.Result == FSResult.Ok)
+ {
+ this.FileSystemCallbacks.OnPlaceholderFolderCreated(relativePath, string.Empty);
+ }
+ else if (result.Result == FSResult.FileOrPathNotFound)
+ {
+ // This will happen when the parent folder is also in the dehydrate list and is no longer on disk.
+ result = new FileSystemResult(FSResult.Ok, 0);
+ }
+ else
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(result.Result), result.Result);
+ metadata.Add(nameof(result.RawResult), result.RawResult);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.DehydrateFolder)}: Write placeholder failed");
+ }
+ }
+
+ return result;
+ }
+
+ public override bool TryStart(out string error)
+ {
+ error = string.Empty;
+
+ // Callbacks
+ this.virtualizationInstance.OnEnumerateDirectory = this.OnEnumerateDirectory;
+ this.virtualizationInstance.OnGetFileStream = this.OnGetFileStream;
+ this.virtualizationInstance.OnLogError = this.OnLogError;
+ this.virtualizationInstance.OnLogWarning = this.OnLogWarning;
+ this.virtualizationInstance.OnLogInfo = this.OnLogInfo;
+ this.virtualizationInstance.OnFileModified = this.OnFileModified;
+ this.virtualizationInstance.OnPreDelete = this.OnPreDelete;
+ this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated;
+ this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed;
+ this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated;
+ this.virtualizationInstance.OnFilePreConvertToFull = this.NotifyFilePreConvertToFull;
+
+ uint threadCount = (uint)Environment.ProcessorCount * 2;
+
+ Result result = this.virtualizationInstance.StartVirtualizationInstance(
+ this.Context.Enlistment.WorkingDirectoryBackingRoot,
+ this.Context.Enlistment.WorkingDirectoryRoot,
+ threadCount);
+
+ // TODO(Linux): note that most start errors are not reported
+ // because they can only be retrieved from projfs_stop() at present
+ if (result != Result.Success)
+ {
+ this.Context.Tracer.RelatedError($"{nameof(this.virtualizationInstance.StartVirtualizationInstance)} failed: " + result.ToString("X") + "(" + result.ToString("G") + ")");
+ error = "Failed to start virtualization instance (" + result.ToString() + ")";
+ return false;
+ }
+
+ // TODO(Linux): wait for device ID from stat() to change
+
+ this.Context.Tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.TryStart)}_StartedVirtualization", metadata: null);
+ return true;
+ }
+
+ private static string ConvertDotPath(string path)
+ {
+ if (path == ".")
+ {
+ path = string.Empty;
+ }
+
+ return path;
+ }
+
+ private static byte[] ToVersionIdByteArray(byte[] version)
+ {
+ byte[] bytes = new byte[VirtualizationInstance.PlaceholderIdLength];
+ Buffer.BlockCopy(version, 0, bytes, 0, version.Length);
+ return bytes;
+ }
+
+ ///
+ /// Gets the target of the symbolic link.
+ ///
+ /// SHA of the loose object containing the target path of the symbolic link
+ /// Target path of the symbolic link
+ private bool TryGetSymLinkTarget(string sha, out string symLinkTarget)
+ {
+ symLinkTarget = null;
+
+ string symLinkBlobContents = null;
+ try
+ {
+ if (!this.GitObjects.TryCopyBlobContentStream(
+ sha,
+ CancellationToken.None,
+ GVFSGitObjects.RequestSource.SymLinkCreation,
+ (stream, blobLength) =>
+ {
+ byte[] buffer = new byte[SymLinkTargetBufferSize];
+ uint bufferIndex = 0;
+
+ // TODO(#1361): Find a better solution than reading from the stream one byte at at time
+ int nextByte = stream.ReadByte();
+ while (nextByte != -1)
+ {
+ while (bufferIndex < buffer.Length && nextByte != -1)
+ {
+ buffer[bufferIndex] = (byte)nextByte;
+ nextByte = stream.ReadByte();
+ ++bufferIndex;
+ }
+
+ if (bufferIndex < buffer.Length)
+ {
+ buffer[bufferIndex] = 0;
+ symLinkBlobContents = Encoding.UTF8.GetString(buffer);
+ }
+ else
+ {
+ buffer[bufferIndex - 1] = 0;
+
+ EventMetadata metadata = this.CreateEventMetadata();
+ metadata.Add(nameof(sha), sha);
+ metadata.Add("bufferContents", Encoding.UTF8.GetString(buffer));
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: SymLink target exceeds buffer size");
+
+ throw new GetSymLinkTargetException("SymLink target exceeds buffer size");
+ }
+ }
+ }))
+ {
+ EventMetadata metadata = this.CreateEventMetadata();
+ metadata.Add(nameof(sha), sha);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: TryCopyBlobContentStream failed");
+
+ return false;
+ }
+ }
+ catch (GetSymLinkTargetException e)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath: null, exception: e);
+ metadata.Add(nameof(sha), sha);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: TryCopyBlobContentStream caught GetSymLinkTargetException");
+
+ return false;
+ }
+ catch (DecoderFallbackException e)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath: null, exception: e);
+ metadata.Add(nameof(sha), sha);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.TryGetSymLinkTarget)}: TryCopyBlobContentStream caught DecoderFallbackException");
+
+ return false;
+ }
+
+ symLinkTarget = symLinkBlobContents;
+
+ return true;
+ }
+
+ private Result OnGetFileStream(
+ ulong commandId,
+ string relativePath,
+ byte[] providerId,
+ byte[] contentId,
+ int triggeringProcessId,
+ string triggeringProcessName,
+ int fd)
+ {
+ try
+ {
+ if (contentId == null)
+ {
+ this.Context.Tracer.RelatedError($"{nameof(this.OnGetFileStream)} called with null contentId, path: " + relativePath);
+ return Result.EInvalidOperation;
+ }
+
+ if (providerId == null)
+ {
+ this.Context.Tracer.RelatedError($"{nameof(this.OnGetFileStream)} called with null epochId, path: " + relativePath);
+ return Result.EInvalidOperation;
+ }
+
+ string sha = GetShaFromContentId(contentId);
+ byte placeholderVersion = GetPlaceholderVersionFromProviderId(providerId);
+
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(triggeringProcessId), triggeringProcessId);
+ metadata.Add(nameof(triggeringProcessName), triggeringProcessName);
+ metadata.Add(nameof(sha), sha);
+ metadata.Add(nameof(placeholderVersion), placeholderVersion);
+ metadata.Add(nameof(commandId), commandId);
+ ITracer activity = this.Context.Tracer.StartActivity("GetFileStream", EventLevel.Verbose, Keywords.Telemetry, metadata);
+
+ if (placeholderVersion != FileSystemVirtualizer.PlaceholderVersion)
+ {
+ activity.RelatedError(metadata, nameof(this.OnGetFileStream) + ": Unexpected placeholder version");
+ activity.Dispose();
+
+ // TODO(#1362): Is this the correct Result to return?
+ return Result.EIOError;
+ }
+
+ try
+ {
+ if (!this.GitObjects.TryCopyBlobContentStream(
+ sha,
+ CancellationToken.None,
+ GVFSGitObjects.RequestSource.FileStreamCallback,
+ (stream, blobLength) =>
+ {
+ // TODO(#1361): Find a better solution than reading from the stream one byte at at time
+ byte[] buffer = new byte[4096];
+ uint bufferIndex = 0;
+ int nextByte = stream.ReadByte();
+ int bytesWritten = 0;
+ while (nextByte != -1)
+ {
+ while (bufferIndex < buffer.Length && nextByte != -1)
+ {
+ buffer[bufferIndex] = (byte)nextByte;
+ nextByte = stream.ReadByte();
+ ++bufferIndex;
+ }
+
+ Result result = this.virtualizationInstance.WriteFileContents(
+ fd,
+ buffer,
+ bufferIndex);
+ if (result != Result.Success)
+ {
+ activity.RelatedError(metadata, $"{nameof(this.virtualizationInstance.WriteFileContents)} failed, error: " + result.ToString("X") + "(" + result.ToString("G") + ")");
+ throw new GetFileStreamException(result);
+ }
+
+ if (bufferIndex == buffer.Length)
+ {
+ bufferIndex = 0;
+ bytesWritten += buffer.Length;
+ }
+ }
+ bytesWritten += Convert.ToInt32(bufferIndex);
+
+ if (bytesWritten != blobLength)
+ {
+ // If the read size does not match the expected size print an error and add the file to ModifiedPaths.dat
+ // This allows the user to see that something went wrong with file hydration
+ // Unfortunately we must do this check *after* the file is hydrated since the header isn't corrupt for trunctated objects on Linux
+ this.Context.Tracer.RelatedError($"Read {relativePath} to {bytesWritten}, not expected size of {blobLength}");
+ this.FileSystemCallbacks.OnFailedFileHydration(relativePath);
+ }
+ }))
+ {
+ activity.RelatedError(metadata, $"{nameof(this.OnGetFileStream)}: TryCopyBlobContentStream failed");
+
+ // TODO(#1362): Is this the correct Result to return?
+ return Result.EFileNotFound;
+ }
+ }
+ catch (GetFileStreamException e)
+ {
+ return e.Result;
+ }
+
+ this.FileSystemCallbacks.OnPlaceholderFileHydrated(triggeringProcessName);
+ return Result.Success;
+ }
+ catch (Exception e)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
+ metadata.Add(nameof(triggeringProcessId), triggeringProcessId);
+ metadata.Add(nameof(triggeringProcessName), triggeringProcessName);
+ metadata.Add(nameof(commandId), commandId);
+ this.LogUnhandledExceptionAndExit(nameof(this.OnGetFileStream), metadata);
+ }
+
+ return Result.EIOError;
+ }
+
+ private void OnLogError(string errorMessage)
+ {
+ this.Context.Tracer.RelatedError($"{nameof(LinuxFileSystemVirtualizer)}::{nameof(this.OnLogError)}: {errorMessage}");
+ }
+
+ private void OnLogWarning(string warningMessage)
+ {
+ this.Context.Tracer.RelatedWarning($"{nameof(LinuxFileSystemVirtualizer)}::{nameof(this.OnLogWarning)}: {warningMessage}");
+ }
+
+ private void OnLogInfo(string infoMessage)
+ {
+ this.Context.Tracer.RelatedInfo($"{nameof(LinuxFileSystemVirtualizer)}::{nameof(this.OnLogInfo)}: {infoMessage}");
+ }
+
+ private void OnFileModified(string relativePath)
+ {
+ try
+ {
+ if (Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath))
+ {
+ this.OnDotGitFileOrFolderChanged(relativePath);
+ }
+ }
+ catch (Exception e)
+ {
+ this.LogUnhandledExceptionAndExit(nameof(this.OnFileModified), this.CreateEventMetadata(relativePath, e));
+ }
+ }
+
+ private Result NotifyFilePreConvertToFull(string relativePath)
+ {
+ this.OnFilePreConvertToFull(relativePath);
+ return Result.Success;
+ }
+
+ private Result OnPreDelete(string relativePath, bool isDirectory)
+ {
+ try
+ {
+ bool pathInsideDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath);
+ if (pathInsideDotGit)
+ {
+ if (relativePath.Equals(GVFSConstants.DotGit.Index, GVFSPlatform.Instance.Constants.PathComparison))
+ {
+ string lockedGitCommand = this.Context.Repository.GVFSLock.GetLockedGitCommand();
+ if (string.IsNullOrEmpty(lockedGitCommand))
+ {
+ EventMetadata metadata = new EventMetadata();
+ metadata.Add("Area", this.EtwArea);
+ metadata.Add(TracingConstants.MessageKey.WarningMessage, "Blocked index delete outside the lock");
+ this.Context.Tracer.RelatedEvent(EventLevel.Warning, $"{nameof(this.OnPreDelete)}_BlockedIndexDelete", metadata);
+
+ return Result.EAccessDenied;
+ }
+ }
+
+ this.OnDotGitFileOrFolderDeleted(relativePath);
+ }
+ else
+ {
+ this.OnWorkingDirectoryFileOrFolderDeleteNotification(relativePath, isDirectory, isPreDelete: true);
+ }
+ }
+ catch (Exception e)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
+ metadata.Add("isDirectory", isDirectory);
+ this.LogUnhandledExceptionAndExit(nameof(this.OnPreDelete), metadata);
+ }
+
+ return Result.Success;
+ }
+
+ private void OnNewFileCreated(string relativePath, bool isDirectory)
+ {
+ try
+ {
+ if (!Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath))
+ {
+ if (isDirectory)
+ {
+ string lockedGitCommand = this.Context.Repository.GVFSLock.GetLockedGitCommand();
+ GitCommandLineParser gitCommand = new GitCommandLineParser(lockedGitCommand);
+ if (gitCommand.IsValidGitCommand)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath);
+ metadata.Add(nameof(lockedGitCommand), lockedGitCommand);
+ metadata.Add(TracingConstants.MessageKey.InfoMessage, "Git command created new folder");
+ this.Context.Tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.OnNewFileCreated)}_GitCreatedFolder", metadata);
+
+ // Record this folder as expanded so that GitIndexProjection will re-expand the folder
+ // when the projection change completes.
+ //
+ // Git creates new folders when there are files that it needs to create.
+ // However, git will only create files that are in ModifiedPaths.dat. There could
+ // be other files in the projection (that were not created by git) and so VFS must re-expand the
+ // newly created folder to ensure that all files are written to disk.
+ this.FileSystemCallbacks.OnPlaceholderFolderExpanded(relativePath);
+ }
+ else
+ {
+ this.FileSystemCallbacks.OnFolderCreated(relativePath, out bool sparseFoldersUpdated);
+ if (sparseFoldersUpdated)
+ {
+ // When sparseFoldersUpdated is true it means the folder was previously excluded from the projection and was
+ // included so it needs to enumerate the directory to get and create placeholders
+ // for all the directory items that are now included
+ this.OnEnumerateDirectory(0, relativePath, -1, $"{nameof(this.OnNewFileCreated)}_FolderIncluded");
+ }
+ }
+ }
+ else
+ {
+ this.FileSystemCallbacks.OnFileCreated(relativePath);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
+ metadata.Add("isDirectory", isDirectory);
+ this.LogUnhandledExceptionAndExit(nameof(this.OnNewFileCreated), metadata);
+ }
+ }
+
+ private void OnFileRenamed(string relativeDestinationPath, bool isDirectory)
+ {
+ // TODO(Linux): VFSForGit doesn't need the source path on Linux for correct behavior,
+ // but it is available if required in the future
+ this.OnFileRenamed(
+ relativeSourcePath: string.Empty,
+ relativeDestinationPath: relativeDestinationPath,
+ isDirectory: isDirectory);
+ }
+
+ private void OnHardLinkCreated(string relativeNewLinkPath)
+ {
+ this.OnHardLinkCreated(
+ relativeExistingFilePath: string.Empty,
+ relativeNewLinkPath: relativeNewLinkPath);
+ }
+
+ private Result OnEnumerateDirectory(
+ ulong commandId,
+ string relativePath,
+ int triggeringProcessId,
+ string triggeringProcessName)
+ {
+ relativePath = ConvertDotPath(relativePath);
+
+ try
+ {
+ Result result;
+ try
+ {
+ IEnumerable projectedItems;
+
+ // TODO(Linux): Pool these connections or schedule this work to run asynchronously using TryScheduleFileOrNetworkRequest
+ using (BlobSizes.BlobSizesConnection blobSizesConnection = this.FileSystemCallbacks.BlobSizes.CreateConnection())
+ {
+ projectedItems = this.FileSystemCallbacks.GitIndexProjection.GetProjectedItems(CancellationToken.None, blobSizesConnection, relativePath);
+ }
+
+ result = this.CreatePlaceholders(relativePath, projectedItems, triggeringProcessName);
+ }
+ catch (SizesUnavailableException e)
+ {
+ // TODO(Linux): Is this the correct Result to return?
+ result = Result.EIOError;
+
+ EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
+ metadata.Add("commandId", commandId);
+ metadata.Add(nameof(result), result.ToString("X") + "(" + result.ToString("G") + ")");
+ this.Context.Tracer.RelatedError(metadata, nameof(this.OnEnumerateDirectory) + ": caught SizesUnavailableException");
+ }
+
+ return result;
+ }
+ catch (Exception e)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
+ metadata.Add("commandId", commandId);
+ this.LogUnhandledExceptionAndExit(nameof(this.OnEnumerateDirectory), metadata);
+ }
+
+ return Result.EIOError;
+ }
+
+ private Result CreatePlaceholders(string directoryRelativePath, IEnumerable projectedItems, string triggeringProcessName)
+ {
+ foreach (ProjectedFileInfo fileInfo in projectedItems)
+ {
+ string childRelativePath = Path.Combine(directoryRelativePath, fileInfo.Name);
+
+ string sha;
+ FileSystemResult fileSystemResult;
+ if (fileInfo.IsFolder)
+ {
+ sha = string.Empty;
+ fileSystemResult = this.WritePlaceholderDirectory(childRelativePath);
+ }
+ else
+ {
+ sha = fileInfo.Sha.ToString();
+ fileSystemResult = this.WritePlaceholderFile(childRelativePath, fileInfo.Size, sha);
+ }
+
+ Result result = (Result)fileSystemResult.RawResult;
+ if (result != Result.Success)
+ {
+ EventMetadata metadata = this.CreateEventMetadata(childRelativePath);
+ metadata.Add("fileInfo.Name", fileInfo.Name);
+ metadata.Add("fileInfo.Size", fileInfo.Size);
+ metadata.Add("fileInfo.IsFolder", fileInfo.IsFolder);
+ metadata.Add(nameof(sha), sha);
+ this.Context.Tracer.RelatedError(metadata, $"{nameof(this.CreatePlaceholders)}: Write placeholder failed");
+
+ return result;
+ }
+ else
+ {
+ if (fileInfo.IsFolder)
+ {
+ this.FileSystemCallbacks.OnPlaceholderFolderCreated(childRelativePath, triggeringProcessName);
+ }
+ else
+ {
+ this.FileSystemCallbacks.OnPlaceholderFileCreated(childRelativePath, sha, triggeringProcessName);
+ }
+ }
+ }
+
+ this.FileSystemCallbacks.OnPlaceholderFolderExpanded(directoryRelativePath);
+
+ return Result.Success;
+ }
+
+ private class GetFileStreamException : Exception
+ {
+ public GetFileStreamException(Result errorCode)
+ : this("GetFileStreamException exception, error: " + errorCode.ToString(), errorCode)
+ {
+ }
+
+ public GetFileStreamException(string message, Result result)
+ : base(message)
+ {
+ this.Result = result;
+ }
+
+ public Result Result { get; }
+ }
+
+ private class GetSymLinkTargetException : Exception
+ {
+ public GetSymLinkTargetException(string message)
+ : base(message)
+ {
+ }
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/LinuxPlatform.Shared.cs b/GVFS/GVFS.Platform.Linux/LinuxPlatform.Shared.cs
new file mode 100644
index 0000000000..28528bd94e
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxPlatform.Shared.cs
@@ -0,0 +1,55 @@
+using System;
+using System.IO;
+using GVFS.Common;
+using GVFS.Platform.POSIX;
+
+namespace GVFS.Platform.Linux
+{
+ public partial class LinuxPlatform
+ {
+ public const string DotGVFSRoot = ".vfsforgit";
+ public const string UpgradeConfirmMessage = "`sudo gvfs upgrade --confirm --no-verify`";
+
+ public static string GetDataRootForGVFSImplementation()
+ {
+ // TODO(Linux): determine installation location and data path
+ string path = Environment.GetEnvironmentVariable("VFS4G_DATA_PATH");
+ return path ?? "/var/run/vfsforgit";
+ }
+
+ public static string GetDataRootForGVFSComponentImplementation(string componentName)
+ {
+ return Path.Combine(GetDataRootForGVFSImplementation(), componentName);
+ }
+
+ public static bool TryGetGVFSEnlistmentRootImplementation(string directory, out string enlistmentRoot, out string errorMessage)
+ {
+ return POSIXPlatform.TryGetGVFSEnlistmentRootImplementation(directory, DotGVFSRoot, out enlistmentRoot, out errorMessage);
+ }
+
+ public static string GetUpgradeHighestAvailableVersionDirectoryImplementation()
+ {
+ return GetUpgradeNonProtectedDirectoryImplementation();
+ }
+
+ public static string GetUpgradeNonProtectedDirectoryImplementation()
+ {
+ return Path.Combine(GetDataRootForGVFSImplementation(), ProductUpgraderInfo.UpgradeDirectoryName);
+ }
+
+ public static string GetNamedPipeNameImplementation(string enlistmentRoot)
+ {
+ return POSIXPlatform.GetNamedPipeNameImplementation(enlistmentRoot, DotGVFSRoot);
+ }
+
+ public static string GetUpgradeReminderNotificationImplementation()
+ {
+ return $"A new version of VFS for Git is available. Run {UpgradeConfirmMessage} to upgrade.";
+ }
+
+ private string GetUpgradeNonProtectedDataDirectory()
+ {
+ return GetUpgradeNonProtectedDirectoryImplementation();
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/LinuxPlatform.cs b/GVFS/GVFS.Platform.Linux/LinuxPlatform.cs
new file mode 100644
index 0000000000..0880b80afc
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxPlatform.cs
@@ -0,0 +1,175 @@
+using GVFS.Common;
+using GVFS.Common.FileSystem;
+using GVFS.Common.Tracing;
+using GVFS.Platform.POSIX;
+using System.Collections.Generic;
+using System.IO;
+
+namespace GVFS.Platform.Linux
+{
+ public partial class LinuxPlatform : POSIXPlatform
+ {
+ // TODO(Linux): determine installation location and upgrader path
+ private const string UpgradeProtectedDataDirectory = "/usr/local/vfsforgit_upgrader";
+
+ public LinuxPlatform()
+ {
+ }
+
+ public override IDiskLayoutUpgradeData DiskLayoutUpgrade { get; } = new LinuxDiskLayoutUpgradeData();
+ public override IKernelDriver KernelDriver { get; } = new ProjFSLib();
+ public override string Name { get => "Linux"; }
+ public override GVFSPlatformConstants Constants { get; } = new LinuxPlatformConstants();
+ public override IPlatformFileSystem FileSystem { get; } = new LinuxFileSystem();
+
+ public override string GVFSConfigPath
+ {
+ get
+ {
+ return Path.Combine(this.Constants.GVFSBinDirectoryPath, LocalGVFSConfig.FileName);
+ }
+ }
+
+ ///
+ /// On Linux VFSForGit does not need to use system wide logs to track
+ /// installer messages. VFSForGit is able to specifiy a custom installer
+ /// log file as a commandline argument to the installer.
+ ///
+ public override bool SupportsSystemInstallLog
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public override string GetOSVersionInformation()
+ {
+ ProcessResult result = ProcessHelper.Run("sw_vers", args: string.Empty, redirectOutput: true);
+ return string.IsNullOrWhiteSpace(result.Output) ? result.Errors : result.Output;
+ }
+
+ public override string GetSecureDataRootForGVFS()
+ {
+ // On the Linux, unlike Windows, there is no separate secure data root directory.
+ return LinuxPlatform.GetDataRootForGVFSImplementation();
+ }
+
+ public override string GetSecureDataRootForGVFSComponent(string componentName)
+ {
+ // On the Linux, unlike Windows, there is no separate secure data root directory.
+ return LinuxPlatform.GetDataRootForGVFSComponentImplementation(componentName);
+ }
+
+ public override string GetCommonAppDataRootForGVFS()
+ {
+ return this.GetSecureDataRootForGVFS();
+ }
+
+ public override string GetLogsDirectoryForGVFSComponent(string componentName)
+ {
+ return Path.Combine(this.GetCommonAppDataRootForGVFS(), componentName);
+ }
+
+ public override bool TryGetGVFSEnlistmentRoot(string directory, out string enlistmentRoot, out string errorMessage)
+ {
+ return LinuxPlatform.TryGetGVFSEnlistmentRootImplementation(directory, out enlistmentRoot, out errorMessage);
+ }
+
+ public override string GetNamedPipeName(string enlistmentRoot)
+ {
+ return LinuxPlatform.GetNamedPipeNameImplementation(enlistmentRoot);
+ }
+
+ public override FileBasedLock CreateFileBasedLock(
+ PhysicalFileSystem fileSystem,
+ ITracer tracer,
+ string lockPath)
+ {
+ return new LinuxFileBasedLock(fileSystem, tracer, lockPath);
+ }
+
+ public override string GetUpgradeProtectedDataDirectory()
+ {
+ return UpgradeProtectedDataDirectory;
+ }
+
+ public override string GetUpgradeHighestAvailableVersionDirectory()
+ {
+ return GetUpgradeHighestAvailableVersionDirectoryImplementation();
+ }
+
+ ///
+ /// This is the directory in which the upgradelogs directory should go.
+ /// There can be multiple logs directories, so here we return the containing
+ /// directory.
+ ///
+ public override string GetUpgradeLogDirectoryParentDirectory()
+ {
+ return this.GetUpgradeNonProtectedDataDirectory();
+ }
+
+ public override string GetSystemInstallerLogPath()
+ {
+ return null;
+ }
+
+ public override ProductUpgraderPlatformStrategy CreateProductUpgraderPlatformInteractions(
+ PhysicalFileSystem fileSystem,
+ ITracer tracer)
+ {
+ return new LinuxProductUpgraderPlatformStrategy(fileSystem, tracer);
+ }
+
+ public class LinuxPlatformConstants : POSIXPlatformConstants
+ {
+ public override string InstallerExtension
+ {
+ get { return ".deb"; }
+ }
+
+ public override string WorkingDirectoryBackingRootPath
+ {
+ get { return Path.Combine(this.DotGVFSRoot, "lower"); }
+ }
+
+ public override string DotGVFSRoot
+ {
+ get { return LinuxPlatform.DotGVFSRoot; }
+ }
+
+ public override string GVFSBinDirectoryPath
+ {
+ get { return Path.Combine("/usr", "local", this.GVFSBinDirectoryName); }
+ }
+
+ public override string GVFSBinDirectoryName
+ {
+ get { return "vfsforgit"; }
+ }
+
+ public override string UpgradeInstallAdviceMessage
+ {
+ get { return $"When ready, run {this.UpgradeConfirmCommandMessage} to upgrade."; }
+ }
+
+ public override string UpgradeConfirmCommandMessage
+ {
+ get { return UpgradeConfirmMessage; }
+ }
+
+ public override string StartServiceCommandMessage
+ {
+ // TODO(Linux): implement service daemon
+ get { return "Not yet implemented"; }
+ }
+
+ public override string RunUpdateMessage
+ {
+ get { return $"Run {UpgradeConfirmMessage}."; }
+ }
+
+ public override bool CaseSensitiveFileSystem => true;
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/LinuxProductUpgraderPlatformStrategy.cs b/GVFS/GVFS.Platform.Linux/LinuxProductUpgraderPlatformStrategy.cs
new file mode 100644
index 0000000000..75e206dcc4
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/LinuxProductUpgraderPlatformStrategy.cs
@@ -0,0 +1,62 @@
+using GVFS.Common;
+using GVFS.Common.FileSystem;
+using GVFS.Common.Tracing;
+using System;
+using System.IO;
+
+namespace GVFS.Platform.Linux
+{
+ public class LinuxProductUpgraderPlatformStrategy : ProductUpgraderPlatformStrategy
+ {
+ public LinuxProductUpgraderPlatformStrategy(PhysicalFileSystem fileSystem, ITracer tracer)
+ : base(fileSystem, tracer)
+ {
+ }
+
+ public override bool TryPrepareLogDirectory(out string error)
+ {
+ error = null;
+ return true;
+ }
+
+ public override bool TryPrepareApplicationDirectory(out string error)
+ {
+ string upgradeApplicationDirectory = ProductUpgraderInfo.GetUpgradeApplicationDirectory();
+
+ Exception deleteDirectoryException;
+ if (this.FileSystem.DirectoryExists(upgradeApplicationDirectory) &&
+ !this.FileSystem.TryDeleteDirectory(upgradeApplicationDirectory, out deleteDirectoryException))
+ {
+ error = $"Failed to delete {upgradeApplicationDirectory} - {deleteDirectoryException.Message}";
+
+ this.TraceException(deleteDirectoryException, nameof(this.TryPrepareApplicationDirectory), $"Error deleting {upgradeApplicationDirectory}.");
+ return false;
+ }
+
+ this.FileSystem.CreateDirectory(upgradeApplicationDirectory);
+
+ error = null;
+ return true;
+ }
+
+ public override bool TryPrepareDownloadDirectory(out string error)
+ {
+ string directory = ProductUpgraderInfo.GetAssetDownloadsPath();
+
+ Exception deleteDirectoryException;
+ if (this.FileSystem.DirectoryExists(directory) &&
+ !this.FileSystem.TryDeleteDirectory(directory, out deleteDirectoryException))
+ {
+ error = $"Failed to delete {directory} - {deleteDirectoryException.Message}";
+
+ this.TraceException(deleteDirectoryException, nameof(this.TryPrepareDownloadDirectory), $"Error deleting {directory}.");
+ return false;
+ }
+
+ this.FileSystem.CreateDirectory(directory);
+
+ error = null;
+ return true;
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Linux/ProjFSLib.cs b/GVFS/GVFS.Platform.Linux/ProjFSLib.cs
new file mode 100644
index 0000000000..490567e2e5
--- /dev/null
+++ b/GVFS/GVFS.Platform.Linux/ProjFSLib.cs
@@ -0,0 +1,68 @@
+using GVFS.Common;
+using GVFS.Common.FileSystem;
+using GVFS.Common.Tracing;
+using System;
+using System.IO;
+
+namespace GVFS.Platform.Linux
+{
+ public class ProjFSLib : IKernelDriver
+ {
+ public bool EnumerationExpandsDirectories { get; } = true;
+ public bool EmptyPlaceholdersRequireFileSize { get; } = true;
+
+ /* TODO(Linux): check for kernel fuse, libfuse v3, libprojfs;
+ * flesh out all methods below
+ */
+
+ public string LogsFolderPath
+ {
+ get
+ {
+ return Path.Combine(System.IO.Path.GetTempPath(), "ProjFSLib");
+ }
+ }
+
+ public bool IsGVFSUpgradeSupported()
+ {
+ return false;
+ }
+
+ public bool IsSupported(string normalizedEnlistmentRootPath, out string warning, out string error)
+ {
+ warning = null;
+ error = null;
+ return true;
+ }
+
+ public bool TryFlushLogs(out string error)
+ {
+ Directory.CreateDirectory(this.LogsFolderPath);
+ error = string.Empty;
+ return true;
+ }
+
+ public bool IsReady(JsonTracer tracer, string enlistmentRoot, TextWriter output, out string error)
+ {
+ error = null;
+ return true;
+ }
+
+ public bool RegisterForOfflineIO()
+ {
+ return true;
+ }
+
+ public bool UnregisterForOfflineIO()
+ {
+ return true;
+ }
+
+ public bool TryPrepareFolderForCallbacks(string folderPath, out string error, out Exception exception)
+ {
+ error = null;
+ exception = null;
+ return true;
+ }
+ }
+}
diff --git a/GVFS/GVFS.Platform.Mac/GVFS.Platform.Mac.csproj b/GVFS/GVFS.Platform.Mac/GVFS.Platform.Mac.csproj
index d702269054..feb5604c7b 100644
--- a/GVFS/GVFS.Platform.Mac/GVFS.Platform.Mac.csproj
+++ b/GVFS/GVFS.Platform.Mac/GVFS.Platform.Mac.csproj
@@ -18,6 +18,10 @@
$(GVFSVersion)
+
+ true
+
+
@@ -32,7 +36,7 @@
-
+
diff --git a/GVFS/GVFS.PlatformLoader/PlatformLoader.Linux.cs b/GVFS/GVFS.PlatformLoader/PlatformLoader.Linux.cs
new file mode 100644
index 0000000000..ed8ecd82eb
--- /dev/null
+++ b/GVFS/GVFS.PlatformLoader/PlatformLoader.Linux.cs
@@ -0,0 +1,21 @@
+using GVFS.Common;
+using GVFS.Common.Git;
+using GVFS.Platform.Linux;
+using GVFS.Virtualization.FileSystem;
+
+namespace GVFS.PlatformLoader
+{
+ public static class GVFSPlatformLoader
+ {
+ public static FileSystemVirtualizer CreateFileSystemVirtualizer(GVFSContext context, GVFSGitObjects gitObjects)
+ {
+ return new LinuxFileSystemVirtualizer(context, gitObjects);
+ }
+
+ public static void Initialize()
+ {
+ GVFSPlatform.Register(new LinuxPlatform());
+ return;
+ }
+ }
+}
diff --git a/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj b/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj
index c351c3ba93..aa42534e24 100644
--- a/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj
+++ b/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj
@@ -50,8 +50,10 @@
+
+
diff --git a/GVFS/GVFS.Upgrader/GVFS.Upgrader.csproj b/GVFS/GVFS.Upgrader/GVFS.Upgrader.csproj
index 3b9629484a..25995b9847 100644
--- a/GVFS/GVFS.Upgrader/GVFS.Upgrader.csproj
+++ b/GVFS/GVFS.Upgrader/GVFS.Upgrader.csproj
@@ -20,6 +20,11 @@
$(GVFSVersion)
+
+ true
+ true
+
+
@@ -35,7 +40,20 @@
-
+
+
+ netcoreapp2.1
+ linux-x64
+ $(DefineConstants);LINUX_BUILD
+
+
+
+
+ PlatformLoader.Linux.cs
+
+
+
+
netcoreapp2.1
osx-x64
@@ -47,7 +65,7 @@
PlatformLoader.Mac.cs
-
+
diff --git a/GVFS/GVFS.Upgrader/LinuxUpgradeOrchestrator.cs b/GVFS/GVFS.Upgrader/LinuxUpgradeOrchestrator.cs
new file mode 100644
index 0000000000..92043f2844
--- /dev/null
+++ b/GVFS/GVFS.Upgrader/LinuxUpgradeOrchestrator.cs
@@ -0,0 +1,17 @@
+namespace GVFS.Upgrader
+{
+ public class LinuxUpgradeOrchestrator : UpgradeOrchestrator
+ {
+ public LinuxUpgradeOrchestrator(UpgradeOptions options)
+ : base(options)
+ {
+ }
+
+ protected override bool TryMountRepositories(out string consoleError)
+ {
+ // Linux upgrader does not mount repositories
+ consoleError = null;
+ return true;
+ }
+ }
+}
diff --git a/GVFS/GVFS.Upgrader/UpgradeOrchestratorFactory.cs b/GVFS/GVFS.Upgrader/UpgradeOrchestratorFactory.cs
index c2ce00dbcf..fec275d162 100644
--- a/GVFS/GVFS.Upgrader/UpgradeOrchestratorFactory.cs
+++ b/GVFS/GVFS.Upgrader/UpgradeOrchestratorFactory.cs
@@ -1,10 +1,14 @@
+using System;
+
namespace GVFS.Upgrader
{
public static class UpgradeOrchestratorFactory
{
public static UpgradeOrchestrator Create(UpgradeOptions options)
{
-#if MACOS_BUILD
+#if LINUX_BUILD
+ return new LinuxUpgradeOrchestrator(options);
+#elif MACOS_BUILD
return new MacUpgradeOrchestrator(options);
#elif WINDOWS_BUILD
return new WindowsUpgradeOrchestrator(options);
diff --git a/GVFS/GVFS/GVFS.Linux.csproj b/GVFS/GVFS/GVFS.Linux.csproj
new file mode 100644
index 0000000000..116f594d97
--- /dev/null
+++ b/GVFS/GVFS/GVFS.Linux.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ gvfs
+ netcoreapp2.1
+ x64
+ linux-x64
+
+
+
+ $(GVFSVersion)
+
+
+ $(GVFSVersion)
+
+
+
+ all
+
+
+
+
+
+
+
+
+
diff --git a/MirrorProvider/MirrorProvider.Linux/MirrorProvider.Linux.csproj b/MirrorProvider/MirrorProvider.Linux/MirrorProvider.Linux.csproj
index 19adade198..2b88e7d4ad 100644
--- a/MirrorProvider/MirrorProvider.Linux/MirrorProvider.Linux.csproj
+++ b/MirrorProvider/MirrorProvider.Linux/MirrorProvider.Linux.csproj
@@ -38,7 +38,7 @@
diff --git a/ProjFS.Linux/PrjFSLib.Linux.Managed/PrjFSLib.Linux.Managed.csproj b/ProjFS.Linux/PrjFSLib.Linux.Managed/PrjFSLib.Linux.Managed.csproj
index 34866e92a6..edc8fee45f 100644
--- a/ProjFS.Linux/PrjFSLib.Linux.Managed/PrjFSLib.Linux.Managed.csproj
+++ b/ProjFS.Linux/PrjFSLib.Linux.Managed/PrjFSLib.Linux.Managed.csproj
@@ -4,6 +4,7 @@
x64
Debug;Release
..\..\GVFS\GVFS.Build\GVFS.ruleset
+ true
diff --git a/Scripts/Linux/BuildGVFSForLinux.sh b/Scripts/Linux/BuildGVFSForLinux.sh
new file mode 100755
index 0000000000..a25c25c9c8
--- /dev/null
+++ b/Scripts/Linux/BuildGVFSForLinux.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+CONFIGURATION=$1
+if [ -z $CONFIGURATION ]; then
+ CONFIGURATION=Debug
+fi
+
+if [ ! -d $VFS_OUTPUTDIR ]; then
+ mkdir $VFS_OUTPUTDIR
+fi
+
+echo 'Building Linux libraries...'
+$VFS_SRCDIR/ProjFS.Linux/Scripts/Build.sh $CONFIGURATION || exit 1
+
+# Create the directory where we'll do pre build tasks
+BUILDDIR=$VFS_OUTPUTDIR/GVFS.Build
+if [ ! -d $BUILDDIR ]; then
+ mkdir $BUILDDIR || exit 1
+fi
+
+# TODO(Linux): download VFS-enabled version of upstream Git
+#echo 'Downloading a VFS-enabled version of Git...'
+#$VFS_SCRIPTDIR/DownloadGVFSGit.sh || exit 1
+GITVERSION="$($VFS_SCRIPTDIR/GetGitVersionNumber.sh)"
+#GITPATH="$(find $VFS_PACKAGESDIR/gitformac.gvfs.installer/$GITVERSION -type f -name *.dmg)" || exit 1
+#echo "Downloaded Git $GITVERSION"
+
+# TODO(Linux): git version based on .dmg filename:
+# packages/gitformac.gvfs.installer/2.20190724.1/tools/git-2.22.0.vfs.1.1.49.ge8fe4ef9d1-intel-universal-snow-leopard.dmg
+# downloaded from https://vfsforgit.myget.org/F/build-dependencies/api/v3/flatcontainer/gitformac.gvfs.installer/2.20190724.1/gitformac.gvfs.installer.2.20190724.1.nupkg
+GITPATH="2.22.0.vfs.1.1.49"
+# Now that we have a path containing the version number, generate GVFSConstants.GitVersion.cs
+$VFS_SCRIPTDIR/GenerateGitVersionConstants.sh "$GITPATH" $BUILDDIR || exit 1
+
+# If we're building the Profiling(Release) configuration, remove Profiling() for building .NET code
+if [ "$CONFIGURATION" == "Profiling(Release)" ]; then
+ CONFIGURATION=Release
+fi
+
+echo 'Generating CommonAssemblyVersion.cs...'
+$VFS_SCRIPTDIR/GenerateCommonAssemblyVersion.sh || exit 1
+
+echo 'Restoring packages...'
+dotnet restore $VFS_SRCDIR/GVFS.sln /p:Configuration=$CONFIGURATION.Linux --packages $VFS_PACKAGESDIR /warnasmessage:MSB4011 || exit 1
+dotnet build $VFS_SRCDIR/GVFS.sln --runtime linux-x64 --framework netcoreapp2.1 --configuration $CONFIGURATION.Linux -p:CopyPrjFS=true /maxcpucount:1 /warnasmessage:MSB4011 || exit 1
+
+# TODO(Linux): build native hook programs
+NATIVEDIR=$VFS_SRCDIR/GVFS/GVFS.Native.Linux
+#xcodebuild -configuration $CONFIGURATION -workspace $NATIVEDIR/GVFS.Native.Linux.xcworkspace build -scheme GVFS.Native.Linux -derivedDataPath $VFS_OUTPUTDIR/GVFS.Native.Linux || exit 1
+
+if [ ! -d $VFS_PUBLISHDIR ]; then
+ mkdir $VFS_PUBLISHDIR || exit 1
+fi
+
+# TODO(Linux): copy native hook programs
+#echo 'Copying native binaries to Publish directory...'
+#cp $VFS_OUTPUTDIR/GVFS.Native.Linux/Build/Products/$CONFIGURATION/GVFS.ReadObjectHook $VFS_PUBLISHDIR || exit 1
+#cp $VFS_OUTPUTDIR/GVFS.Native.Linux/Build/Products/$CONFIGURATION/GVFS.VirtualFileSystemHook $VFS_PUBLISHDIR || exit 1
+#cp $VFS_OUTPUTDIR/GVFS.Native.Linux/Build/Products/$CONFIGURATION/GVFS.PostIndexChangedHook $VFS_PUBLISHDIR || exit 1
+
+# Publish after native build, so installer package can include the native binaries.
+dotnet publish $VFS_SRCDIR/GVFS.sln /p:Configuration=$CONFIGURATION.Linux /p:Platform=x64 -p:CopyPrjFS=true --runtime linux-x64 --framework netcoreapp2.1 --self-contained --output $VFS_PUBLISHDIR /maxcpucount:1 /warnasmessage:MSB4011 || exit 1
+
+# TODO(Linux): copy installer (if any)
+#echo 'Copying Git installer to the output directory...'
+#$VFS_SCRIPTDIR/PublishGit.sh $GITPATH || exit 1
+
+echo 'Running VFS for Git unit tests...'
+$VFS_PUBLISHDIR/GVFS.UnitTests || exit 1
diff --git a/Scripts/Linux/DownloadGVFSGit.sh b/Scripts/Linux/DownloadGVFSGit.sh
new file mode 100755
index 0000000000..54a742b906
--- /dev/null
+++ b/Scripts/Linux/DownloadGVFSGit.sh
@@ -0,0 +1,7 @@
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+BUILDDIR=$VFS_OUTPUTDIR/GVFS.Build
+GITVERSION="$($VFS_SCRIPTDIR/GetGitVersionNumber.sh)"
+cp $VFS_SRCDIR/nuget.config $BUILDDIR
+dotnet new classlib -n Restore.GitInstaller -o $BUILDDIR --force
+dotnet add $BUILDDIR/Restore.GitInstaller.csproj package --package-directory $VFS_PACKAGESDIR GitForMac.GVFS.Installer --version $GITVERSION
\ No newline at end of file
diff --git a/Scripts/Linux/GVFS_Clone.sh b/Scripts/Linux/GVFS_Clone.sh
new file mode 100755
index 0000000000..250d3aa9a1
--- /dev/null
+++ b/Scripts/Linux/GVFS_Clone.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+REPOURL=$1
+
+CONFIGURATION=$2
+if [ -z $CONFIGURATION ]; then
+ CONFIGURATION=Debug
+fi
+
+$VFS_PUBLISHDIR/gvfs clone $REPOURL ~/GVFSTest --local-cache-path ~/GVFSTest/.gvfsCache --no-mount --no-prefetch
diff --git a/Scripts/Linux/GVFS_Mount.sh b/Scripts/Linux/GVFS_Mount.sh
new file mode 100755
index 0000000000..78e6f946ac
--- /dev/null
+++ b/Scripts/Linux/GVFS_Mount.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+CONFIGURATION=$1
+if [ -z $CONFIGURATION ]; then
+ CONFIGURATION=Debug
+fi
+
+$VFS_PUBLISHDIR/gvfs mount ~/GVFSTest
diff --git a/Scripts/Linux/GVFS_Unmount.sh b/Scripts/Linux/GVFS_Unmount.sh
new file mode 100755
index 0000000000..36d7460d2b
--- /dev/null
+++ b/Scripts/Linux/GVFS_Unmount.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+CONFIGURATION=$1
+if [ -z $CONFIGURATION ]; then
+ CONFIGURATION=Debug
+fi
+
+$VFS_PUBLISHDIR/gvfs unmount ~/GVFSTest
diff --git a/Scripts/Linux/GenerateCommonAssemblyVersion.sh b/Scripts/Linux/GenerateCommonAssemblyVersion.sh
new file mode 100755
index 0000000000..4e2535e7b6
--- /dev/null
+++ b/Scripts/Linux/GenerateCommonAssemblyVersion.sh
@@ -0,0 +1,11 @@
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+GVFSPROPS=$VFS_SRCDIR/GVFS/GVFS.Build/GVFS.props
+VERSIONNUMBER="$(cat $GVFSPROPS | grep GVFSVersion | grep -Eo '[0-9.]+(-\w+)?')"
+
+cat >$VFS_OUTPUTDIR/CommonAssemblyVersion.cs <$2/GVFSConstants.GitVersion.cs </dev/null
+export VFS_SCRIPTDIR="$(pwd)"
+popd &>/dev/null
+
+export VFS_SRCDIR=$VFS_SCRIPTDIR/../..
+
+VFS_ENLISTMENTDIR=$VFS_SRCDIR/..
+export VFS_OUTPUTDIR=$VFS_ENLISTMENTDIR/BuildOutput
+export VFS_PUBLISHDIR=$VFS_ENLISTMENTDIR/Publish
+export VFS_PACKAGESDIR=$VFS_ENLISTMENTDIR/packages
diff --git a/Scripts/Linux/NukeBuildOutputs.sh b/Scripts/Linux/NukeBuildOutputs.sh
new file mode 100755
index 0000000000..287a657a48
--- /dev/null
+++ b/Scripts/Linux/NukeBuildOutputs.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+. "$(dirname ${BASH_SOURCE[0]})/InitializeEnvironment.sh"
+
+sudo rm -Rf $VFS_OUTPUTDIR
+rm -Rf $VFS_PACKAGESDIR
+rm -Rf $VFS_PUBLISHDIR
+
+echo git --work-tree=$VFS_SRCDIR clean -Xdf -n
+git --work-tree=$VFS_SRCDIR clean -Xdf -n