Skip to content

Commit

Permalink
Mac: Update MirrorProvider to support symbolic links
Browse files Browse the repository at this point in the history
  • Loading branch information
wilbaker committed Oct 2, 2018
1 parent 067170c commit 47ad31a
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 40 deletions.
63 changes: 61 additions & 2 deletions MirrorProvider/MirrorProvider.Mac/MacFileSystemVirtualizer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using PrjFSLib.Mac;
using System;
using System.IO;

using System.Runtime.InteropServices;
using System.Text;

namespace MirrorProvider.Mac
{
public class MacFileSystemVirtualizer : FileSystemVirtualizer
Expand Down Expand Up @@ -58,7 +60,7 @@ private Result OnEnumerateDirectory(

foreach (ProjectedFileInfo child in this.GetChildItems(relativePath))
{
if (child.IsDirectory)
if (child.Type == ProjectedFileInfo.FileType.Directory)
{
Result result = this.virtualizationInstance.WritePlaceholderDirectory(
Path.Combine(relativePath, child.Name));
Expand All @@ -69,6 +71,28 @@ private Result OnEnumerateDirectory(
return result;
}
}
else if (child.Type == ProjectedFileInfo.FileType.SymLink)
{
string childRelativePath = Path.Combine(relativePath, child.Name);

string symLinkTarget;
if (this.TryGetSymLinkTarget(childRelativePath, out symLinkTarget))
{
Result result = this.virtualizationInstance.WriteSymLink(
childRelativePath,
symLinkTarget);

if (result != Result.Success)
{
Console.WriteLine($"WriteSymLink failed: {result}");
return result;
}
}
else
{
return Result.EIOError;
}
}
else
{
// The MirrorProvider marks every file as executable (mode 755), but this is just a shortcut to avoid the pain of
Expand Down Expand Up @@ -176,6 +200,35 @@ private void OnHardLinkCreated(string relativeNewLinkPath)
{
Console.WriteLine($"OnHardLinkCreated: {relativeNewLinkPath}");
}

private bool TryGetSymLinkTarget(string relativePath, out string symLinkTarget)
{
symLinkTarget = null;
string fullPathInMirror = this.GetFullPathInMirror(relativePath);

const ulong BufSize = 4096;
byte[] targetBuffer = new byte[BufSize];
long bytesRead = ReadLink(fullPathInMirror, targetBuffer, BufSize);
if (bytesRead < 0)
{
Console.WriteLine($"GetSymLinkTarget failed: {Marshal.GetLastWin32Error()}");
return false;
}

targetBuffer[bytesRead] = 0;
symLinkTarget = Encoding.UTF8.GetString(targetBuffer);

if (symLinkTarget.StartsWith(this.Enlistment.MirrorRoot, StringComparison.OrdinalIgnoreCase))
{
// Link target is an absolute path inside the MirrorRoot.
// The target needs to be adjusted to point inside the src root
symLinkTarget = Path.Combine(
this.Enlistment.SrcRoot.TrimEnd(Path.DirectorySeparatorChar),
symLinkTarget.Substring(this.Enlistment.MirrorRoot.Length).TrimStart(Path.DirectorySeparatorChar));
}

return true;
}

private static byte[] ToVersionIdByteArray(byte version)
{
Expand All @@ -184,5 +237,11 @@ private static byte[] ToVersionIdByteArray(byte version)

return bytes;
}

[DllImport("libc", EntryPoint = "readlink", SetLastError = true)]
private static extern long ReadLink(
string path,
byte[] buf,
ulong bufsize);
}
}
117 changes: 82 additions & 35 deletions MirrorProvider/MirrorProvider/FileSystemVirtualizer.cs
Original file line number Diff line number Diff line change
@@ -1,81 +1,104 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using System.Linq;

namespace MirrorProvider
{
public abstract class FileSystemVirtualizer
{
private Enlistment enlistment;
protected Enlistment Enlistment { get; private set; }

public abstract bool TryConvertVirtualizationRoot(string directory, out string error);
public virtual bool TryStartVirtualizationInstance(Enlistment enlistment, out string error)
{
this.enlistment = enlistment;
this.Enlistment = enlistment;
error = null;
return true;
}

protected string GetFullPathInMirror(string relativePath)
{
return Path.Combine(this.Enlistment.MirrorRoot, relativePath);
}

protected bool DirectoryExists(string relativePath)
{
string fullPathInMirror = Path.Combine(this.enlistment.MirrorRoot, relativePath);
string fullPathInMirror = this.GetFullPathInMirror(relativePath);
DirectoryInfo dirInfo = new DirectoryInfo(fullPathInMirror);

return dirInfo.Exists;
}

protected bool FileExists(string relativePath)
{
string fullPathInMirror = Path.Combine(this.enlistment.MirrorRoot, relativePath);
string fullPathInMirror = this.GetFullPathInMirror(relativePath);
FileInfo fileInfo = new FileInfo(fullPathInMirror);

return fileInfo.Exists;
}

protected ProjectedFileInfo GetFileInfo(string relativePath)
{
string fullPathInMirror = Path.Combine(this.enlistment.MirrorRoot, relativePath);
string fullPathInMirror = this.GetFullPathInMirror(relativePath);
string fullParentPath = Path.GetDirectoryName(fullPathInMirror);
string fileName = Path.GetFileName(relativePath);

string actualCaseName;
if (this.DirectoryExists(fullParentPath, fileName, out actualCaseName))
ProjectedFileInfo.FileType type;
if (this.FileOrDirectoryExists(fullParentPath, fileName, out actualCaseName, out type))
{
return new ProjectedFileInfo(actualCaseName, size: 0, isDirectory: true);
}
else if (this.FileExists(fullParentPath, fileName, out actualCaseName))
{
return new ProjectedFileInfo(actualCaseName, size: new FileInfo(fullPathInMirror).Length, isDirectory: false);
return new ProjectedFileInfo(
actualCaseName,
size: (type == ProjectedFileInfo.FileType.File) ? new FileInfo(fullPathInMirror).Length : 0,
type: type);
}

return null;
}

protected IEnumerable<ProjectedFileInfo> GetChildItems(string relativePath)
{
string fullPathInMirror = Path.Combine(this.enlistment.MirrorRoot, relativePath);
string fullPathInMirror = this.GetFullPathInMirror(relativePath);
DirectoryInfo dirInfo = new DirectoryInfo(fullPathInMirror);

if (!dirInfo.Exists)
{
yield break;
}

foreach (FileInfo file in dirInfo.GetFiles())
foreach (FileSystemInfo fileSystemInfo in dirInfo.GetFileSystemInfos())
{
yield return new ProjectedFileInfo(file.Name, file.Length, isDirectory: false);
}
if ((fileSystemInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
// While not 100% accurate on all platforms, for simplicity assume that if the the file has reparse data it's a symlink
yield return new ProjectedFileInfo(
fileSystemInfo.Name,
size: 0,
type: ProjectedFileInfo.FileType.SymLink);
}
else if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
yield return new ProjectedFileInfo(
fileSystemInfo.Name,
size: 0,
type: ProjectedFileInfo.FileType.Directory);
}
else
{
FileInfo fileInfo = fileSystemInfo as FileInfo;
yield return new ProjectedFileInfo(
fileInfo.Name,
fileInfo.Length,
ProjectedFileInfo.FileType.File);
}

foreach (DirectoryInfo subDirectory in dirInfo.GetDirectories())
{
yield return new ProjectedFileInfo(subDirectory.Name, size: 0, isDirectory: true);
}
}

protected FileSystemResult HydrateFile(string relativePath, int bufferSize, Func<byte[], uint, bool> tryWriteBytes)
{
string fullPathInMirror = Path.Combine(this.enlistment.MirrorRoot, relativePath);
string fullPathInMirror = this.GetFullPathInMirror(relativePath);
if (!File.Exists(fullPathInMirror))
{
return FileSystemResult.EFileNotFound;
Expand Down Expand Up @@ -106,23 +129,47 @@ protected FileSystemResult HydrateFile(string relativePath, int bufferSize, Func
return FileSystemResult.Success;
}

private bool DirectoryExists(string fullParentPath, string directoryName, out string actualCaseName)
private bool FileOrDirectoryExists(
string fullParentPath,
string fileName,
out string actualCaseName,
out ProjectedFileInfo.FileType type)
{
return this.NameExists(Directory.GetDirectories(fullParentPath), directoryName, out actualCaseName);
}
actualCaseName = null;
type = ProjectedFileInfo.FileType.Invalid;

private bool FileExists(string fullParentPath, string fileName, out string actualCaseName)
{
return this.NameExists(Directory.GetFiles(fullParentPath), fileName, out actualCaseName);
}
DirectoryInfo dirInfo = new DirectoryInfo(fullParentPath);
if (!dirInfo.Exists)
{
return false;
}

private bool NameExists(IEnumerable<string> paths, string name, out string actualCaseName)
{
actualCaseName =
paths
.Select(path => Path.GetFileName(path))
.FirstOrDefault(actualName => actualName.Equals(name, StringComparison.OrdinalIgnoreCase));
return actualCaseName != null;
FileSystemInfo fileSystemInfo =
dirInfo
.GetFileSystemInfos()
.FirstOrDefault(fsInfo => fsInfo.Name.Equals(fileName, StringComparison.OrdinalIgnoreCase));

if (fileSystemInfo == null)
{
return false;
}

actualCaseName = fileSystemInfo.Name;

if ((fileSystemInfo.Attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint)
{
type = ProjectedFileInfo.FileType.SymLink;
}
else if ((fileSystemInfo.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
type = ProjectedFileInfo.FileType.Directory;
}
else
{
type = ProjectedFileInfo.FileType.File;
}

return true;
}
}
}
17 changes: 14 additions & 3 deletions MirrorProvider/MirrorProvider/ProjectedFileInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@
{
public class ProjectedFileInfo
{
public ProjectedFileInfo(string name, long size, bool isDirectory)
public ProjectedFileInfo(string name, long size, FileType type)
{
this.Name = name;
this.Size = size;
this.IsDirectory = isDirectory;
this.Type = type;
}

public enum FileType
{
Invalid,

File,
Directory,
SymLink

}

public string Name { get; }
public long Size { get; }
public bool IsDirectory { get; }
public FileType Type { get; }
public bool IsDirectory => this.Type == FileType.Directory;
}
}

0 comments on commit 47ad31a

Please sign in to comment.