add pre-rename and post-delete Linux events

The GVFS.Virtualization.FileSystemCallbacks background task
processing code is responsible for adding and removing paths
to the GVFS modified-paths database, and in the case of rename
file operations, needs to remove the old path from the database.

In the Mac Darwin/BSD kauth design, a rename file operation is
preceded by a KAUTH_VNODE_DELETE permission check, which the
ProjFS.Mac kext reports as a PreDelete notification.  This in
turn ensures that the FileSystemCallbacks
ExecuteBackgroundOperation() method will remove the source path
from the modified-paths database.  However, the ProjFS.Linux
and libprojfs implementation did not include a parallel pre-delete
event prior to rename file operations, leading to functional test
failures on Linux.

Because there is no necessity to follow the BSD design, we
can introduce true pre-rename (PROJFS_MOVE_PERM) permission
requests in Linux, as well as post-delete events.  This brings
the libprojfs/ProjFS.Linux design somewhat closer to the Windows
projectedfslib/PrjFlt one.

In particular, this allows the GVFS LinuxFileSystemVirtualizer
to enforce restrictions on renaming the .git/index file, akin
to the ones enforced on Windows.

By reporting both the source and target (destination) paths in
the post-rename events, we permit the FileSystemCallbacks
task processing code to add and remove the paths to the database,
without having to do so in either the pre-rename event or a
BSD-like pre-delete event delivered prior to a rename.
This commit is contained in:
Chris Darroch 2019-09-02 18:17:45 -07:00
Родитель 1b7dad44c4
Коммит 5c9708d065
6 изменённых файлов: 121 добавлений и 45 удалений

Просмотреть файл

@ -255,7 +255,9 @@ namespace GVFS.Platform.Linux
this.virtualizationInstance.OnLogInfo = this.OnLogInfo;
this.virtualizationInstance.OnFileModified = this.OnFileModified;
this.virtualizationInstance.OnPreDelete = this.OnPreDelete;
this.virtualizationInstance.OnPreRename = this.OnPreRename;
this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated;
this.virtualizationInstance.OnFileDeleted = this.OnFileDeleted;
this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed;
this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated;
this.virtualizationInstance.OnFilePreConvertToFull = this.NotifyFilePreConvertToFull;
@ -553,27 +555,19 @@ namespace GVFS.Platform.Linux
try
{
bool pathInsideDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath);
if (pathInsideDotGit)
if (pathInsideDotGit &&
relativePath.Equals(GVFSConstants.DotGit.Index, GVFSPlatform.Instance.Constants.PathComparison))
{
if (relativePath.Equals(GVFSConstants.DotGit.Index, GVFSPlatform.Instance.Constants.PathComparison))
string lockedGitCommand = this.Context.Repository.GVFSLock.GetLockedGitCommand();
if (string.IsNullOrEmpty(lockedGitCommand))
{
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);
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;
}
return Result.EAccessDenied;
}
this.OnDotGitFileOrFolderDeleted(relativePath);
}
else
{
this.OnWorkingDirectoryFileOrFolderDeleteNotification(relativePath, isDirectory, isPreDelete: true);
}
}
catch (Exception e)
@ -586,6 +580,38 @@ namespace GVFS.Platform.Linux
return Result.Success;
}
private Result OnPreRename(string relativePath, string relativeDestinationPath, bool isDirectory)
{
try
{
bool pathInsideDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath);
if (pathInsideDotGit &&
(relativePath.Equals(GVFSConstants.DotGit.Index, GVFSPlatform.Instance.Constants.PathComparison) ||
relativeDestinationPath.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 rename outside the lock");
this.Context.Tracer.RelatedEvent(EventLevel.Warning, $"{nameof(this.OnPreRename)}_BlockedIndexDelete", metadata);
return Result.EAccessDenied;
}
}
}
catch (Exception e)
{
EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
metadata.Add("destinationPath", relativeDestinationPath);
metadata.Add("isDirectory", isDirectory);
this.LogUnhandledExceptionAndExit(nameof(this.OnPreRename), metadata);
}
return Result.Success;
}
private void OnNewFileCreated(string relativePath, bool isDirectory)
{
try
@ -638,21 +664,26 @@ namespace GVFS.Platform.Linux
}
}
private void OnFileRenamed(string relativeDestinationPath, bool isDirectory)
private void OnFileDeleted(string relativePath, 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);
try
{
bool pathInsideDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath);
if (pathInsideDotGit)
{
this.OnDotGitFileOrFolderDeleted(relativePath);
}
else
{
this.OnWorkingDirectoryFileOrFolderDeleteNotification(relativePath, isDirectory, isPreDelete: false);
}
}
catch (Exception e)
{
EventMetadata metadata = this.CreateEventMetadata(relativePath, e);
metadata.Add("isDirectory", isDirectory);
this.LogUnhandledExceptionAndExit(nameof(this.OnFileDeleted), metadata);
}
}
private Result OnEnumerateDirectory(

Просмотреть файл

@ -33,7 +33,9 @@ namespace MirrorProvider.Linux
this.virtualizationInstance.OnLogInfo = this.OnLogInfo;
this.virtualizationInstance.OnFileModified = this.OnFileModified;
this.virtualizationInstance.OnPreDelete = this.OnPreDelete;
this.virtualizationInstance.OnPreRename = this.OnPreRename;
this.virtualizationInstance.OnNewFileCreated = this.OnNewFileCreated;
this.virtualizationInstance.OnFileDeleted = this.OnFileDeleted;
this.virtualizationInstance.OnFileRenamed = this.OnFileRenamed;
this.virtualizationInstance.OnHardLinkCreated = this.OnHardLinkCreated;
this.virtualizationInstance.OnFilePreConvertToFull = this.OnFilePreConvertToFull;
@ -226,19 +228,30 @@ namespace MirrorProvider.Linux
return Result.Success;
}
private Result OnPreRename(string relativePath, string relativeDestinationPath, bool isDirectory)
{
Console.WriteLine($"OnPreRename (isDirectory: {isDirectory}): {relativePath} destination: {relativeDestinationPath}");
return Result.Success;
}
private void OnNewFileCreated(string relativePath, bool isDirectory)
{
Console.WriteLine($"OnNewFileCreated (isDirectory: {isDirectory}): {relativePath}");
}
private void OnFileRenamed(string relativeDestinationPath, bool isDirectory)
private void OnFileDeleted(string relativePath, bool isDirectory)
{
Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}) destination: {relativeDestinationPath}");
Console.WriteLine($"OnFileDeleted (isDirectory: {isDirectory}): {relativePath}");
}
private void OnHardLinkCreated(string relativeNewLinkPath)
private void OnFileRenamed(string relativePath, string relativeDestinationPath, bool isDirectory)
{
Console.WriteLine($"OnHardLinkCreated: {relativeNewLinkPath}");
Console.WriteLine($"OnFileRenamed (isDirectory: {isDirectory}): {relativePath} destination: {relativeDestinationPath}");
}
private void OnHardLinkCreated(string relativeExistingFilePath, string relativeNewLinkPath)
{
Console.WriteLine($"OnHardLinkCreated: {relativeExistingFilePath} link: {relativeNewLinkPath}");
}
private Result OnFilePreConvertToFull(string relativePath)

Просмотреть файл

@ -32,6 +32,11 @@ namespace PrjFSLib.Linux
string relativePath,
bool isDirectory);
public delegate Result NotifyPreRenameEvent(
string relativePath,
string relativeDestinationPath,
bool isDirectory);
public delegate Result NotifyFilePreConvertToFullEvent(
string relativePath);
@ -40,11 +45,17 @@ namespace PrjFSLib.Linux
string relativePath,
bool isDirectory);
public delegate void NotifyFileDeletedEvent(
string relativePath,
bool isDirectory);
public delegate void NotifyFileRenamedEvent(
string relativePath,
string relativeDestinationPath,
bool isDirectory);
public delegate void NotifyHardLinkCreatedEvent(
string relativeExistingFilePath,
string relativeNewLinkPath);
public delegate void NotifyFileModified(

Просмотреть файл

@ -11,9 +11,11 @@ namespace PrjFSLib.Linux.Interop
public const ulong PROJFS_CLOSE_WRITE = 0x00000008;
public const ulong PROJFS_MOVE = 0x000000C0;
public const ulong PROJFS_CREATE = 0x00000100;
public const ulong PROJFS_DELETE = 0x00000200;
public const ulong PROJFS_OPEN_PERM = 0x00010000;
public const ulong PROJFS_ONDIR = 0x40000000;
public const ulong PROJFS_DELETE_PERM = 0x000100000000;
public const ulong PROJFS_DELETE_PERM = 0x000200000000;
public const ulong PROJFS_MOVE_PERM = 0x000400000000;
public const ulong PROJFS_ONLINK = 0x100000000000;
}
}

Просмотреть файл

@ -10,10 +10,12 @@ namespace PrjFSLib.Linux
None = 0x00000001,
NewFileCreated = 0x00000004,
PreDelete = 0x00000010,
PreRename = 0x00000020,
FileRenamed = 0x00000080,
HardLinkCreated = 0x00000100,
PreConvertToFull = 0x00001000,
FileModified = 0x10000002,
FileDeleted = 0x10000004,
}
}

Просмотреть файл

@ -35,7 +35,9 @@ namespace PrjFSLib.Linux
public virtual NotifyFileModified OnFileModified { get; set; }
public virtual NotifyFilePreConvertToFullEvent OnFilePreConvertToFull { get; set; }
public virtual NotifyPreDeleteEvent OnPreDelete { get; set; }
public virtual NotifyPreRenameEvent OnPreRename { get; set; }
public virtual NotifyNewFileCreatedEvent OnNewFileCreated { get; set; }
public virtual NotifyFileDeletedEvent OnFileDeleted { get; set; }
public virtual NotifyFileRenamedEvent OnFileRenamed { get; set; }
public virtual NotifyHardLinkCreatedEvent OnHardLinkCreated { get; set; }
@ -462,6 +464,10 @@ namespace PrjFSLib.Linux
{
nt = NotificationType.PreDelete;
}
else if ((ev.Mask & ProjFS.Constants.PROJFS_MOVE_PERM) != 0)
{
nt = NotificationType.PreRename;
}
else if ((ev.Mask & ProjFS.Constants.PROJFS_CLOSE_WRITE) != 0)
{
nt = NotificationType.FileModified;
@ -478,6 +484,10 @@ namespace PrjFSLib.Linux
{
nt = NotificationType.HardLinkCreated;
}
else if ((ev.Mask & ProjFS.Constants.PROJFS_DELETE) != 0)
{
nt = NotificationType.FileDeleted;
}
else if ((ev.Mask & ProjFS.Constants.PROJFS_OPEN_PERM) != 0)
{
nt = NotificationType.PreConvertToFull;
@ -488,20 +498,19 @@ namespace PrjFSLib.Linux
}
bool isDirectory = (ev.Mask & ProjFS.Constants.PROJFS_ONDIR) != 0;
string relativePath;
string relativePath = PtrToStringUTF8(ev.Path);
string relativeDestinationPath = null;
if (nt == NotificationType.FileRenamed ||
if (nt == NotificationType.PreRename ||
nt == NotificationType.FileRenamed ||
nt == NotificationType.HardLinkCreated)
{
relativePath = PtrToStringUTF8(ev.TargetPath);
}
else
{
relativePath = PtrToStringUTF8(ev.Path);
relativeDestinationPath = PtrToStringUTF8(ev.TargetPath);
}
Result result = this.OnNotifyOperation(
relativePath: relativePath,
relativeDestinationPath: relativeDestinationPath,
isDirectory: isDirectory,
notificationType: nt);
@ -534,6 +543,7 @@ namespace PrjFSLib.Linux
private Result OnNotifyOperation(
string relativePath,
string relativeDestinationPath,
bool isDirectory,
NotificationType notificationType)
{
@ -542,6 +552,9 @@ namespace PrjFSLib.Linux
case NotificationType.PreDelete:
return this.OnPreDelete(relativePath, isDirectory);
case NotificationType.PreRename:
return this.OnPreRename(relativePath, relativeDestinationPath, isDirectory);
case NotificationType.FileModified:
this.OnFileModified(relativePath);
return Result.Success;
@ -550,12 +563,16 @@ namespace PrjFSLib.Linux
this.OnNewFileCreated(relativePath, isDirectory);
return Result.Success;
case NotificationType.FileDeleted:
this.OnFileDeleted(relativePath, isDirectory);
return Result.Success;
case NotificationType.FileRenamed:
this.OnFileRenamed(relativePath, isDirectory);
this.OnFileRenamed(relativePath, relativeDestinationPath, isDirectory);
return Result.Success;
case NotificationType.HardLinkCreated:
this.OnHardLinkCreated(relativePath);
this.OnHardLinkCreated(relativePath, relativeDestinationPath);
return Result.Success;
case NotificationType.PreConvertToFull: