MSPNTFY.C
/* 
 *  M S P N T F Y . C 
 * 
 *  Code for doing internal cross-process notifications within the  
 *  Sample Message Store provider. 
 * 
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved. 
 */ 
 
#include "msp.h" 
#include <stdarg.h> 
 
/* INTERNAL Function prototypes. */ 
 
long STDAPICALLTYPE LSMSOQNotifCallback(LPVOID lpvContext, ULONG cNotif, 
    LPNOTIFICATION lpNotifs); 
static void EmptyTable(LPTABLEDATA lptbl, PLMR plmr); 
static HRESULT HrApplyOQNotifChanges(LPTABLEDATA lptbl, PONB ponbIn); 
static HRESULT HrGetOutgoingNotificationKey(PIMS pims, LPNOTIFKEY * lppKey); 
static HRESULT HrNotifyOnOutgoingQueue(PIMS pims, PEID peid, LPSRow prw, 
    ULONG ulTableEvent, FILETIME *pftBeforeUpdate); 
 
long STDAPICALLTYPE LSMSTblNotifCallback(LPVOID lpvContext, ULONG cNotif, 
    LPNOTIFICATION lpNotifs); 
HRESULT HrApplyTblNotifChanges(PIMS pims, PTNB ptnbIn); 
static HRESULT HrGetTableNotificationKey(PIMS pims, LPNOTIFKEY * lppKey); 
 
/* 
 *  EXTERNAL FUNCTIONS (called from outside this file). 
 */ 
 
/*  
 * HrCreateOGQueueMutex 
 * 
 *  Purpose 
 *      Create the outgoing queue mutex, and return it to the caller. 
 * 
 *  Arguments 
 *      phQMutex: Pointer to the location to return the new mutex. 
 * 
 *  Returns: 
 *      HRESULT: Will return an error only if the CreateMutex call fails. 
 */ 
HRESULT HrCreateOGQueueMutex(HANDLE *phQMutex) 
{ 
    HRESULT hr = hrSuccess; 
    HANDLE hMutex; 
    LPTSTR szMutexName = "SMS_OGQUEUEFILE_MUTEX"; 
 
    hMutex = CreateMutex(NULL, FALSE, szMutexName); 
 
    if (hMutex) 
        *phQMutex = hMutex; 
 
    #ifndef WIN16 
    else 
    { 
        TraceSz1("SampleMS: HrCreateOGQueueMutex: call to" 
            " CreateMutex failed (error %08lX)", GetLastError()); 
         
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
    } 
    #endif 
 
    DebugTraceResult(HrCreateOGQueueMutex, hr); 
    return hr; 
} 
 
/* 
 * HrSetupPrivateNotifications 
 * 
 *  Purpose 
 *      Setup two private channels via the MAPI notification engine to 
 *      tell other processes running against this store when 1) the  
 *      outgoing queue changes and 2) when contents and hierarchy tables 
 *      change. We communicate between the multiple client processes and 
 *      the one spooler process. For the outgoing queue, we use a key that 
 *      is the full pathname to the outgoing queue file. For the other 
 *      tables, we use a unique 16-byte ID. Remember the connections so 
 *      that we can Unsubscribe when we shutdown the store. 
 * 
 *  Arguments 
 *      pims: a pointer to the message store object. 
 * 
 *  Side Effects 
 *      Fills in the ulOQConn and ulTblConn members of pims. 
 * 
 *  Returns 
 *      HRESULT 
 */ 
HRESULT HrSetupPrivateNotifications(PIMS pims) 
{ 
    HRESULT hr; 
    LPMAPIADVISESINK lpAdvise; 
    ULONG ulOQConn = 0; 
    ULONG ulTblConn = 0; 
    LPNOTIFKEY lpKey = NULL; 
 
    /* Use the MAPI notification engine to tell myself */ 
    /* (across processes) when to update outgoing queue. */ 
 
    /* The key is the path to the disk cache of the outbound queue */ 
    hr = HrGetOutgoingNotificationKey(pims, &lpKey); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = HrAllocAdviseSink(&LSMSOQNotifCallback, (LPVOID) pims, &lpAdvise); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = pims->psup->lpVtbl->Subscribe(pims->psup, lpKey, 
        fnevExtended, 0L, lpAdvise, &ulOQConn); 
 
    /* Always release; mapi will have addref'ed it during Subscribe */ 
    UlRelease(lpAdvise); 
 
    if (hr != hrSuccess) 
        goto exit; 
 
    FreeNull(lpKey); 
    lpKey = NULL; 
 
    /* Now, setup notifications for the other tables. */ 
 
    hr = HrGetTableNotificationKey(pims, &lpKey); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = HrAllocAdviseSink(&LSMSTblNotifCallback, (LPVOID) pims, &lpAdvise); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = pims->psup->lpVtbl->Subscribe(pims->psup, lpKey, 
        fnevExtended, 0L, lpAdvise, &ulTblConn); 
 
    /* Always release; mapi will have addref'ed it during Subscribe */ 
    UlRelease(lpAdvise); 
 
    if (hr != hrSuccess) 
        goto exit; 
 
exit: 
    FreeNull(lpKey); 
 
    if (hr == hrSuccess) 
    { 
        /* Remember our use of the notification engine. */ 
        pims->ulOQConn = ulOQConn; 
        pims->ulTblConn = ulTblConn; 
    } 
    else if (ulOQConn != 0) 
        (void) pims->psup->lpVtbl->Unsubscribe(pims->psup, ulOQConn); 
 
    DebugTraceResult(HrSetupPrivateNotifications, hr); 
    return hr; 
} 
 
/* 
 * HrUpdateOutgoingQueue 
 * 
 *  Purpose 
 *      Updates the outgoing queue based on the information given. If the 
 *      outgoing queue table is not open, or is out-of-date with respect to 
 *      the outgoing queue file on disk, initializes the table. The function 
 *      then applies the change requested to the table, writes the table on 
 *      disk, and notifies other processes of the change. 
 * 
 *  Arguments 
 *      pims: A pointer to the message store object. 
 *      pimsg: For a TABLE_ROW_ADDED event, a pointer to the message being 
 *          added to the queue; otherwise, this parameter should be NULL. 
 *      peid: For a TABLE_ROW_DELETED event, a pointer to the entryid of the 
 *          message being deleted from the queue; otherwise, this parameter 
 *          should be NULL. 
 *      ulTableEvent: The type of update event: Either TABLE_ROW_ADDED or 
 *          TABLE_ROW_DELETED. 
 * 
 *  Returns 
 *      HRESULT 
 */ 
HRESULT HrUpdateOutgoingQueue(PIMS pims, PIMSG pimsg, PEID peid, 
    ULONG ulTableEvent) 
{ 
    HRESULT hr = hrSuccess; 
    LPTABLEDATA lptbl; 
    SRow srNewRow = {0, 0, NULL}; 
    LPSRow prw; 
    BOOL fInMutex = FALSE; 
    FILETIME ftBeforeUpdate; 
 
    /* If the file mutex doesn't yet exist on this process, create it. */ 
 
    if (pims->hOGQueueMutex == NULL) 
    { 
        hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex); 
        if (hr != hrSuccess) 
            goto exit; 
    } 
 
    /* Get the file mutex so that we can use the file (and change it) */ 
    /* without crossing paths with another process. */ 
 
    WaitForSingleObject(pims->hOGQueueMutex, INFINITE); 
    fInMutex = TRUE; 
 
    /* This routine will open the outgoing queue table if it's not already */ 
    /* open in this process, and will leave the opened copy around in pims. */ 
 
    if (pims->lptblOutgoing == NULL) 
    { 
        hr = HrNewOutgoingTableData(pims); 
        if (hr != hrSuccess) 
            goto exit; 
    } 
 
    lptbl = pims->lptblOutgoing; 
    ftBeforeUpdate = pims->ftOGQueue; 
 
    if (ulTableEvent == TABLE_ROW_ADDED) 
    { 
        ULONG cValues; 
 
        AssertSz(pimsg, "A msg should be provided on an add"); 
        AssertSz(peid == NULL, "No entryid should be provided on an add"); 
 
        hr = pimsg->lpVtbl->GetProps(pimsg, (LPSPropTagArray) &sptaOutgoing, 
            0, /* ansi */ 
            &cValues, &srNewRow.lpProps); 
     
        if (HR_FAILED(hr))          /* Ignore warnings from GetProps. */ 
            goto exit; 
     
        srNewRow.cValues = cValues; 
     
        hr = lptbl->lpVtbl->HrModifyRow(lptbl, &srNewRow); 
        if (hr != hrSuccess) 
            goto exit; 
 
        prw = &srNewRow; 
    } 
    else 
    { 
        AssertSz(ulTableEvent == TABLE_ROW_DELETED, 
            "Bad event type received"); 
        AssertSz(pimsg == NULL, "No msg should be provided on a delete"); 
        AssertSz(peid, "An entryid should be provided on a delete"); 
 
        /* remove it from the outgoing queue */ 
        hr = HrRemoveRow(lptbl, peid); 
        if (hr != hrSuccess) 
            goto exit; 
 
        prw = NULL; 
    } 
 
    hr = HrWriteTableOnDisk(lptbl, (POBJ) pims, NULL, szOutgoingFileName); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* Update the last mod time of the table inside this process's */ 
    /* message store object. */ 
 
    hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName, 
        &pims->ftOGQueue); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = HrNotifyOnOutgoingQueue(pims, peid, prw, ulTableEvent, 
        &ftBeforeUpdate); 
 
exit: 
    if (fInMutex) 
        ReleaseMutex(pims->hOGQueueMutex); 
 
    LMFree(&pims->lmr, srNewRow.lpProps); 
 
    DebugTraceResult(HrUpdateOutgoingQueue, hr); 
    return hr; 
} 
 
/* 
 * HrNewOutgoingTableData 
 * 
 * Purpose  Checks the outgoing table data object in the message store. 
 *          If there isn't one, creates it and initializes it from disk. 
 *          If there is one, empties it, and re-initializes it from disk. 
 *          Should be inside the outgoing queue mutex during this function. 
 * 
 * Parameters 
 *  pims    A pointer to the message store object. 
 * 
 * Side Effects 
 *          Fills in the lptblOutgoing member of the pims. 
 *          Updates pims->ftOGQueue with the last mod time of the  
 *          outgoing queue file. 
 */ 
HRESULT HrNewOutgoingTableData(PIMS pims) 
{ 
    HRESULT hr = hrSuccess; 
    LPTABLEDATA lptbl = pims->lptblOutgoing; 
    BOOL fTableCreated = FALSE; 
 
    if (!lptbl) 
    { 
        PINST pinst; 
        SCODE sc = S_OK; 
     
        /* The table doesn't exist. Create it. */ 
 
        pinst = (PINST) PvGetInstanceGlobals(); 
     
        if (pinst == NULL) 
        { 
            hr = ResultFromScode(MAPI_E_CALL_FAILED); 
            goto exit; 
        } 
     
        sc = CreateTable((LPIID) &IID_IMAPITableData, pims->lmr.lpAllocBuf, 
            pims->lmr.lpAllocMore, pims->lmr.lpFreeBuf, pinst->lpmalloc, 
            TBLTYPE_DYNAMIC, PR_INSTANCE_KEY, (LPSPropTagArray) &sptaOutgoing, 
            &lptbl); 
     
        if (sc != S_OK) 
        { 
            hr = ResultFromScode(sc); 
            goto exit; 
        } 
 
        fTableCreated = TRUE; 
    } 
    else 
    { 
        /* The table exists already. Delete all rows from it. */ 
        EmptyTable(lptbl, &pims->lmr); 
    } 
 
    /* read the table in from disk */ 
    /* outgoing queue tables can't be regenerated, so any error reading */ 
    /* the table is fatal. We lose all messages in the OG queue if this */ 
    /* function has an error. */ 
 
    hr = HrReadTableFromDisk(lptbl, (POBJ) pims, NULL, OUTGOING_COLUMNS, 
        szOutgoingFileName); 
    if (hr != hrSuccess) 
    { 
        TraceSz("SMS: Bad OG Queue data on disk."); 
        goto exit; 
    } 
 
    /* Verify that all messages in the table actually exist on disk. If */ 
    /* not, remove the row(s) and re-write the table to disk. */ 
    hr = HrSyncOutgoingTable(lptbl, pims); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* Save away the last mod time of the table inside this process's */ 
    /* message store object. */ 
 
    hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName, 
        &pims->ftOGQueue); 
    if (hr != hrSuccess) 
        goto exit; 
 
    pims->lptblOutgoing = lptbl; 
 
exit: 
    if (hr != hrSuccess && fTableCreated) 
        UlRelease(lptbl); 
 
    DebugTraceResult(HrNewOutgoingTableData, hr); 
    return hr; 
} 
 
/* 
 * INTERNAL Functions (called from within this file ONLY). 
 * 
 */ 
 
/*----------------------------------------------------------------------+ 
|                                                                       | 
|           CONTENTS AND HIERARCHY TABLE NOTIFICATION HANDLING          | 
|                                                                       | 
+----------------------------------------------------------------------*/ 
 
/* 
 * LSMSTblNotifCallback 
 * 
 *  Purpose 
 *      Update the contents or hierarchy table associated with the folder eid 
 *      passed across. We should receive a notification when the tabledata 
 *      object needs to have a row added, deleted or modified. Calls 
 *      ChangeTable after decoding the notification. 
 * 
 *  Arguments 
 *      lpvContext: A pointer to the message store object to use. We need 
 *          to verify that the object is still valid before using it. 
 *      cNotif: The number of notifications to process. 
 *      lpNotif: A pointer to an array of NOTIFICATION structures. 
 * 
 *  Returns 
 *      LONG: Always returns 0. 
 */ 
long STDAPICALLTYPE LSMSTblNotifCallback(LPVOID lpvContext, ULONG cNotif, 
    LPNOTIFICATION lpNotif) 
{ 
    PIMS pims = (PIMS) lpvContext; 
    SCODE sc = S_OK; 
    HRESULT hr = hrSuccess; 
    PTNB ptnb; 
 
    /* 
     * Our code sends one extended notification at a time. 
     * The notification consists of the table event that occurred along with 
     * an object notification containing the entryids we need. We only use 
     * two of the entryids in the object notification. The ParentID fields 
     * refer to the parent folder of the table we need to update. The EntryID 
     * fields refer to the object that changed within the folder. The 
     * ulObjType field will be either MAPI_MESSAGE (for contents table 
     * changes) or MAPI_FOLDER (for hierarchy table changes).  All other 
     * fields in the structure are unused and should be set to 0. 
     */ 
    if (IMS_IsInvalid(pims) 
        || cNotif != 1 
        || (IsBadReadPtr(lpNotif, ((UINT) cNotif) * sizeof(NOTIFICATION))) 
        || lpNotif->ulEventType != fnevExtended 
        || lpNotif->info.ext.ulEvent != 0) 
        return 0; 
 
    ptnb = (PTNB) lpNotif->info.ext.pbEventParameters; 
 
    if (IsBadReadPtr(ptnb, CbNewTNB(0)) 
        || IsBadReadPtr(ptnb, CbTNB(ptnb))) 
        return 0; 
 
    IMS_EnterCriticalSection(pims); 
 
    hr = HrApplyTblNotifChanges(pims, ptnb); 
 
    IMS_LeaveCriticalSection(pims); 
 
    if (hr != hrSuccess) 
        sc = GetScode(hr); 
 
    return sc; 
} 
 
/*  
 * HrApplyTblNotifChanges 
 * 
 * Purpose 
 *  This function relocates and validates the internal object notification 
 *  passed in, and then calls ChangeTable to actually update any open tables 
 *  within this process with the change given. The notification needs to be 
 *  relocated because the pointers from the other process may not be valid 
 *  on this process. 
 * 
 * Parameters 
 *  pims: A pointer to the message store object. 
 *  ptnbIn: A pointer to the table notification block (TNB) containing the 
 *      data we need in order to update any open tables om this process. 
 * 
 * Returns: validation errors or hrSuccess. 
 */ 
HRESULT HrApplyTblNotifChanges(PIMS pims, PTNB ptnbIn) 
{ 
    HRESULT hr = hrSuccess; 
    SCODE sc = S_OK; 
    LPNOTIFICATION lpntf; 
    PTNB ptnb = NULL; 
    OBJECT_NOTIFICATION *pon; 
    ULONG cb; 
 
    /* Allocate a new notification block, and copy the data over before */ 
    /* relocation. */ 
 
    hr = HrAlloc(CbNewTNB(ptnbIn->cbNtf), &ptnb); 
    if (hr != hrSuccess) 
        goto ret; 
 
    memcpy(ptnb, ptnbIn, (UINT) CbNewTNB(ptnbIn->cbNtf)); 
 
    lpntf = (LPNOTIFICATION) ptnb->abNtf; 
 
    if (lpntf->ulEventType != fnevObjectModified) 
    { 
        TraceSz1("SMS: HrApplyTblNotifChanges: Bad ulEventType %08lX " 
            "received", lpntf->ulEventType); 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto ret; 
    } 
 
    /* Relocate the notification into the address space of this process. */ 
    /* We passed along the memory offset of the originating process to allow */ 
    /* this code to work. Note that ScRelocNotifications currently only */ 
    /* works from "bad" addresses to "good" addresses. The code assumes that */ 
    /* pointers are "bad" inside the notification, and that after conversion, */ 
    /* they are valid. */ 
 
    sc = ScRelocNotifications(1, lpntf, ptnb->pvRef, (LPVOID) lpntf, &cb); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto ret; 
    } 
 
    if (ptnb->cbNtf != cb) 
    { 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto ret; 
    } 
 
    pon = (OBJECT_NOTIFICATION *) &(lpntf->info.obj); 
 
    if (pon->ulObjType != MAPI_MESSAGE && pon->ulObjType != MAPI_FOLDER) 
    { 
        TraceSz1("SMS: HrApplyTblNotifChanges: unexpected Object Type %08lX", 
            pon->ulObjType); 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto ret; 
    } 
 
    if (    FIsInvalidEID(pon->cbParentID, (PEID) pon->lpParentID, pims) 
        ||  !FIsFolder((PEID) pon->lpParentID)) 
    { 
        TraceSz("SMS: HrApplyTblNotifChanges: invalid parent entryid"); 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto ret; 
    } 
 
    /* TABLE_CHANGED events don't require a lpEntryID, because multiple 
     * objects have changed. ChangeTable() simply validates all rows in 
     * the table against the files on disk. 
     */ 
    if (    ptnb->ulTableEvent != TABLE_CHANGED 
        &&  FIsInvalidEID(pon->cbEntryID, (PEID) pon->lpEntryID, pims)) 
    { 
        TraceSz("SMS: HrApplyTblNotifChanges: invalid entryid"); 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto ret; 
    } 
 
    ChangeTable(pims, (PEID) pon->lpParentID, (PEID) pon->lpEntryID, 
        pon->ulObjType, ptnb->ulTableEvent, FALSE); 
 
ret: 
    FreeNull(ptnb); 
 
    DebugTraceResult(HrApplyTblNotifChanges, hr); 
    return hr; 
} 
 
/*  
 * HrSendNotif 
 * 
 * Purpose 
 *  This function constructs and sends a notification to other active Sample 
 *  Message Store processes open on this message store file. The notification 
 *  describes a change (add, delete, or modify) to either a message (contents 
 *  table) or folder (hierarchy table). The receiver will need to get two 
 *  entryids, one for the parent folder of the changed object, and one for 
 *  the changed object itself. The receiver also needs to know whether the 
 *  changed object is a message or a folder, and what type of change occurred: 
 *  add, delete, modify, or change (contents tables only). 
 * 
 * Parameters 
 *  pims: Pointer to the message store object. 
 *  peidParent: The entryid of the folder containing the table to update and 
 *              the object that changed. 
 *  peidObject: The entryid of the object that changed. May be NULL when 
 *              sending a TABLE_CHANGED notification (contents tables only). 
 *  ulTableEvent: TABLE_ROW_ADDED, TABLE_ROW_DELETED, TABLE_ROW_MODIFIED, or 
 *              TABLE_CHANGED (TABLE_CHANGED only works on contents tables). 
 *  ulObjType: The type of object that changed. This implies the type of table 
 *              to update. May be MAPI_MESSAGE (contents table) or MAPI_FOLDER 
 *              (hierarchy table). 
 * 
 * Returns: Memory and disk errors or success. 
 */ 
HRESULT HrSendNotif(PIMS pims, PEID peidParent, PEID peidObject, 
    ULONG ulTableEvent, ULONG ulObjType) 
{ 
    HRESULT hr = hrSuccess; 
    SCODE sc; 
    LPNOTIFKEY lpKey = NULL; 
    NOTIFICATION ntfTemp; 
    NOTIFICATION ntf; 
    PTNB ptnb = NULL; 
    ULONG cbNtf; 
    ULONG cbOut; 
    ULONG ulFlags = 0; 
 
    /* Get the key */ 
    hr = HrGetTableNotificationKey(pims, &lpKey); 
    if (hr != hrSuccess) 
        goto ret; 
 
    /* 
     * Our code sends one extended notification at a time. The notification 
     * consists of the table event that occurred along with an object 
     * notification containing the entryids we need. We send the type of 
     * table event as part of our extended notification structure, and 
     * package the object notification always as "fnevObjectModified". We 
     * only use two of the entryids in the object notification. The ParentID 
     * fields refer to the parent folder of the table we need to update. The 
     * EntryID fields refer to the object that changed within the folder. The 
     * ulObjType field will be either MAPI_MESSAGE (for contents table 
     * changes) or MAPI_FOLDER (for hierarchy table changes).  All other 
     * fields in the structure are unused and should be set to 0. 
     */ 
    memset(&ntfTemp, 0, sizeof(NOTIFICATION)); 
 
    /* We always send the same type of event here. This is enough to get the 
     * notification code to count and relocate what we send. We send the real 
     * table event in ptnb->ulTableEvent (see below). 
     */ 
    ntfTemp.ulEventType = fnevObjectModified; 
 
    ntfTemp.info.obj.ulObjType  = ulObjType; 
 
    ntfTemp.info.obj.lpParentID = (LPENTRYID) peidParent; 
    ntfTemp.info.obj.cbParentID = CbEID(peidParent); 
 
    if (ulTableEvent != TABLE_CHANGED) 
    { 
        ntfTemp.info.obj.lpEntryID  = (LPENTRYID) peidObject; 
        ntfTemp.info.obj.cbEntryID  = CbEID(peidObject); 
    } 
 
    sc = ScCountNotifications(1, &ntfTemp, &cbNtf); 
    if (sc != S_OK) 
    {                    
        hr = ResultFromScode(sc); 
        goto ret; 
    } 
 
    hr = HrAlloc(CbNewTNB(cbNtf), &ptnb); 
    if (hr != hrSuccess) 
        goto ret; 
 
    /* Here's where we send the table event. It's either TABLE_ROW_ADDED, 
     * TABLE_ROW_DELETED, TABLE_ROW_MODIFIED, or TABLE_CHANGED. 
     */ 
    ptnb->ulTableEvent = ulTableEvent; 
    ptnb->cbNtf = cbNtf; 
 
    sc = ScCopyNotifications(1, &ntfTemp, (LPVOID) ptnb->abNtf, &cbOut); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto ret; 
    } 
 
    AssertSz(cbOut == cbNtf, "ScCopyNotifications used a different # of bytes " 
        "than ScCountNotifications returned."); 
 
    /* Pass across the notification's memory offset so that the receiving */ 
    /* process can relocate the notification to its address space. */ 
    ptnb->pvRef = (LPVOID) ptnb->abNtf; 
     
    ntf.ulEventType = fnevExtended; 
    ntf.info.ext.ulEvent = 0; 
    ntf.info.ext.cb = CbTNB(ptnb); 
    ntf.info.ext.pbEventParameters = (LPBYTE) ptnb; 
 
    hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, &ntf, &ulFlags); 
 
ret: 
    FreeNull(lpKey); 
    FreeNull(ptnb); 
 
    DebugTraceResult(HrSendNotif, hr); 
    return hr; 
} 
 
/* 
 * HrGetTableNotificationKey 
 * 
 * Purpose 
 *  Generate and return the notification key that will allow cross-process 
 *  notifications for changes to any contents or hierarchy tables within a 
 *  store. The memory returned should be freed with FreeNull. This 
 *  notification key needs to work for processes attached to this particular 
 *  message store, and should only receive notifications having to do with 
 *  changes to tables. The key contains the store guid (unique for this store) 
 *  preceeded by a ULONG with 0x0000ABCD in it. Note that the choice of 
 *  0x0000ABCD is arbitrary. As long as the sender sends to the same key as 
 *  the receiver listens to, and no unexpected sender sends to that key, 
 *  we're fine. 
 * 
 * Parameters 
 *  pims        pointer to the message store object. 
 *  lppKey      pointer to the location to return the new key. 
 */ 
static HRESULT HrGetTableNotificationKey(PIMS pims, LPNOTIFKEY * lppKey) 
{ 
    HRESULT hr = hrSuccess; 
    LPNOTIFKEY lpKey = NULL; 
    ULONG cb;                   /* number of bytes in the key */ 
 
    /* allocate space for the key */ 
    cb = sizeof(ULONG) + sizeof(MAPIUID); 
    hr = HrAlloc(CbNewNOTIFKEY(cb), (PPV) &lpKey); 
    if (hr != hrSuccess) 
        goto exit; 
 
    *((ULONG *) &(lpKey->ab[0])) = 0x0000ABCD; 
    GetResourceUID(pims, (MAPIUID *) &(lpKey->ab[sizeof(ULONG)])); 
    lpKey->cb = cb; 
 
exit: 
    if (HR_FAILED(hr)) 
    { 
        FreeNull(lpKey); 
        lpKey = NULL; 
    } 
 
    *lppKey = lpKey; 
 
    DebugTraceResult(HrGetTableNotificationKey, hr); 
    return hr; 
} 
 
/*----------------------------------------------------------------------+ 
|                                                                       | 
|               OUTGOING QUEUE NOTIFICATION HANDLING                    | 
|                                                                       | 
+----------------------------------------------------------------------*/ 
 
/* 
 * LSMSOQNotifCallback 
 * 
 *  Purpose 
 *      Update the outgoing queue table associated with the process that 
 *      the spooler is using. We should receive a notification when the 
 *      tabledata object needs to have a row added or deleted. If the 
 *      tabledata object exists on the message store object, then call 
 *      HrModifyRow (when a row is added), or HrRemoveRow (when a row is 
 *      deleted) to update the table appropriately. 
 * 
 *  Arguments 
 *      lpvContext: A pointer to the message store object to use. We need 
 *          to verify that the object is still valid before using it. 
 *      cNotif: The number of notifications to process. 
 *      lpNotif: A pointer to an array of NOTIFICATION structures. 
 * 
 *  Returns 
 *      LONG: Always returns 0. 
 */ 
long STDAPICALLTYPE LSMSOQNotifCallback(LPVOID lpvContext, ULONG cNotif, 
    LPNOTIFICATION lpNotif) 
{ 
    PIMS pims = (PIMS) lpvContext; 
    SCODE sc = S_OK; 
    HRESULT hr = hrSuccess; 
    FILETIME ftCurrent; 
    BOOL fInMutex = FALSE; 
    PONB ponb; 
 
    /* Our code sends one extended notification at a time. This */ 
    /* extended notification contains two filetimes (the time */ 
    /* before the outgoing queue file was modified, and the time after */ 
    /* the change was made), and a standard notification with the change */ 
    /* to apply to the table. It should be an fnevTableModified, and */ 
    /* should be either TABLE_ROW_ADDED or TABLE_ROW_DELETED. If we ever */ 
    /* receive anything other than this, the code must change. */ 
    if (IMS_IsInvalid(pims) 
        || cNotif != 1 
        || (IsBadReadPtr(lpNotif, ((UINT) cNotif) * sizeof(NOTIFICATION))) 
        || lpNotif->ulEventType != fnevExtended 
        || lpNotif->info.ext.ulEvent != 0) 
        return 0; 
 
    ponb = (PONB) lpNotif->info.ext.pbEventParameters; 
 
    if (IsBadReadPtr(ponb, CbNewONB(0)) 
        || IsBadReadPtr(ponb, CbONB(ponb))) 
        return 0; 
 
    IMS_EnterCriticalSection(pims); 
 
    /* Check to see if the outgoing queue table data is open. If it isn't, */ 
    /* there is nothing to do, because the current table will be read and */ 
    /* initialized from disk when the spooler opens it. We're done. */ 
 
    if (!pims->lptblOutgoing) 
        goto exit; 
 
    /* If the file mutex doesn't yet exist on this process, create it. */ 
 
    if (pims->hOGQueueMutex == NULL) 
    { 
        hr = HrCreateOGQueueMutex(&pims->hOGQueueMutex); 
        if (hr != hrSuccess) 
            goto exit; 
    } 
 
    /* Get the file mutex so that we can use the file (and change it) */ 
    /* without crossing paths with another process. */ 
 
    WaitForSingleObject(pims->hOGQueueMutex, INFINITE); 
    fInMutex = TRUE; 
 
    /* Get time that the file was last modified */ 
    hr = HrGetFileModTime(pims->szStorePath, szOutgoingFileName, &ftCurrent); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* If the time that this process last read the file is the same as */ 
    /* the time that the file was last modified, then don't do anything */ 
    /* because we already have all changes, including the one sent to us */ 
    /* in this notification. This can happen because we picked up two */ 
    /* changes at once, or because this process is actually the same */ 
    /* process that sent the notification. */ 
    if (CompareFileTime(&ftCurrent, &pims->ftOGQueue) == 0) 
        goto exit; 
 
    /* If the time that this process last read the file is the same as */ 
    /* the time that the other process read the file, then we can simply */ 
    /* apply the changes sent in the notification itself, and update our */ 
    /* time to the time after update sent in the notification. */ 
    /* If the times are different, or there is a problem applying the */ 
    /* changes, then reconstruct the table from the on-disk copy. */ 
 
    if ((CompareFileTime(&ponb->ftBeforeUpdate, &pims->ftOGQueue) == 0) 
        && (HrApplyOQNotifChanges(pims->lptblOutgoing, ponb) == hrSuccess)) 
    { 
        pims->ftOGQueue = ponb->ftAfterUpdate; 
    } 
    else 
    { 
        hr = HrNewOutgoingTableData(pims); 
        if (hr != hrSuccess) 
            goto exit; 
    } 
 
exit: 
    if (fInMutex) 
        ReleaseMutex(pims->hOGQueueMutex); 
 
    IMS_LeaveCriticalSection(pims); 
 
    if (hr != hrSuccess) 
        sc = GetScode(hr); 
 
    return sc; 
} 
 
/* 
 * EmptyTable 
 * 
 *  Purpose 
 *      Deletes all rows from the table data object given. 
 *      Helper function for the outgoing queue table notification callback 
 *      routine. 
 * 
 *  Arguments 
 *      lptbl: A pointer to the tabledata object to empty. 
 *      plmr: A pointer to the linked memory routines. 
 * 
 *  Returns 
 *      void. 
 */ 
static void EmptyTable(LPTABLEDATA lptbl, PLMR plmr) 
{ 
    HRESULT hr; 
    LPSRow lpsRow; 
    LPSPropValue pval; 
    LPSPropValue pvalMac; 
 
    while (TRUE) 
    { 
        /* Get the first row. Note that as we delete rows, this will */ 
        /* keep giving us a new row. */ 
 
        hr = lptbl->lpVtbl->HrEnumRow(lptbl, 0, &lpsRow); 
        if (hr != hrSuccess) 
        { 
            TraceSz1("Sample MS: EmptyTable: HrEnumRow failed with" 
                " sc == %s", SzDecodeScode(GetScode(hr))); 
            break; 
        } 
 
        /* The table is empty when no row is returned */ 
        if (!lpsRow) 
            break; 
 
        /* find the entryid in the property value array */ 
pval = lpsRow->lpProps; 
        pvalMac = pval + lpsRow->cValues; 
 
        for (; pval < pvalMac; ++pval) 
            if (pval->ulPropTag == PR_INSTANCE_KEY) 
                break; 
 
        /* Every row should contain an inst key. It is the index property. */ 
        if (pval == pvalMac) 
        { 
            TrapSz("No PR_INSTANCE_KEY found in the table row"); 
            break; 
        } 
 
        /* delete this row from the table */ 
        hr = lptbl->lpVtbl->HrDeleteRow(lptbl, pval); 
 
        LMFree(plmr, lpsRow); 
        lpsRow = NULL; 
 
        if (hr != hrSuccess) 
        { 
            TraceSz1("Sample MS: EmptyTable: HrDeleteRow failed with" 
                " error %s", SzDecodeScode(GetScode(hr))); 
            break; 
        } 
    } 
 
    return; 
} 
 
/* 
 * HrApplyOQNotifChanges 
 * 
 *  Purpose 
 *      Helper function of the notification callback routine. If the  
 *      table on disk hasn't changed except for the notification given, 
 *      then we can update the outgoing queue table data directly instead 
 *      of re-reading the table from disk. This function modifies the 
 *      table data directly by calling HrModifyRow (when a row is added), 
 *      or HrRemoveRow (when a row is deleted). 
 *      Note that we have to convert the notification inside the ONB so 
 *      that the pointers are valid. Also note that we may NOT modify the 
 *      notification data at all; therefore, we must copy the data before 
 *      changing it. 
 * 
 *  Arguments 
 *      lptbl: a pointer to the table data object to update. 
 *      ponbIn: a pointer to the ONB received in the callback. 
 * 
 *  Returns 
 *      HRESULT 
 */ 
static HRESULT HrApplyOQNotifChanges(LPTABLEDATA lptbl, PONB ponbIn) 
{ 
    HRESULT hr = hrSuccess; 
    SCODE sc; 
    ULONG cb; 
    LPNOTIFICATION lpntf; 
    PONB ponb = NULL; 
 
    /* Allocate a new notification block, and copy the data over before */ 
    /* relocation. */ 
 
    hr = HrAlloc(CbNewONB(ponbIn->cbNtf), &ponb); 
    if (hr != hrSuccess) 
        goto exit; 
 
    memcpy(ponb, ponbIn, (UINT) CbNewONB(ponbIn->cbNtf)); 
 
    lpntf = (LPNOTIFICATION) ponb->abNtf; 
 
    /* Relocate the notification into the address space of this process. */ 
    /* We passed along the memory offset of the originating process to allow */ 
    /* this code to work. Note that ScRelocNotifications currently only */ 
    /* works from "bad" addresses to "good" addresses. The code assumes that */ 
    /* pointers are "bad" inside the notification, and that after conversion, */ 
    /* they are valid. */ 
 
    sc = ScRelocNotifications(1, lpntf, ponb->pvRef, (LPVOID) lpntf, &cb); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto exit; 
    } 
 
    if (ponb->cbNtf != cb) 
    { 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto exit; 
    } 
 
    /* We don't expect any events other than those for a table. */ 
 
    if (lpntf->ulEventType != fnevTableModified) 
    { 
        TraceSz1("SMS: HrApplyOQNotifChanges: unexpected ulEventType %08lX", 
            lpntf->ulEventType); 
        hr = ResultFromScode(MAPI_E_CALL_FAILED); 
        goto exit; 
    } 
 
    switch (lpntf->info.tab.ulTableEvent) 
    { 
    case TABLE_ROW_DELETED: 
 
        /* delete the row from the table according to the index */ 
        /* property value in the notification structure */ 
        hr = lptbl->lpVtbl->HrDeleteRow(lptbl, &lpntf->info.tab.propIndex); 
        if (hr != hrSuccess) 
        { 
            TraceSz1("SMS: HrApplyOQNotifChanges: HrDeleteRow returns sc == %s", 
                SzDecodeScode(GetScode(hr))); 
            goto exit; 
        } 
        break; 
 
    case TABLE_ROW_ADDED: 
 
        /* add the row to the table. We don't care where in the table */ 
        /* it goes, because the row will be sorted by the spooler in */ 
        /* its view anyway. */ 
 
        hr = lptbl->lpVtbl->HrModifyRow(lptbl, &lpntf->info.tab.row); 
        if (hr != hrSuccess) 
        { 
            TraceSz1("SMS: HrApplyOQNotifChanges: HrModifyRow returns sc == %s", 
                SzDecodeScode(GetScode(hr))); 
            goto exit; 
        } 
        break; 
 
    default: 
 
        /* We don't expect any other table events than the */ 
        /* two above. */ 
 
        TraceSz1("SMS: HrApplyOQNotifChanges: unexpected ulTableEvent %08lX", 
            lpntf->info.tab.ulTableEvent); 
        break; 
    } 
 
exit: 
    FreeNull(ponb); 
 
    DebugTraceResult(HrApplyOQNotifChanges, hr); 
    return hr; 
} 
 
/* 
 * HrGetOutgoingNotificationKey 
 * 
 * Purpose  return the nofication key for the outgoing queue 
 *          memory should be freed with FreeNull 
 *          The key we use contains the full pathname to the outgoing 
 *          queue file on disk. This should be unique for the store 
 *          that we're running against. 
 * 
 * Parameters 
 * pims         the store whose outgoing queue is being referred to 
 * lppKey       pointer to the key 
 */ 
static HRESULT HrGetOutgoingNotificationKey(PIMS pims, LPNOTIFKEY * lppKey) 
{ 
    HRESULT hr = hrSuccess; 
    LPNOTIFKEY lpKey = NULL; 
    ULONG cb;                   /* number of bytes in the key */ 
    LPSTR szPath = NULL;        /* path to outgoing queue */ 
 
    hr = HrGetTableName((POBJ) pims, NULL, szOutgoingFileName, &szPath); 
    if (HR_FAILED(hr)) 
        goto exit; 
 
    /* allocate space for the key */ 
    cb = Cbtszsize(szPath); 
    hr = HrAlloc(CbNewNOTIFKEY(cb), (PPV) &lpKey); 
    if (hr != hrSuccess) 
        goto exit; 
 
    lstrcpy(lpKey->ab, szPath); 
    lpKey->cb = cb; 
 
exit: 
    FreeNull(szPath); 
    if (HR_FAILED(hr)) 
    { 
        FreeNull(lpKey); 
        lpKey = NULL; 
    } 
    *lppKey = lpKey; 
    return hr; 
} 
 
/* 
 * HrNotifyOnOutgoingQueue 
 * 
 * Purpose 
 *  Send out a notification that the Outgoing Queue has had a row added 
 *  or deleted. Also send the filetime of the queue file before and after 
 *  the modification. 
 * 
 * Parameters 
 *  pims: A pointer to the message store object. 
 *  peid: (For TABLE_ROW_DELETED) The entryid of the message in the 
 *          queue that was deleted. 
 *  prw: (For TABLE_ROW_ADDED) A pointer to the row of data added 
 *          to the OG Queue. 
 *  ulTableEvent: Either TABLE_ROW_ADDED or TABLE_ROW_DELETED. 
 *  pftBeforeUpdate: A pointer to the filetime of the queue file before the 
 *          update was performed. 
 */ 
static HRESULT HrNotifyOnOutgoingQueue(PIMS pims, PEID peid, LPSRow prw, 
    ULONG ulTableEvent, FILETIME *pftBeforeUpdate) 
{ 
    HRESULT hr; 
    LPNOTIFKEY lpKey = NULL; 
    ULONG ulFlags = 0; 
    NOTIFICATION ntf; 
    NOTIFICATION ntfTemp; 
    PONB ponb = NULL; 
    ULONG cbNtf; 
    ULONG cbOut; 
    SCODE sc; 
 
    /* get the key */ 
    hr = HrGetOutgoingNotificationKey(pims, &lpKey); 
    if (HR_FAILED(hr)) 
        goto exit; 
 
    /* Assemble the notification. */ 
    ntfTemp.ulEventType = fnevTableModified; 
    ntfTemp.info.tab.ulTableEvent = ulTableEvent; 
    ntfTemp.info.tab.hResult = hrSuccess; 
 
    if (ulTableEvent == TABLE_ROW_DELETED) 
    { 
        /* Send across the index property for the row: PR_INSTANCE_KEY */ 
        ntfTemp.info.tab.propIndex.ulPropTag = PR_INSTANCE_KEY; 
        ntfTemp.info.tab.propIndex.Value.bin.cb = CbEID(peid); 
        ntfTemp.info.tab.propIndex.Value.bin.lpb = (BYTE *) peid; 
        memset(&(ntfTemp.info.tab.propPrior), 0, sizeof(SPropValue)); 
        ntfTemp.info.tab.row.cValues = 0; 
        ntfTemp.info.tab.row.lpProps = NULL; 
    } 
    else 
    { 
        AssertSz(ulTableEvent == TABLE_ROW_ADDED, 
            "Bad event type: about to send bogus internal notification"); 
 
        memset(&(ntfTemp.info.tab.propIndex), 0, sizeof(SPropValue)); 
        memset(&(ntfTemp.info.tab.propPrior), 0, sizeof(SPropValue)); 
        ntfTemp.info.tab.row = *prw; 
    } 
 
    sc = ScCountNotifications(1, &ntfTemp, &cbNtf); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto exit; 
    } 
 
    hr = HrAlloc(CbNewONB(cbNtf), &ponb); 
    if (hr != hrSuccess) 
        goto exit; 
 
    ponb->cbNtf = cbNtf; 
 
    sc = ScCopyNotifications(1, &ntfTemp, (LPVOID) ponb->abNtf, &cbOut); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto exit; 
    } 
 
    AssertSz(cbOut == cbNtf, "ScCopyNotifications used a different # of bytes " 
        "than ScCountNotifications returned."); 
 
    ponb->ftBeforeUpdate = *pftBeforeUpdate; 
    ponb->ftAfterUpdate = pims->ftOGQueue; 
 
    /* Pass across the notification's memory offset so that the receiving */ 
    /* process can relocate the notification to its address space. */ 
    ponb->pvRef = (LPVOID) ponb->abNtf; 
     
    ntf.ulEventType = fnevExtended; 
    ntf.info.ext.ulEvent = 0; 
    ntf.info.ext.cb = CbONB(ponb); 
    ntf.info.ext.pbEventParameters = (LPBYTE) ponb; 
 
    hr = pims->psup->lpVtbl->Notify(pims->psup, lpKey, 1, &ntf, &ulFlags); 
 
exit: 
    FreeNull(lpKey); 
    FreeNull(ponb); 
 
    DebugTraceResult(HrNotifyOnOutgoingQueue, hr); 
    return hr; 
}