microsoft / ProjFS-Managed-API

A managed-code API for the Windows Projected File System
Other
142 stars 34 forks source link

HResult.Handle result code from WriteFileData() and left a hydrated placeholder in a bad state? #59

Closed FeralSquid closed 3 years ago

FeralSquid commented 3 years ago

Hello,

We had an odd situation today where one user was unable to launch one of their executables with a "The application was unable to start correctly (0xc0000005)" error.

In my logs I found that we got an HResult.Handle back from VirtualizationInstance.WriteFileData() during a GetFileDataCallback(). We returned HResult.InternalError as the result of the callback.

The user, though, still had this file as a hydrated placeholder in this bad/incomplete state on their drive which required manual intervention to remove and fix (calling VirtualizationInstance.DeleteFile was enough, the next hydration attempt succeeded as normal).

So my questions are:

  1. What would cause the HResult.Handle result code from WriteFileData()? Something have a non-shareWrite handle open to it or?

  2. Is there a different GetFileDataCallback() result code I should be returning to prevent that corrupt hydrated placeholder from being written / staying in that bad state? Or something else I can do from within the callback to handle this better? Or do I need to schedule a VirtualizationInstance.DeleteFile to occur in another thread shortly after the callback completes so it at least doesn't stay in a bad state?

This user oddly hit this same issue on 2 files that were likely being fetched at the same time, but among many others before/during/after that all worked fine.

Thanks!

cgallred commented 3 years ago
  1. I think the most likely cause of HResult.Handle from WriteFileData() is if the provider used a dataStreamId value that the driver didn't recognize.

    When the driver sends a GetFileData request it assigns an ID to the data stream it wants content for and sticks it in a table that maps the ID to a file stream. Then it gives the provider that ID in the dataStreamId parameter of IRequiredCallbacks.GetFileDataCallback(). When the driver gets a WriteFileData() from the provider it looks in the table for the ID the provider gave so it knows what file stream should get the bytes. If the given ID isn't in the table the driver returns STATUS_INVALID_HANDLE, which the provider sees as HResult.Handle.

  2. A callback can return any error it gets from a ProjFS API. See the bottom couple lines in this section of documentation. So you can just pass the error from WriteFileData() through.

    That said, it is strange that the placeholder (which was already created during your GetPlaceholderInfoCallback()) would have ended up in the hydrated placeholder state. I'd expect it to still be in the placeholder (non-hydrated) state. The driver doesn't do the state transition until after the GetFileData successfully completes. Any failure should have left the placeholder alone.

BTW, ProjFS doesn't have to worry about somebody having a no-share-write handle open during hydration. Sharing modes only affect creation of a new handle. WriteFileData() sends the driver the file data in a filter communication port message, then the filter writes the data using the already-opened handle.

FeralSquid commented 3 years ago

Thanks for the info and links.

This is still the only instance I've seen of anything like this, so we'll see if it happens again. One other potentially interesting piece of info is that their virtual root is on a USB hdd ... and we've had all kinds of random corrupt file issues with them that appear to be related to data not being flushed to the disk before various events happen (hibernation, usb cable jostled, power loss, etc) ... so perhaps something like that happened here...