зеркало из https://github.com/github/VFSForGit.git
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:
Родитель
1b7dad44c4
Коммит
5c9708d065
|
@ -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:
|
||||
|
|
Загрузка…
Ссылка в новой задаче