Managing Entry IDs in the Wrapped PST

Update – this is now part of the Outlook MAPI Code Samples.

[This is now documented here: https://msdn.microsoft.com/en-us/library/bb905271.aspx, https://msdn.microsoft.com/en-us/library/bb821132.aspx]

Sam Khavari of Zimbra asked me if I had noticed that the Wrapped PST sample crashes when you turn on the preview pane. After a bit of wrangling over the repro, we found that it will crash if you preview one of the first handful of messages created in the store. Since I had been focused on the replication API, and my sample implementation of the replication API created many items in many different folders, every time I had tested it, those first few, dangerous items were in folders such as Drafts, which don't have a preview pane. So no, I hadn't noticed it, but since they pointed it out I've got to fix it. :)

A bit of debugging showed that Outlook had somehow gotten a hold of a folder object instead of a message and was trying to preview it, leading to much hilarity. Further debugging showed that the reason it had the folder object was because the CompareEntryIDs function was insisting that the entry ID for the folder and the entry ID for the message were trying to open referred to the same object. At this point I threw up my hands and went to the guys that wrote the PST to begin with.

They pointed out that my handling of the PR_OST_OLFI property in SetOLFIInOST was botched. They agreed to let me document more about the OLFI structure so we can handle this property correctly. Here goes:

Original SetOLFIInOST:

Let's review what we used to do in this function. We built an OLFI structure with dwAlloc set to 0x7FFFFFFF and everything else nulled out, then we write this to PR_OST_OLFI. We do this every time SetOLFInOST is called, regardless of what may have already been in the property. To understand why this almost worked and why it's such a bad idea, we have to discuss what the OLFI structure is for.

What is OLFI?:

The Offline File Info (OLFI) structure is used by the PST provider for allocating entry IDs in offline mode. Interestingly, it is not used by the PST when it is not being wrapped, so only a PST wrapper needs to worry about this. Here's the revised OLFI structure:

 // OffLineFileInfo
typedef struct {
 ULONG    ulVersion;     // Structure version
    MAPIUID  muidReserved;
  ULONG    ulReserved;
    DWORD    dwAlloc;       // Number of primary source keys
    DWORD    dwNextAlloc;
   LTID     ltidAlloc;
 LTID     ltidNextAlloc;
} OLFI, *POLFI;

Every time the PST needs an entry ID for a new object (message or folder) it needs two pieces of information from the wrapper, a GUID and an index. These are combined to make the ID. The OLFI structure is the mechanism for tracking GUIDs and indexes which have been handed out. The PST reserves entry IDs in blocks. To reserve a block of entry IDs, it consults the ltidAlloc structure to get the current GUID and index. It decrements dwAlloc by the number of entries being allocated and writes the next index back into ltidAlloc. It will continue to do this as long as dwAlloc is larger than the block size it's trying to reserve.

When dwAlloc is smaller than the block size, the PST copies ltidNextAlloc and dwNextAlloc over to ltidAlloc and dwAlloc respectively. It then nulls out ltidNextAlloc and dwNextAlloc. The wrapper is expected to periodically check ltidNextAlloc to see if it's NULL and if it is populate it with a new guid and reset dwNextAlloc.

If dwAlloc is still too small, we fail. Otherwise, we've updated dwAlloc and ltidAlloc, so we write the structure back to PR_OST_OLFI so we can check it next time.

What Almost Worked:

By initializing dwAlloc to 0x7fffffff, we guaranteed that many reservations of EntryIDs should work. We didn't set a guid, but that's not what killed us. Remember that we used to rewrite the property whenever SetOLFInOST is called? Turns out we're calling it from Logon, which can be called multiple times! So when the PST makes its second batch of reservations, it gets the same block as the first batch. The net effect is that some messages end up getting entry IDs that compare as equivalent to some of the default folders. As long as we never try to CompareEntryIDs two of these conflicting entry IDs we won't see a problem. But as soon as we do, we're toast.

Better SetOLFIInOST:

First, we need to read the current PR_OLFI_OST and see what's in it before we go writing anything. Blindly restamping dwAlloc is a recipe for failure. Second, we need to write guids into ltidAlloc and ltidAllocNext so our PST has a chance of working after dwAlloc is eventually exhausted. Finally, we need to periodically call SetOLFIInOST to ensure that we restamp ltidNextAlloc before it's too late.

New code:

I've shared the updated code out here:

https://stephengriffin.members.winisp.net/wrappst/wrappst.zip

Update: 10/05/06 - 10:44AM Sam let me know it's OK to give his name