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: Add PreDelete Notification #181

Merged

Conversation

wilbaker
Copy link
Member

@wilbaker wilbaker commented Aug 16, 2018

Part of the work required for #152.

Changes for PreDelete

  • Added MessageType_KtoU_NotifyFilePreDelete and MessageType_KtoU_NotifyDirectoryPreDelete (kernel->user) notifications
  • Updated VFSForGit's user mode code to handle the PreDelete notification
  • Added new Categories.Mac.M2TODO functional test categories for tests that are required for M2 but not yet passing
  • Enabled additional functional tests for file\folder deletes
  • Added PreDelete handling to MirrorProvider

Unrelated Fixes\Changes

  • Updated Message_Init to properly set the pid and procname in the MessageHeader
  • Updated ParseMessageMemory to set the path to empty string rather than null (when the message from the kernel has a null path)
  • Made log formatting more consistent in MirrorProvider
  • Updated Kext project to treat warnings as errors

Mac functional test run with these changes

Test Run Summary
  Overall result: Warning
  Test Count: 156, Passed: 154, Failed: 0, Warnings: 0, Inconclusive: 0, Skipped: 2
    Skipped Tests - Ignored: 2, Explicit: 0, Other: 0
  Start time: 2018-08-17 15:55:50Z
    End time: 2018-08-17 15:59:53Z
    Duration: 242.238 seconds

@@ -196,6 +196,7 @@
TargetAttributes = {
C6C780AF207FC67200E7E054 = {
CreatedOnToolsVersion = 9.3;
ProvisioningStyle = Automatic;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sanoursa @nickgra any ideas what this setting does? At some point xcode decided to add it to the project.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was going to ask you the same thing :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you update your XCode version lately? Apple is reworking some things with signing and provisioning profiles, so this is likely related. We should just take the change now (like we do when VS insists on changing a project metadata file), no point in fighting with the IDE.

Copy link
Contributor

@sanoursa sanoursa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes are looking good. I'll try to give this a test on my machine as well, though you don't need to block on that if I don't get to it in time.

@@ -233,7 +239,14 @@ public override void DeleteFile_FileShouldNotBeFound(string path)

public override void DeleteFile_AccessShouldBeDenied(string path)
{
this.DeleteFile(path).ShouldContain(permissionDeniedMessage);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we start getting more of these, we could create a WindowsBashRunner and a MacBashRunner. This seems fine for now though.

this.DeleteFile(path).ShouldContain(permissionDeniedMessage);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
this.DeleteFile(path).ShouldContain(permissionDeniedMessage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, don't bother checking the platform, and just make this ShouldContainOneOf(both messages)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not relax the check when we're running the tests on Windows. I like the idea of separate Windows\Mac BashRunners, but I also agree at this point we probably don't need both.

@@ -51,11 +54,12 @@ public void CreateFileInFolderTest()

this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete.");

GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName + "/" + Environment.NewLine);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming ModifiedPathsShouldContain to ModifiedPathsShouldContainLine, and move the newline inside the method. That way every caller doesn't have to know this detaill

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that idea, that will also allow for making ModifiedPathsNewLine private (as mentioned in a separate comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will move the newline inside the method but leave the method named ModifiedPathsShouldContain.

I found ModifiedPathsShouldContainLine looked a bit off as the caller is not passing in lines, it's passing in the paths that ``ModifiedPaths` should (or should not) have.

@@ -153,11 +158,13 @@ public void ReadingFileDoesNotUpdateIndexOrSparseCheckout()
afterUpdateResult.Output.StartsWith("S ").ShouldEqual(true);
afterUpdateResult.Output.ShouldContain("ctime: 0:0", "mtime: 0:0", "size: 0\t");

GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, gitFileToCheck + Environment.NewLine);
GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, gitFileToCheck + GVFSHelpers.ModifiedPathsNewLine);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for ShouldNotContainLine

@@ -139,7 +145,7 @@ public void GitStatusAfterUnstage()
public void GitStatusAfterFileDelete()
{
string existingFilename = "test.cs";
this.Enlistment.GetVirtualPathTo(existingFilename).ShouldBeAFile(this.fileSystem);
this.EnsureTestFileExists(existingFilename);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you need to change the behavior here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately all of the tests in this class were written to be completely dependent on all of the tests running in order.

GitStatusAfterFileDelete was assuming that all prior tests ran, and one of those tests creates "test.cs". Because we can't run all of the tests just yet there is currently no "test.cs" at the point this test runs.

With this change you can run this individual test without having to run the tests listed above it.


// This delete could be part of a rename, and so we must hydrate the file now to
// ensure it's hydrated post-rename
if (kauthResult == KAUTH_RESULT_DEFER &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could the result currently be anything but defer?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With VFSForGit it will always be defer, but if a provider decided to block the delete of an empty placeholder it could be DENY.

(The idea here was to only hydrate if the delete is going to be allowed, let me know what you think)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See related comment, I'll refactor this code to avoid duplication (and to hydrate first)

if (kauthResult == KAUTH_RESULT_DEFER &&
FileFlagsBitIsSet(currentVnodeFileFlags, FileFlags_IsEmpty))
{
if (!TrySendRequestAndWaitForResponse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid duplicating this logic for hydrating a file? It seems like KAUTH_VNODE_DELETE should just be another action that can trigger a hydration in the existing code path. Then after that, we could check for KAUTH_VNODE_DELETE again and trigger the pre-delete notification.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could move the PreDelete notification code down and avoid duplicating the logic. It's a trade-off between avoiding duplicating the logic and preventing an unnecessary hydration if a provider blocks the delete.

However, blocking the delete of an unhydrated would be quite rare (and it's just a perf tradeoff), and so I'll go with your suggestion as I'd rather keep the kext simpler at this point.

if (ActionBitIsSet(
if (ActionBitIsSet(action, KAUTH_VNODE_DELETE))
{
if (!TrySendRequestAndWaitForResponse(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I'm curious, why are you triggering pre-delete before hydration? Does the ordering make any difference?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the ordering make any difference?

If the provider decides to block the delete it avoids an unnecessary hydration (I don't think VFSForGit would ever do that though ...)

Assuming the provider allows the delete, I don't think the ordering would make a difference. You could argue that the provider wouldn't get the PreDelete notification if the hydration were to fail, but I'm not sure that's enough of a reason to re-order these.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See related comment, I'll refactor this code to avoid duplication (and to hydrate first)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: For now I've removed all of the "hydrate on delete" code. Interestingly, it seems like we still get some kauth request that hydrates the file before moving. I'll test more in this area when adding support for rename notifications.

// TODO(Mac): Determine the overhead of making this check for all actions and
// vnodes. If it's too high, this check can be further reduced to the scenarios
// that VFSForGit care about.
if (!HasParentInVirtualizationRoot(vnode, context))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you need this change in behaivor?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this change VFSForGit would not get PreDelete notifications for the index file as it does not have the FileFlags_IsInVirtualizationRoot flag set.

In general the FileFlags_IsInVirtualizationRoot will only be set for files inside the root that originated as placeholders. It will not be set for newly created files or files that were renamed into the root.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And I decided to walk up the tree looking for the flag as we still want to get out the way as fast as we can for files we don't care about, and so I didn't want to add the overhead of looking up the VirtualizationRoot* here)

Copy link
Contributor

@sanoursa sanoursa Aug 16, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the prototype, we detected that a new file was created, and set the FileFlags_IsInVirtualizationRoot bit on it. The point of that flag is to give you one spot to check for this optimization. Though I think @pmj might have some plans to get rid of that flag and just do a vnode lookup instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the prototype, we detected that a new file was created, and set the FileFlags_IsInVirtualizationRoot bit on it. The point of that flag is to give you one spot to check for this optimization.

Got it that makes sense.

It sounds like we will need this change for handling when new files are created, and at that time we can add the FileFlags_IsInVirtualizationRoot flag and eliminate this check for other notifications\events (like PreDelete).

The other complication is that the index file is created before the virtualization root is active. If we want to continue to block deletes of the index we'll either need to be OK with only having this protection for index files created after the initial index, or we'll need a way for the clone verb to stick this flag on the index after it's created.

My plan is to pick up new file notifications next. Let me know if you'd like me to combine those changes with these changes, or if you're OK with me merging these changes in first and then making the changes discussed above in the next PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If doing that work avoids the need for workarounds that we're going to immediately throw away, it may be worth combining them.

//
// TODO(Mac): Double check that rules are properly enforced when filedFlaggedAsInVirtualizationRoot
// is false. These checks are currently skipped when filedFlaggedAsInVirtualizationRoot is false to
// allow providers to create new files inside of their root before starting the virtualization
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to allow that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem we have right now is that gvfs mount will always attempt to copy in the hooks.

This code didn't cause a problem before because prior to my change above (to proceed even when FileFlags_IsInVirtualizationRoot is not set on the file), the kext would bail out before here because neither the .git folder nor the hooks file have the flag set.

I think that once we have the all of the FileOp\Vnode event handling wired up we'll need to revisit how to handle these offline scenarios.

I've filed a follow up issue (#182) for this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See related comment, this might not be an issue after new file handling is added (and FileFlags_IsInVirtualizationRootis stamped on new files)

@@ -284,10 +296,56 @@ static int HandleVnodeOperation(
}
}
}
else if (ActionBitIsSet(action, KAUTH_VNODE_DELETE))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more question - do we need to trigger a recursive hydration of this directory? What's the current behavior if you move a virtual directory? What if you move a directory that is non-empty but has virtual items nested deep within it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this investigation to the PR that adds support for the rename notification.

@@ -196,6 +196,7 @@
TargetAttributes = {
C6C780AF207FC67200E7E054 = {
CreatedOnToolsVersion = 9.3;
ProvisioningStyle = Automatic;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you update your XCode version lately? Apple is reworking some things with signing and provisioning profiles, so this is likely related. We should just take the change now (like we do when VS insists on changing a project metadata file), no point in fighting with the IDE.

@wilbaker
Copy link
Member Author

@nickgra @pmj, after seeing the feedback from @sanoursa (and how things can get cleaned up with handling new file notifications) I've decided to make the changes for new file notifications in this PR as well.

Feel free to wait until those changes are ready before reviewing.

Thanks!

@wilbaker wilbaker changed the title Mac: Add PreDelete Notification [WIP] Mac: Add PreDelete and new file notifications Aug 16, 2018
@wilbaker wilbaker requested review from nickgra and removed request for nickgra August 16, 2018 23:49
Copy link
Member

@nickgra nickgra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Waiting on your proposed changes.

@wilbaker wilbaker changed the title [WIP] Mac: Add PreDelete and new file notifications Mac: Add PreDelete Notification Aug 17, 2018
@wilbaker
Copy link
Member Author

I've decided to make the changes for new file notifications in this PR as well.

@sanoursa @nickgra @pmj, after looking into what's needed for new file notifications, I've come to the conclusion that it would be cleaner to leave those changes out of this PR. Rather than adding new file notifications, I'll update this by removing all of the code that's not strictly needed to get basic PreDelete notifications working (e.g. hydrating on delete, delete notifications for renamed files, etc.)

In the next round of PRs (when adding new file and rename notifications), I can add back support for the more complicated scenarios without the need for the work around currently in this PR.

I'll post another message to this PR when it's ready for another round of review.

Thanks!

@wilbaker
Copy link
Member Author

after looking into what's needed for new file notifications, I've come to the conclusion that it would be cleaner to leave those changes out of this PR. Rather than adding new file notifications, I'll update this by removing all of the code that's not strictly needed to get basic PreDelete notifications working (e.g. hydrating on delete, delete notifications for renamed files, etc.)

@sanoursa @nickgra @pmj, this cleanup as been completed and the PR is ready for another look.

@@ -284,10 +296,38 @@ static int HandleVnodeOperation(
}
}
}
else if (ActionBitIsSet(action, KAUTH_VNODE_DELETE))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the DELETE bit always be exclusive of all the bits we checked for above? You may need to drop this else to avoid missing events.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I'll remove the elses

goto CleanupAndReturn;
}
}
else if (ActionBitIsSet(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, probably want to drop the else

Copy link
Member

@nickgra nickgra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@wilbaker wilbaker merged commit 6fa5dfe into microsoft:master Aug 17, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants