Skip to content

Hardlink notifications on Mac & Windows and rename notifications on Mac#203

Merged
wilbaker merged 10 commits intomicrosoft:masterfrom
wilbaker:users/wilbaker/hardlink_and_mac_rename_notifications
Aug 23, 2018
Merged

Hardlink notifications on Mac & Windows and rename notifications on Mac#203
wilbaker merged 10 commits intomicrosoft:masterfrom
wilbaker:users/wilbaker/hardlink_and_mac_rename_notifications

Conversation

@wilbaker
Copy link
Member

@wilbaker wilbaker commented Aug 22, 2018

Part of the work for #152 and fixes #200.

Summary of changes

  • Added hardlink notifications to VFSForGit for Mac and Windows
  • Added new hardlink functional tests
  • Added rename notifications on Mac
  • Enabled additional functional tests on Mac

Future work

Unlike VFSForGit on Windows, on Mac users can rename\move folders have not been expanded. To ensure that all placeholders are on disk prior to the rename the folder must be expanded in the PreDelete notification (#202)

Functional test run (on Mac) with these changes:

Test Run Summary
  Overall result: Warning
  Test Count: 206, Passed: 204, Failed: 0, Warnings: 0, Inconclusive: 0, Skipped: 2
    Skipped Tests - Ignored: 2, Explicit: 0, Other: 0
  Start time: 2018-08-22 22:09:59Z
    End time: 2018-08-22 22:14:22Z
    Duration: 263.482 seconds

@wilbaker wilbaker requested review from nickgra and sanoursa August 22, 2018 17:00
@wilbaker wilbaker changed the title Users/wilbaker/hardlink and mac rename notifications Hardlink notifications on Mac\Windows and rename notifications on Mac Aug 22, 2018
@wilbaker wilbaker changed the title Hardlink notifications on Mac\Windows and rename notifications on Mac Hardlink notifications on Mac & Windows and rename notifications on Mac Aug 22, 2018
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.

Just a quick first pass to make sure I understand the overall logic. I still need to spend a little more time with it, and test it out as well.


public override void CreateHardLink(string targetPath, string newLinkPath)
{
Assert.Fail($"{nameof(PowerShellRunner)} does not support {nameof(this.CreateHardLink)}");
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: move this to the base class?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure, I'll move a default SupportsHardlinkCreation to the base as well

string relativePath,
string destinationPath)
{
this.OnHardLinkCreated(relativePath, destinationPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these parameters ordered properly? The first parameter is ambiguous: relativePath could map to either relativeTargetPath or relativeNewLinkPath. But the second parameter sure looks like it maps better to relativeTargetPath, which makes me think these are swapped?

Copy link
Member Author

Choose a reason for hiding this comment

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

These are ordered correctly.

I agree the naming could be better (and I could change these names), but as a start I went with the approach of matching the parameter names declared in the ProjFS Managed API:

    /// <param name="relativePath">The path, relative to the virtualization root, of the file for
    ///     which the hard link was created.
    ///     <para>This parameter will be "" to indicate that the hard link name was created inside
    ///     the virtualization instance, i.e. a new hard link was created inside the virtualization
    ///     instance to a file that exists outside the virtualization instance.</para>
    /// </param>
    /// <param name="destinationPath">The path, relative to the virtualization root, of the new hard
    ///     link name.
    ///     <para>This parameter will be "" to indicate that the hard link name was created
    ///     outside the virtualization instance, i.e. a new hard link was created outside the
    ///     virtualization instance for a file that exists inside the virtualization instance.</para>
    /// </param>

Let me know if you think it's better to match the API names or use our own names.

}
}

protected void OnHardLinkCreated(string relativeTargetPath, string relativeNewLinkPath)
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 even need the relativeTargetPath parameter? It doesn't seem to be used at all.

Copy link
Contributor

Choose a reason for hiding this comment

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

And why would it always be relative? Couldn't you create a hardlink inside your repo to some path outside of it? It seems like the target would need to be treated as an absolute path.

Copy link
Member Author

Choose a reason for hiding this comment

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

Why do we even need the relativeTargetPath parameter? It doesn't seem to be used at all.

It's not strictly needed, but we do log it if there's an exception:

metadata.Add(nameof(relativeTargetPath), relativeTargetPath);

My thought was that if something goes wrong it's helpful to have as much information as we can.

And why would it always be relative? Couldn't you create a hardlink inside your repo to some path outside of it? It seems like the target would need to be treated as an absolute path.

See related comment as well (for detailed descriptions of these paths), if either of the paths are outside of the repo they will be empty strings.

Console.WriteLine($"OnNewFileCreated (isDirectory: {isDirectory}): {relativePath}");
}

private void OnFileRenamed(string relativeDestinationPath, bool isDirectory)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems really odd to get a rename event with only a single path in it. I understand the reasons why, but it's confusing from an API perspective. Should we just treat this as a new file event instead, and just not have a rename event?

Copy link
Member Author

Choose a reason for hiding this comment

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

For historical purposes, it should be noted that we could deliver both paths on Mac, but because VFSForGit does not strictly need the source path on Mac we opted to keep the kext simpler and only deliver the destination path for now.

but it's confusing from an API perspective. Should we just treat this as a new file event instead, and just not have a rename event?

I've found it helpful when debugging\developing to keep these as separate events (and Keeping them as separate events also makes us more consistent with ProjFS on Windows).

I lean toward keeping them separate and having VFSForGit decide to handle this notification in the same way as a new file 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've added some additional comments in the notification callback

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've added some additional comments in the notification callback

Actually, comments were added in VFSForGit, not in the MirrorProvider

}

private void OnFileRenamed(
string relativePath,
Copy link
Contributor

Choose a reason for hiding this comment

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

relativeSourcePath?

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 other comment as well. Currently these names match the names used in the ProjFS API, let me know if you think it's better to keep them consistent or change them.

}

private void OnHardlinkCreated(
string relativePath,
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question as elsewhere - there's no guarantee this is relative, right?

Copy link
Contributor

Choose a reason for hiding this comment

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

Er, for one of these two parameters. I actually can't tell which one is the path of the link and which one is the path of the target :-)

Copy link
Member Author

Choose a reason for hiding this comment

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

Same question as elsewhere - there's no guarantee this is relative, right?

As discussed elsewhere, any paths outside of the root will be an empty string (by design)

I actually can't tell which one is the path of the link and which one is the path of the target :-)

Same comment here too, do you think we should use our own names or stay consistent with ProjFS?

static uint32_t ReadVNodeFileFlags(vnode_t vn, vfs_context_t context);
static inline bool FileFlagsBitIsSet(uint32_t fileFlags, uint32_t bit);
static inline bool FileIsFlaggedAsInRoot(vnode_t vnode, vfs_context_t context);
static inline bool ActionBitIsSet(kauth_action_t action, kauth_action_t mask);
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, why did you take the inlines back out?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can add them back.

I remembered while working on the code that the inline is only required on the definition and not the declaration (see: https://isocpp.org/wiki/faq/inline-functions#inline-nonmember-fns).

But it doesn't hurt anything to leave them (and makes it clearer that they are expected to be inline) so I can add them back in.


vtype vnodeType = vnode_vtype(currentVnode);
if (ShouldIgnoreVnodeType(vnodeType, currentVnode))
// TODO(Mac): Improve error handling
Copy link
Contributor

Choose a reason for hiding this comment

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

And more importantly, we just lost a notification here. We will need some pattern for dealing with 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.

Good point, I will add some more details to this comment

int kauthResult;
int kauthError;
if (!TrySendRequestAndWaitForResponse(
root,
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency with your other code, indent one more level 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.

👍

[TestCase, Order(2)]
public void CreateHardLinkTest()
{
if (!this.fileSystem.SupportsHardlinkCreation)
Copy link
Member Author

@wilbaker wilbaker Aug 22, 2018

Choose a reason for hiding this comment

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

This test is not getting any coverage on Mac as it's not using BashRunner

Copy link
Member Author

Choose a reason for hiding this comment

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

Test fixture updated to use all runners (whatever used to cause failures with BashRunner is no longer an issue)

[Category(Categories.Mac.M2)]
public void AddAndStageHardLinksTest()
{
if (!this.FileSystem.SupportsHardlinkCreation)
Copy link
Member Author

Choose a reason for hiding this comment

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

This is not adding any coverage is AddStageTests are only run with SystemIORunner

Copy link
Member Author

Choose a reason for hiding this comment

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

Updated CreateHardLink to always use BashRunner to create the hard link.

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 updated code looks great, and I confirmed that all the functional tests pass on my machine. I didn't get a chance to do any manual testing yet, but no need to block on that.

return;
}

this.CreateHardLink("Readme.md", "ReadmeLink.md");
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: in the IPlatformFileSystem interface up above, I saw that the link name was first and the target name was second. I think it would help us keep these straight if we keep a consistent ordering through all of our own code.

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 clean this up

// creation) so use a BashRunner instead.
this.FileSystem.ShouldBeOfType<SystemIORunner>();
this.FileSystem.SupportsHardlinkCreation.ShouldBeFalse(
"FileSystem *does* support hard link creation, CreateHardLink no longer needs to create a BashRunner");
Copy link
Contributor

Choose a reason for hiding this comment

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

This message is confusing. You're saying that if in the future it ends up supporting hardlinks, we can remove this code?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep that's right, let me try re-working this message.

@wilbaker wilbaker merged commit 950d6e6 into microsoft:master Aug 23, 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.

VFSForGit should listen to hardlink creation events and treat them the same as creating a new file

2 participants