Skip to content
2 changes: 1 addition & 1 deletion MirrorProvider/MirrorProvider.Linux/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace MirrorProvider.Linux
namespace MirrorProvider.Linux
{
class Program
{
Expand Down
5 changes: 5 additions & 0 deletions MirrorProvider/Scripts/Linux/Build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ fi
# Build the ProjFS library interface
$SRCDIR/ProjFS.Linux/Scripts/Build.sh $CONFIGURATION || exit 1

# If we're building the Profiling(Release) configuration, remove Profiling() for building .NET code
if [ "$CONFIGURATION" == "Profiling(Release)" ]; then
CONFIGURATION=Release
fi

# Build the MirrorProvider
dotnet restore $SLN /p:Configuration="$CONFIGURATION.Linux" --packages $ROOTDIR/packages
dotnet build $SLN --configuration $CONFIGURATION.Linux
Expand Down
63 changes: 62 additions & 1 deletion ProjFS.Linux/PrjFSLib.Linux.Managed/Interop/ProjFS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ internal partial class ProjFS

private const string ProviderIdAttrName = "vfsforgit.providerid";
private const string ContentIdAttrName = "vfsforgit.contentid";
private const string StateAttrName = "empty";

private readonly IntPtr providerIdAttrNamePtr = Marshal.StringToHGlobalAnsi(ProviderIdAttrName);
private readonly IntPtr contentIdAttrNamePtr = Marshal.StringToHGlobalAnsi(ContentIdAttrName);
private readonly IntPtr stateAttrNamePtr = Marshal.StringToHGlobalAnsi(StateAttrName);

private readonly IntPtr handle;

Expand Down Expand Up @@ -145,6 +147,65 @@ public Result GetProjAttrs(
}
}

public Result GetProjState(
string relativePath,
out ProjectionState state)
{
unsafe
{
byte stateAttr;
Attr[] attrs = new[]
{
new Attr
{
Name = (byte*)this.stateAttrNamePtr,
Value = &stateAttr,
Size = 1
}
};

int res = _GetProjAttrs(
this.handle,
relativePath,
attrs,
(uint)attrs.Length);
Result result = res.ToResult();

if (result == Result.Success)
{
if (attrs[0].Size == -1)
{
state = ProjectionState.Full;
}
else if (stateAttr == 'n')
{
state = ProjectionState.Hydrated;
}
else if (stateAttr == 'y')
{
state = ProjectionState.Empty;
}
else
{
state = ProjectionState.Invalid;
result = Result.Invalid;
}
}
else if (res == Errno.Constants.EPERM)
{
// EPERM returned when inode is neither file nor directory
state = ProjectionState.Unknown;
result = Result.Invalid;
}
else
{
state = ProjectionState.Invalid;
}

return result;
}
}

[DllImport(PrjFSLibPath, EntryPoint = "projfs_new")]
private static extern IntPtr _New(
string lowerdir,
Expand Down Expand Up @@ -188,7 +249,7 @@ private static extern int _CreateProjSymlink(
private static extern int _GetProjAttrs(
IntPtr fs,
string relativePath,
Attr[] attrs,
[In, Out] Attr[] attrs,
uint nattrs);

[StructLayout(LayoutKind.Sequential)]
Expand Down
2 changes: 1 addition & 1 deletion ProjFS.Linux/PrjFSLib.Linux.Managed/NotificationType.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace PrjFSLib.Linux
{
Expand Down
14 changes: 14 additions & 0 deletions ProjFS.Linux/PrjFSLib.Linux.Managed/ProjectionState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace PrjFSLib.Linux
{
public enum ProjectionState
{
Invalid = 0x00000000,
Unknown,

Empty,
Hydrated,
Full
}
}
2 changes: 1 addition & 1 deletion ProjFS.Linux/PrjFSLib.Linux.Managed/UpdateFailureCause.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace PrjFSLib.Linux
{
Expand Down
2 changes: 1 addition & 1 deletion ProjFS.Linux/PrjFSLib.Linux.Managed/UpdateType.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;

namespace PrjFSLib.Linux
{
Expand Down
141 changes: 104 additions & 37 deletions ProjFS.Linux/PrjFSLib.Linux.Managed/VirtualizationInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class VirtualizationInstance

private ProjFS projfs;
private int currentProcessId = Process.GetCurrentProcess().Id;
private string virtualizationRoot;

// We must hold a reference to the delegates to prevent garbage collection
private ProjFS.EventHandler preventGCOnProjEventDelegate;
Expand Down Expand Up @@ -65,6 +66,7 @@ public virtual Result StartVirtualizationInstance(
}

this.projfs = fs;
this.virtualizationRoot = virtualizationRootFullPath;
return Result.Success;
}

Expand Down Expand Up @@ -97,18 +99,51 @@ public virtual Result DeleteFile(
UpdateType updateFlags,
out UpdateFailureCause failureCause)
{
/*
UpdateFailureCause deleteFailureCause = UpdateFailureCause.NoFailure;
Result result = ProjFS.DeleteFile(
relativePath,
updateFlags,
ref deleteFailureCause);
failureCause = UpdateFailureCause.NoFailure;
if (string.IsNullOrEmpty(relativePath))
{
/* Our mount point directory can not be deleted; we would
* receive an EBUSY error. Therefore we just return
* EDirectoryNotEmpty because that error is silently handled
* by our caller in GitIndexProjection, and this is the
* expected behavior (corresponding to the Mac implementation).
*/
return Result.EDirectoryNotEmpty;
}

string fullPath = Path.Combine(this.virtualizationRoot, relativePath);
bool isDirectory = Directory.Exists(fullPath);
Result result = Result.Success;
if (!isDirectory)
{
// TODO(Linux): try to handle races with hydration?
ProjectionState state;
result = this.projfs.GetProjState(relativePath, out state);

// also treat unknown state as full/dirty (e.g., for sockets)
if ((result == Result.Success && state == ProjectionState.Full) ||
(result == Result.Invalid && state == ProjectionState.Unknown))
{
failureCause = UpdateFailureCause.DirtyData;
return Result.EVirtualizationInvalidOperation;
}
}

if (result == Result.Success)
{
result = RemoveFileOrDirectory(fullPath, isDirectory);
}

if (result == Result.EAccessDenied)
{
failureCause = UpdateFailureCause.ReadOnly;
}
else if (result == Result.EFileNotFound || result == Result.EPathNotFound)
{
return Result.Success;
}

failureCause = deleteFailureCause;
return result;
*/
failureCause = UpdateFailureCause.NoFailure;
return Result.ENotYetImplemented;
}

public virtual Result WritePlaceholderDirectory(
Expand Down Expand Up @@ -156,28 +191,21 @@ public virtual Result UpdatePlaceholderIfNeeded(
UpdateType updateFlags,
out UpdateFailureCause failureCause)
{
/*
if (providerId.Length != ProjFS.PlaceholderIdLength ||
contentId.Length != ProjFS.PlaceholderIdLength)
if (providerId.Length != PlaceholderIdLength ||
contentId.Length != PlaceholderIdLength)
{
throw new ArgumentException();
}

UpdateFailureCause updateFailureCause = UpdateFailureCause.NoFailure;
Result result = ProjFS.UpdatePlaceholderFileIfNeeded(
relativePath,
providerId,
contentId,
fileSize,
fileMode,
updateFlags,
ref updateFailureCause);
Result result = this.DeleteFile(relativePath, updateFlags, out failureCause);
if (result != Result.Success)
{
return result;
}

failureCause = updateFailureCause;
return result;
*/
// TODO(Linux): try to handle races with hydration?
failureCause = UpdateFailureCause.NoFailure;
return Result.ENotYetImplemented;
return this.WritePlaceholderFile(relativePath, providerId, contentId, fileSize, fileMode);
}

public virtual Result ReplacePlaceholderFileWithSymLink(
Expand All @@ -186,19 +214,15 @@ public virtual Result ReplacePlaceholderFileWithSymLink(
UpdateType updateFlags,
out UpdateFailureCause failureCause)
{
/*
UpdateFailureCause updateFailureCause = UpdateFailureCause.NoFailure;
Result result = ProjFS.ReplacePlaceholderFileWithSymLink(
relativePath,
symLinkTarget,
updateFlags,
ref updateFailureCause);
Result result = this.DeleteFile(relativePath, updateFlags, out failureCause);
if (result != Result.Success)
{
return result;
}

failureCause = updateFailureCause;
return result;
*/
// TODO(Linux): try to handle races with hydration?
failureCause = UpdateFailureCause.NoFailure;
return Result.ENotYetImplemented;
return this.WriteSymLink(relativePath, symLinkTarget);
}

public virtual Result CompleteCommand(
Expand All @@ -214,6 +238,49 @@ public virtual Result ConvertDirectoryToPlaceholder(
throw new NotImplementedException();
}

private static Result RemoveFileOrDirectory(
string fullPath,
bool isDirectory)
{
try
{
if (isDirectory)
{
Directory.Delete(fullPath);
}
else
{
File.Delete(fullPath);
}
}
catch (IOException ex) when (ex is DirectoryNotFoundException)
{
return Result.EPathNotFound;
}
catch (IOException ex) when (ex is FileNotFoundException)
{
return Result.EFileNotFound;
}
catch (IOException ex) when (ex.HResult == Errno.Constants.ENOTEMPTY)
{
return Result.EDirectoryNotEmpty;
}
catch (IOException)
{
return Result.EIOError;
}
catch (UnauthorizedAccessException)
{
return Result.EAccessDenied;
}
catch
{
return Result.Invalid;
}

return Result.Success;
}

private static string GetProcCmdline(int pid)
{
try
Expand Down
5 changes: 5 additions & 0 deletions ProjFS.Linux/Scripts/Build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ PROJFS=$SRCDIR/ProjFS.Linux
echo "Generating ProjFS.Linux constants..."
"$SCRIPTDIR"/GenerateConstants.sh || 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 "Restoring and building ProjFS.Linux packages..."
dotnet restore $PROJFS/PrjFSLib.Linux.Managed/PrjFSLib.Linux.Managed.csproj /p:Configuration=$CONFIGURATION /p:Platform=x64 --packages $PACKAGES || exit 1
dotnet build $PROJFS/PrjFSLib.Linux.Managed/PrjFSLib.Linux.Managed.csproj /p:Configuration=$CONFIGURATION /p:Platform=x64 || exit 1
4 changes: 4 additions & 0 deletions ProjFS.Linux/Scripts/GenerateConstants.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ INTEROP_DIR="$SCRIPTDIR/../PrjFSLib.Linux.Managed/Interop"

CC=${CC:-cc}

for includedir in '/usr/local/include/projfs' '/usr/include/projfs'; do
CPPFLAGS="${CPPFLAGS:+$CPPFLAGS }-I$includedir"
done

TMPFILE=$(mktemp -t vfsforgit.tmp.XXXXXX) || exit 1
trap "rm -f -- '$TMPFILE'" EXIT

Expand Down