Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mac: Update MirrorProvider to support symbolic links #325

Merged
merged 1 commit into from
Oct 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}