MSPTBL.C
/* 
 *  M S P T B L . C 
 * 
 *  Functions to manage a hierarchy and contents tables cached on disk. 
 * 
 *  Copyright 1992-1995 Microsoft Corporation.  All Rights Reserved. 
 */ 
 
#include "msp.h" 
 
static SCODE ScCreateFile(LPTSTR szFile, ULONG ulAccess, ULONG ulShare, 
    ULONG ulCreate, HANDLE * lphFile); 
static HRESULT HrOpenTblFileRetry(LPTSTR szFile, ULONG ulAccess, ULONG ulShare, 
    ULONG ulCreate, HANDLE * lphFile); 
static HRESULT HrNewCounts(PIFLD pifld, LPTABLEDATA lptbl); 
static HRESULT HrReadBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToRead, 
    BOOL * pfEOF); 
static HRESULT HrWriteBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToWrite); 
static HRESULT HrWriteRow(HANDLE hFile, LPSRow prw); 
static VOID TranslateFileError(BOOL fSuccess, ULONG cbIn, ULONG cbOut, 
    BOOL * pfEOF, SCODE * pscFile); 
static HRESULT HrGetTime(LPSRow prw, FILETIME * pfiletime); 
static HRESULT HrRemoveBadTableRows(LPTABLEDATA lptbl, PIFLD pifldParent, 
    PIMS pims, BOOL * pfTableChanged); 
static HRESULT HrAddMissingTableRows(LPTABLEDATA lptbl, PIFLD pifld, 
    LPSTR szTemplate, LPSPropTagArray ptaga, BOOL * pfTableChanged); 
 
 
 
/* format of a row of table data on disk (DRW) */ 
typedef struct _DRW 
{ 
    ULONG   cbRow; 
    SRow    rw; 
    BYTE    ab[MAPI_DIM]; 
} DRW, *PDRW; 
 
#define CbNewDRW(_cb)       (offsetof(DRW,ab) + (_cb)) 
#define CbDRW(_pdrw)        (offsetof(DRW,ab) + (UINT)((_pdrw)->cbRow)) 
 
 
/**************************************************************************** 
 * ScCreateFile 
 * 
 * Purpose      Open or create a file 
 * 
 * Parameters 
 *  szFile          name of the file 
 *  ulAccess        read/write access desired see CreateFile 
 *  ulShare         sharing desired see CreateFile 
 *  ulCreate        creation disposition see CreateFile 
 *  lphFile         returns handle of open file, undefined if call fails 
 * 
 */ 
static SCODE  
ScCreateFile(LPTSTR szFile, ULONG ulAccess, ULONG ulShare, 
    ULONG ulCreate, HANDLE * lphFile) 
{ 
    HANDLE hFile; 
    SCODE sc = S_OK; 
 
    hFile = CreateFile(szFile, ulAccess, ulShare, NULL, 
        ulCreate, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 
 
    if (hFile == INVALID_HANDLE_VALUE) 
    { 
        switch (GetLastError()) 
        { 
        case ERROR_FILE_NOT_FOUND: 
            sc = MAPI_E_NOT_FOUND; 
            break; 
 
        case ERROR_SHARING_VIOLATION: 
        case ERROR_LOCK_VIOLATION: 
            sc = MAPI_E_BUSY; 
            break; 
 
        default: 
            sc = MAPI_E_NO_ACCESS; 
            break; 
        } 
    } 
    else 
    { 
        AssertSz(!IsBadWritePtr(lphFile, sizeof(HANDLE)), "Bad parameter" 
            " (lphFile) given to ScCreateFile"); 
 
        *lphFile = hFile; 
    } 
 
    DebugTraceSc(ScCreateFile, sc); 
    return sc; 
} 
 
/* 
 * HrOpenTblFileRetry 
 * 
 * Purpose      Open or create a file, but retry if it is busy 
 * 
 * Parameters 
 *  szFile          name of the file 
 *  ulAccess        read/write access desired see CreateFile 
 *  ulShare         sharing desired see CreateFile 
 *  ulCreate        creation disposition see CreateFile 
 *  lphFile         returns handle of open file, undefined if call fails 
 * 
 */ 
static HRESULT HrOpenTblFileRetry(LPTSTR szFile, ULONG ulAccess, ULONG ulShare, 
    ULONG ulCreate, HANDLE * lphFile) 
{ 
    UINT iRetry; 
    SCODE sc = S_OK; 
    HANDLE hFile; 
 
    iRetry = 0; 
    while (TRUE) 
    { 
        sc = ScCreateFile(szFile, ulAccess, ulShare, ulCreate, &hFile); 
 
        if (sc != MAPI_E_BUSY || ++iRetry >= NUM_RETRIES) 
            break; 
 
        Sleep(500); 
    } 
 
    if (sc == S_OK) 
        *lphFile = hFile; 
 
    #ifdef DEBUG 
    if (iRetry >= NUM_RETRIES) 
        TraceSz("HrOpenTblFileRetry: Failing open. Too many tries."); 
    #endif 
 
    DebugTraceSc(HrOpenTblFileRetry, sc); 
    return ResultFromScode(sc); 
} 
 
/* 
 * HrWriteCounts 
 * 
 *  Purpose 
 *      This function writes the counts of messages and unread messages 
 *      in a folder to the folder's property file. After the code  
 *      validates what the counts should be, this function writes the 
 *      counts so that they are correct. 
 * 
 *  Parameters 
 *      pifld: A pointer to the folder object to update. 
 *      ulMsgs: The number of messages in the folder. 
 *      ulUnread: The number of unread messages in the folder. 
 * 
 */ 
static HRESULT HrWriteCounts(PIFLD pifld, ULONG ulMsgs, ULONG ulUnread) 
{ 
    HRESULT hr; 
    LPMESSAGE lpmsg = NULL; 
    PLMR plmr = &pifld->pims->lmr; 
    ULONG ulMsgsCopy = ulMsgs;      /* workaround - MS C code-gen bug */ 
 
    hr = HrOpenPropertyMessageRetry(pifld->peid, pifld->pims, TRUE, &lpmsg); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = HrSetOneROProp(lpmsg, plmr, PR_CONTENT_COUNT, &ulMsgsCopy); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = HrSetOneROProp(lpmsg, plmr, PR_CONTENT_UNREAD, &ulUnread); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = lpmsg->lpVtbl->SaveChanges(lpmsg, FORCE_SAVE); 
 
exit: 
    UlRelease(lpmsg); 
 
    DebugTraceResult(HrWriteCounts, hr); 
    return hr; 
} 
 
/* 
 * HrNewCounts 
 * 
 * Purpose  Make the contents count and unread count of the folder in pifld 
 *          agree with the data in the contents table lptbl 
 * 
 * Parameters 
 *      pifld       the folder 
 *      lptbl       the table 
 */ 
static HRESULT HrNewCounts(PIFLD pifld, LPTABLEDATA lptbl) 
{ 
    HRESULT hr = hrSuccess; 
    ULONG cMessages = 0; 
    ULONG cUnread = 0; 
    LPSRow lpsRow = NULL; 
    ULONG ulRow = 0; 
    PLMR plmr = &pifld->pims->lmr; 
    LPMESSAGE lpmsg = NULL; 
 
    /* check each row in the table to see if the message has been read */ 
 
    while (TRUE) 
    { 
        LPSPropValue pval; 
        LPSPropValue pvalMax; 
 
        hr = lptbl->lpVtbl->HrEnumRow(lptbl, ulRow++, &lpsRow); 
        if (hr != hrSuccess) 
            goto exit; 
 
        if (lpsRow == NULL) 
            break; 
 
        cMessages++; 
 
        pval = lpsRow->lpProps; 
        pvalMax = pval + lpsRow->cValues; 
 
        /* check PR_MESSAGE_FLAGS to see if this message is unread */ 
        while (pval < pvalMax) 
        { 
            if (pval->ulPropTag == PR_MESSAGE_FLAGS) 
            { 
                if (!(pval->Value.l & MSGFLAG_READ)) 
                    cUnread++; 
                break; 
            } 
            pval++; 
        } 
 
        LMFree(&pifld->pims->lmr, lpsRow); 
        lpsRow = NULL; 
    } 
 
    hr = HrWriteCounts(pifld, cMessages, cUnread); 
 
exit: 
    LMFree(&pifld->pims->lmr, lpsRow); 
 
    DebugTraceResult(HrNewCounts, hr); 
    return hr; 
} 
 
/********************************************************************** 
 * HrGetTableName 
 * 
 * Purpose 
 *      Given an open object and the kind of table 
 *      returns the full path name of the file that caches that table's data. 
 *      Must be freed with FreeNull. 
 * 
 * Parameters 
 *  pobj        object whose table is needed 
 *  szEIDPath   EID path (may be NULL) 
 *  szFileName  Name of the file that holds the table. 
 *  lppszTable  pointer to storage for the path name of the table file 
 */ 
HRESULT HrGetTableName(POBJ pobj, LPSTR szEIDPath, LPSTR szFileName, 
    LPSTR *pszTable) 
{ 
    LPTSTR szDir = NULL;        /* Full Path name of directory containing table */ 
    HRESULT hr = hrSuccess; 
    PIMS pims = pobj->pims; 
 
    Assert(!IsBadWritePtr(pszTable, sizeof(LPTSTR))); 
 
    hr = HrFullPathName(pims->szStorePath, szEIDPath, NULL, &szDir); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* get memory to hold the filename that contains the table. */ 
    /* Add 1 char for the backslash before the filename. */ 
 
    hr = HrAlloc(CCH_NAME * sizeof(TCHAR) 
        + ((lstrlen(szDir) + 1) * sizeof(TCHAR)), (PPV) pszTable); 
    if (hr != hrSuccess) 
        goto exit; 
 
    lstrcpy(*pszTable, szDir); 
    lstrcat(*pszTable, "\\"); 
    lstrcat(*pszTable, szFileName); 
 
exit: 
    FreeNull(szDir); 
    DebugTraceResult(HrGetTableName, hr); 
    return hr; 
} 
 
/********************************************************************** 
 * HrReadBytes 
 * 
 * Read a number of bytes from a file into a buffer, checking for errors and 
 * end-of-file. 
 * 
 * Parameters: 
 * 
 * hFile:       File handle to read from. 
 * lpBuffer:    Pointer to a buffer to read into. The buffer should be big 
 *              enough to hold the incoming data. 
 * cbToRead:    The number of bytes to read. Should be <= UINT_MAX. 
 * pfEOF:       A pointer to the location to return a boolean specifying 
 *              whether the read encountered an end-of-file. If it did, the 
 *              buffer will return with no data in it. This pointer may be NULL, 
 *              in which case end-of-file will be treated as an error. 
 * 
 * Returns: 
 *  HRESULT     (either a file read error, or MAPI_E_CALL_FAILED when either 
 *              EOF is encountered and pfEOF is NULL, or when the number of 
 *              bytes read is less than the number requested). 
 */ 
static HRESULT HrReadBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToRead, 
    BOOL *pfEOF) 
{ 
    SCODE sc = S_OK; 
    ULONG cbRead = 0; 
    BOOL fSuccess; 
 
    AssertSz(cbToRead <= UINT_MAX && !IsBadWritePtr(lpBuffer, (UINT) cbToRead), 
        "Bad buffer"); 
 
    fSuccess = ReadFile(hFile, lpBuffer, cbToRead, &cbRead, NULL); 
 
    TranslateFileError(fSuccess, cbToRead, cbRead, pfEOF, &sc); 
 
    DebugTraceSc(HrReadBytes, sc); 
    return ResultFromScode(sc); 
} 
 
/********************************************************************** 
 * HrWriteBytes 
 * 
 * Write a number of bytes from a buffer into a file, checking for errors. 
 * 
 * Parameters: 
 * 
 * hFile:       File handle to write into. 
 * lpBuffer:    Pointer to a buffer containing the data to write. 
 * cbToRead:    The number of bytes to write. Should be <= UINT_MAX. 
 * 
 * Returns: 
 *  HRESULT     (either a file write error, or MAPI_E_CALL_FAILED when the 
 *              number of bytes written is less than the number requested). 
 */ 
static HRESULT HrWriteBytes(HANDLE hFile, LPVOID lpBuffer, ULONG cbToWrite) 
{ 
    SCODE sc = S_OK; 
    ULONG cbWritten = 0; 
    BOOL fSuccess; 
 
    AssertSz(cbToWrite <= UINT_MAX && !IsBadReadPtr(lpBuffer, (UINT) cbToWrite), 
        "Bad buffer"); 
 
    fSuccess = WriteFile(hFile, lpBuffer, cbToWrite, &cbWritten, NULL); 
 
    TranslateFileError(fSuccess, cbToWrite, cbWritten, NULL, &sc); 
 
    DebugTraceSc(HrWriteBytes, sc); 
    return ResultFromScode(sc); 
} 
 
/********************************************************************** 
 * TranslateFileError 
 * 
 * Checks for errors from a windows ReadFile or WriteFile call, translating 
 * windows error codes to MAPI errors, and, if requested, checking for end-of- 
 * file. Also checks to make sure the number of bytes read or written equals 
 * the number given as input. 
 * 
 * Parameters: 
 * 
 * fSuccess:    The return value from ReadFile or WriteFile. 
 * cbIn:        The number of bytes given as input to ReadFile or WriteFile. 
 * cbOut:       The number of bytes returned from ReadFile or WriteFile. 
 * pfEOF:       A pointer to the location to return a BOOL specifying 
 *              whether cbOut was 0 and fSuccess was TRUE. This condition 
 *              indicates end-of-file when reading, and should not be treated 
 *              as an error. The pointer may be NULL, in which case, this 
 *              condition is treated as an error, and *psc is returned with 
 *              MAPI_E_CALL_FAILED. 
 * psc:         A pointer to the location to return an SCODE indicating 
 *              the success or failure of the ReadFile or WriteFile. 
 * 
 * Returns: VOID 
 */ 
static VOID TranslateFileError(BOOL fSuccess, ULONG cbIn, ULONG cbOut, 
    BOOL *pfEOF, SCODE *pscFile) 
{ 
    SCODE sc = S_OK; 
    BOOL fEOF = FALSE; 
    BOOL fCheckEOF = (pfEOF != NULL); 
 
    if (!fSuccess) 
    { 
        DWORD dwError = GetLastError(); 
 
        if (dwError != 0) 
 
            switch (dwError) 
            { 
            case 0: 
                TraceSz("SampleMS: ScTranslateFileError found unexpected " 
                    "success\n"); 
                break; 
 
            case ERROR_SHARING_VIOLATION: 
            case ERROR_LOCK_VIOLATION: 
                sc = MAPI_E_BUSY; 
                break; 
 
            case ERROR_FILE_NOT_FOUND: 
                sc = MAPI_E_NOT_FOUND; 
                break; 
 
            case ERROR_TOO_MANY_OPEN_FILES: 
                sc = MAPI_E_NOT_ENOUGH_RESOURCES; 
                break; 
 
            case ERROR_ACCESS_DENIED: 
            case ERROR_INVALID_ACCESS: 
            case ERROR_INVALID_DRIVE: 
                sc = MAPI_E_NO_ACCESS; 
                break; 
 
            default: 
                sc = MAPI_E_CALL_FAILED; 
                break; 
            } 
    } 
    else if (cbOut != cbIn) 
    { 
        sc = MAPI_E_CALL_FAILED; 
 
        /* If the caller wants to discriminate end-of-file from other */ 
        /* errors, and the file call returned zero bytes, return EOF, */ 
        /* and don't return an error. */ 
 
        if (fCheckEOF && cbOut == 0) 
        { 
            sc = S_OK; 
            fEOF = TRUE; 
        } 
    } 
 
    AssertSz(!IsBadWritePtr(pscFile, sizeof(SCODE)), "Bad parameter (pscFile) " 
        "given to TranslateFileError"); 
 
    *pscFile = sc; 
 
    if (pfEOF) 
    { 
        AssertSz(!IsBadWritePtr(pfEOF, sizeof(BOOL)), "Bad parameter (pfEOF) " 
            "given to TranslateFileError"); 
        *pfEOF = fEOF; 
    } 
 
    return; 
} 
 
/* 
 * HrWriteRow 
 * 
 *  Purpose 
 *      Writes one row of data to the file handle given. 
 * 
 *  Arguments 
 *      hFile: File handle to update. 
 *      prw: Pointer to the row to write. 
 * 
 *  RETURNS: HRESULT 
 */ 
static HRESULT HrWriteRow(HANDLE hFile, LPSRow prw) 
{ 
    HRESULT hr = hrSuccess; 
    SCODE sc; 
    ULONG cb; 
    PDRW pdrw = NULL; 
 
    sc = ScCountProps((UINT) prw->cValues, prw->lpProps, &cb); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto exit; 
    } 
 
    /* Allocate space for the disk row */ 
    hr = HrAlloc(CbNewDRW(cb), &pdrw); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* fill in the disk row structure */ 
 
    pdrw->cbRow = cb; 
    pdrw->rw.cValues = prw->cValues; 
 
    /* The lpProps field of the row we write should be point at the */ 
    /* row we are writing (i.e., pdrw->ab). We will use that pointer */ 
    /* value when we read the data off disk to fixup the pointers via */ 
    /* ScRelocProps. */ 
    pdrw->rw.lpProps = (LPSPropValue) pdrw->ab; 
 
    sc = ScCopyProps((UINT) prw->cValues, prw->lpProps, &(pdrw->ab), NULL); 
    if (sc != S_OK) 
    { 
        hr = ResultFromScode(sc); 
        goto exit; 
    } 
 
    /* write the row */ 
    hr = HrWriteBytes(hFile, pdrw, CbDRW(pdrw)); 
    if (hr != hrSuccess) 
        goto exit; 
 
exit: 
    FreeNull(pdrw); 
     
    DebugTraceResult(HrWriteRow, hr); 
    return hr; 
} 
 
/************************************************************************* 
 * HrWriteTableOnDisk 
 * 
 * Purpose 
 *      write the table in lptbl to the disk image of lpvObjects table 
 *      of type ulType 
 * 
 *  lptbl       Table to be written 
 *  pobj        object whose table is to be written 
 *  szEIDPath   Pathname of the EID of the folder, or NULL 
 *  szFileName  Name of the file that holds this table type. 
 * 
 */ 
HRESULT HrWriteTableOnDisk(LPTABLEDATA lptbl, POBJ pobj, LPSTR szEIDPath, 
    LPSTR szFileName) 
{ 
    LPTSTR szFile = NULL; 
    HANDLE hFile = INVALID_HANDLE_VALUE; /* handle to open file */ 
    LPSRow lpsRow = NULL;               /* next row to be written */ 
    ULONG ulRowNumber;          /* number of the row being written */ 
    HRESULT hr = hrSuccess; 
    PLMR plmr = &pobj->pims->lmr; 
 
    /* get the name of the file holding the table */ 
    hr = HrGetTableName(pobj, szEIDPath, szFileName, &szFile); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* open the file with exclusive access */ 
 
    hr = HrOpenTblFileRetry(szFile, GENERIC_WRITE, 0L, CREATE_ALWAYS, &hFile); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* write the table data to the file */ 
    ulRowNumber = 0; 
    while (TRUE) 
    { 
        hr = lptbl->lpVtbl->HrEnumRow(lptbl, ulRowNumber, &lpsRow); 
        if (hr != hrSuccess) 
            goto exit; 
        if (lpsRow == NULL) 
            break; 
 
        hr = HrWriteRow(hFile, lpsRow); 
        if (hr != hrSuccess) 
            goto exit; 
 
        LMFree(plmr, lpsRow); 
        lpsRow = NULL; 
        ulRowNumber++; 
    } 
 
exit: 
    AssertSz(GetScode(hr) != MAPI_W_ERRORS_RETURNED, 
        "Unexpected warning return"); 
 
    /* Set the end of file marker, in case we shrank the file. */ 
    if (hr == hrSuccess && SetEndOfFile(hFile) == FALSE) 
        hr = ResultFromScode(MAPI_E_DISK_ERROR); 
 
    if (hFile != INVALID_HANDLE_VALUE) 
        CloseHandle(hFile); 
 
    /* erase the file if in error */ 
    if (hr != hrSuccess && szFile) 
        DeleteFile(szFile); 
 
    FreeNull(szFile); 
    LMFree(plmr, lpsRow); 
 
    DebugTraceResult(HrWriteTableOnDisk, hr); 
    return hr; 
} 
 
/************************************************************************* 
 * HrReadTableFromDisk 
 * 
 * Purpose 
 *      Read a table from this disk cache into the table lptbl 
 * 
 *  lptbl       the table to be built 
 *  pobj        object whose table is to be read 
 *  szEIDPath   Pathname of the EID of the folder, or NULL 
 *  cCols       Number of columns in this type of table 
 *  szFileName  Name of the file that holds this table type. 
 */ 
HRESULT HrReadTableFromDisk(LPTABLEDATA lptbl, POBJ pobj, LPSTR szEIDPath, 
    ULONG cCols, LPSTR szFileName) 
{ 
    HRESULT hr; 
    PLMR    plmr; 
    BOOL    fBadTableData   = FALSE; 
    LPTSTR  szFile          = NULL; 
    HANDLE  hFile           = INVALID_HANDLE_VALUE; 
    PDRW    pdrw            = NULL; 
 
    plmr = &pobj->pims->lmr; 
 
    /* get the name of the file holding the table */ 
    hr = HrGetTableName(pobj, szEIDPath, szFileName, &szFile); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* open the file */ 
    hr = HrOpenTblFileRetry(szFile, GENERIC_READ, FILE_SHARE_READ, 
        OPEN_ALWAYS, &hFile); 
    if (hr != hrSuccess) 
    { 
        /* If the file wasn't found, then simply leave the table empty, */ 
        /* and return success. */ 
 
        if (GetScode(hr) == MAPI_E_NOT_FOUND) 
            hr = hrSuccess; 
 
        goto exit; 
    } 
 
    while (TRUE) 
    { 
        BOOL fEOF; 
        DRW drwTemp; 
        ULONG cbOut; 
        SCODE sc; 
 
        /* Read the beginning of the disk row. */ 
        hr = HrReadBytes(hFile, &drwTemp, CbNewDRW(0), &fEOF); 
        if (hr != hrSuccess) 
            goto exit; 
 
        if (fEOF) 
            break; 
 
        /* Sanity check for bad data. */ 
        /* Note that the number of columns in the disk version of the */ 
        /* table can be less than the number of columns in the in-memory */ 
        /* table due to properties that were missing from the message */ 
        /* when it was written to disk. */ 
 
        if (    drwTemp.rw.cValues > cCols 
            ||  drwTemp.rw.cValues == 0 
            ||  drwTemp.cbRow < drwTemp.rw.cValues * sizeof(SPropValue)) 
        { 
            hr = ResultFromScode(MAPI_E_CORRUPT_DATA); 
            fBadTableData = TRUE; 
            goto exit; 
        } 
 
        hr = HrAlloc(CbNewDRW(drwTemp.cbRow), &pdrw); 
        if (hr != hrSuccess) 
            goto exit; 
 
        memcpy(pdrw, &drwTemp, CbNewDRW(0)); 
 
        /* read the rest of the row */ 
        hr = HrReadBytes(hFile, &pdrw->ab, pdrw->cbRow, NULL); 
        if (hr != hrSuccess) 
        { 
            fBadTableData = TRUE; 
            goto exit; 
        } 
 
        sc = ScRelocProps((UINT) pdrw->rw.cValues, (LPSPropValue) pdrw->ab, 
            pdrw->rw.lpProps, &pdrw->ab, &cbOut); 
        if (sc != S_OK || cbOut != pdrw->cbRow) 
        { 
            hr = ResultFromScode(MAPI_E_CORRUPT_DATA); 
            fBadTableData = TRUE; 
            goto exit; 
        } 
 
        pdrw->rw.lpProps = (LPSPropValue) pdrw->ab; 
 
        /* add this row to the table */ 
        hr = lptbl->lpVtbl->HrModifyRow(lptbl, &pdrw->rw); 
        if (hr != hrSuccess) 
            goto exit; 
 
        FreeNull(pdrw); 
        pdrw = NULL; 
    } 
 
exit: 
    if (hFile != INVALID_HANDLE_VALUE) 
        CloseHandle(hFile); 
 
    /* erase the file if it is bogus. We will regenerate it if we can. */ 
    if (fBadTableData) 
        DeleteFile(szFile); 
 
    FreeNull(szFile); 
    FreeNull(pdrw); 
 
    DebugTraceResult(HrReadTableFromDisk, hr); 
    return hr; 
} 
 
/************************************************************************ 
 * HrGetTime 
 * 
 * Purpose  return the value of PR_LAST_MODIFICATION_TIME from the properties 
 *          in the given property array 
 * 
 * Parameters 
 *  prw         pointer to the table row to search 
 *  pfiletime   pointer to last modification time 
 */ 
static HRESULT HrGetTime(LPSRow prw, FILETIME *pfiletime) 
{ 
    LPSPropValue pvalT; 
    LPSPropValue pvalMax; 
    SCODE sc = MAPI_E_NOT_FOUND; 
 
    Assert(!IsBadReadPtr(prw, sizeof(SRow))); 
    Assert(prw->cValues <= UINT_MAX / sizeof(SPropValue)); 
    Assert(!IsBadReadPtr(prw->lpProps, ((UINT) prw->cValues) * sizeof(SPropValue))); 
    Assert(!IsBadWritePtr(pfiletime, sizeof(FILETIME))); 
 
    pvalT = prw->lpProps; 
    pvalMax = pvalT + prw->cValues; 
 
    while (pvalT < pvalMax) 
    { 
        if (pvalT->ulPropTag == PR_LAST_MODIFICATION_TIME) 
        { 
            sc = S_OK; 
            *pfiletime = pvalT->Value.ft; 
            break; 
        } 
        pvalT++; 
    } 
 
    DebugTraceSc(HrGetTime, sc); 
    return ResultFromScode(sc); 
} 
 
/************************************************************************* 
 * HrSyncOutgoingTable 
 * 
 * Purpose 
 *      Verifies that every table row in memory actually corresponds to 
 *      a message on disk. If bad rows are found, removes them and 
 *      rewrites the disk version of the table data. Note that this code 
 *      does not verify that the message on disk actually is in the queue, 
 *      nor does it check for messages that are somehow missing from the table. 
 * 
 *  lptbl           pointer to the outgoing table data object. 
 *  pims            a pointer to the message store object. 
 * 
 */ 
HRESULT HrSyncOutgoingTable(LPTABLEDATA lptbl, PIMS pims) 
{ 
    HRESULT hr; 
    BOOL fRowsRemoved; 
 
    hr = HrRemoveBadTableRows(lptbl, NULL, pims, &fRowsRemoved); 
 
    if (hr == hrSuccess && fRowsRemoved) 
        hr = HrWriteTableOnDisk(lptbl, (POBJ) pims, NULL, szOutgoingFileName); 
 
    DebugTraceResult(HrSyncOutgoingTable, hr); 
    return hr; 
} 
 
/************************************************************************* 
 * HrSyncContentsTable 
 * 
 * Purpose 
 *      Verifies that the table in memory agrees with what's on disk. 
 *      If discrepancies are found, fixes them, and rewrites the disk 
 *      version of the table data. 
 * 
 *  pifld           the parent folder of the contents table. 
 *  fWriteTable     if TRUE, write the disk version of the table if the 
 *                  in-memory table is out of sync. 
 * 
 */ 
HRESULT HrSyncContentsTable(PIFLD pifld, BOOL fWriteTable) 
{ 
    HRESULT hr; 
    BOOL fRowsRemoved; 
    BOOL fRowsAdded; 
    LPTABLEDATA lptbl; 
 
    lptbl = pifld->lptblContents; 
 
    hr = HrRemoveBadTableRows(lptbl, pifld, pifld->pims, &fRowsRemoved); 
    if (hr != hrSuccess) 
        goto exit; 
 
    hr = HrAddMissingTableRows(lptbl, pifld, szMessageTemplate, 
        (LPSPropTagArray) &sPropTagsContents, &fRowsAdded); 
    if (hr != hrSuccess) 
        goto exit; 
 
    /* update folder's content count and unread count if */ 
    /* the table was out of ssync */ 
 
    if (fRowsRemoved || fRowsAdded) 
    { 
        hr = HrNewCounts(pifld, lptbl); 
        if (hr != hrSuccess) 
            goto exit; 
 
        if (fWriteTable) 
        { 
            hr = HrWriteTableOnDisk(lptbl, (POBJ) pifld, pifld->peid->szPath, 
                szContentsFileName); 
            if (hr != hrSuccess) 
                goto exit; 
        } 
    } 
    else 
    { 
        /* If there aren't any rows in the table, we still need to 
         * update the folder's count of messages, because the counts  
         * may not be zero, even though there aren't any messages on disk. 
         */ 
        LPSRow lpSRow = NULL; 
 
        hr = lptbl->lpVtbl->HrEnumRow(lptbl, 0, &lpSRow); 
        if (hr != hrSuccess) 
            goto exit; 
 
        if (lpSRow == NULL) 
            hr = HrWriteCounts(pifld, 0, 0); 
 
        LMFree(&pifld->pims->lmr, lpSRow); 
    } 
 
exit: 
    DebugTraceResult(HrSyncContentsTable, hr); 
    return hr; 
} 
 
/************************************************************************* 
 * HrRemoveBadTableRows 
 * 
 * Purpose 
 *      Verifies that every row in the table given has a corresponding 
 *      file on disk. If the file on disk does not exist, the routine 
 *      removes the row from the table. 
 * 
 *  lptbl           pointer to table 
 *  pifldParent     the folder that all objects should be in. May be NULL. 
 *  pims            the message store object 
 *  pfTableChanged  Pointer to a location to return a BOOL that, when TRUE, 
 *                  indicates that a bad row was found, and the table was changed 
 * 
 */ 
static HRESULT HrRemoveBadTableRows(LPTABLEDATA lptbl, PIFLD pifldParent, 
    PIMS pims, BOOL *pfTableChanged) 
{ 
    HRESULT hr = hrSuccess; 
    LPSRow lpSRow = NULL; 
    BOOL fTableChanged = FALSE; /* TRUE if lptbl was changed */ 
    ULONG iRow = 0; 
 
    while (TRUE) 
    { 
        LPSTR szPath; 
        BOOL fIsParent = TRUE; 
        PEID peid; 
 
        hr = lptbl->lpVtbl->HrEnumRow(lptbl, iRow++, &lpSRow); 
 
        if (hr != hrSuccess || lpSRow == NULL) 
            break; 
 
        peid = (PEID) lpSRow->lpProps->Value.bin.lpb; 
 
        /* If the caller gave us a parent folder, verify that the message */ 
        /* is in that folder. For example, if the caller is checking the */ 
        /* contents table, we can check that all messages are in the folder */ 
        /* they should be, but for the outgoing queue table, we can't. */ 
 
        if (pifldParent) 
        { 
            hr = HrIsParent(pifldParent->peid, peid, &fIsParent); 
            if (hr != hrSuccess) 
                goto exit; 
        } 
 
        hr = HrFullPathName(pims->szStorePath, peid->szPath, NULL, &szPath); 
        if (hr != hrSuccess) 
            goto exit; 
 
        /* Delete the row if it no longer exists on disk */ 
        /* or isn't in the correct folder */ 
        if ((GetFileAttributes(szPath) == -1) || !fIsParent) 
        { 
            HRESULT hrT; 
 
            hrT = lptbl->lpVtbl->HrDeleteRow(lptbl, lpSRow->lpProps); 
 
            if (hrT != hrSuccess) 
            { 
                TraceSz1("SampleMS: HrSyncTableWithDisk: error %s " 
                    "deleting out-of-date table entry\n", 
                    SzDecodeScode(GetScode(hrT))); 
            } 
 
            fTableChanged = TRUE; 
        } 
 
        LMFree(&pims->lmr, lpSRow); 
        lpSRow = NULL; 
        FreeNull(szPath); 
    } 
 
    AssertSz(!IsBadWritePtr(pfTableChanged, sizeof(BOOL)), "Bad parameter" 
        " (pfTableChanged) given to HrRemoveBadTableRows"); 
    *pfTableChanged = fTableChanged; 
 
exit: 
    LMFree(&pims->lmr, lpSRow); 
    DebugTraceResult(HrRemoveBadTableRows, hr); 
    return hr; 
} 
 
/************************************************************************* 
 * HrAddMissingTableRows 
 * 
 * Purpose 
 *      Searches a folder for objects of the specified type, and verifies 
 *      that each object exists in the table given, and is up-to-date. 
 *      If the object does not exist or is out-of-date, the routine 
 *      adds or updates the table row with current information from the object. 
 *      This routine only works for contents tables currently. 
 * 
 *  lptbl           pointer to table 
 *  pifld           the folder that all objects should be in. 
 *  szTemplate      type of files to search for for this table type 
 *  ptaga           list of proptags for this type of table 
 *  pfTableChanged  Pointer to a location to return a BOOL that, when TRUE, 
 *                  indicates that the table was changed to match the disk 
 * 
 */ 
static HRESULT HrAddMissingTableRows(LPTABLEDATA lptbl, PIFLD pifld, 
    LPSTR szTemplate, LPSPropTagArray ptaga, BOOL * pfTableChanged) 
{ 
    HRESULT hr; 
    WIN32_FIND_DATA ffd; 
    HANDLE hFindFile = INVALID_HANDLE_VALUE; 
    ULONG ulOffset; 
    LPTSTR szFile = NULL;       /* full path name of next file in the table */ 
PEID peid = NULL; 
    BOOL fTableChanged = FALSE; 
    LPSRow lpSRow = NULL; 
    SPropValue pvInstKey; 
    PLMR plmr = &pifld->pims->lmr; 
 
    /* for each file of the right kind ( message or folder ) on disk */ 
    /* make sure its entry in the cache is up to date */ 
 
    pvInstKey.ulPropTag = PR_INSTANCE_KEY; 
 
    /* get the next file */ 
    hr = HrFindFirstID(pifld, szTemplate, &ulOffset, &szFile, &hFindFile, 
        &ffd, &peid); 
 
    while (hr == hrSuccess) 
    { 
        FILETIME ftTableTime;   /* modify time of szFile in lptbl */ 
 
        /* get its row in the table */ 
        pvInstKey.Value.bin.cb = CbEID(peid); 
        pvInstKey.Value.bin.lpb = (LPBYTE) peid; 
 
        hr = lptbl->lpVtbl->HrQueryRow(lptbl, &pvInstKey, &lpSRow, NULL); 
 
        if (GetScode(hr) == MAPI_E_NOT_ENOUGH_MEMORY) 
            goto exit; 
 
        /* update the row if necessary */ 
        /* store in ftTableTime, the modify time of this object */ 
        /* as stored in the lptbl */ 
        if (hr == hrSuccess && lpSRow) 
            hr = HrGetTime(lpSRow, &ftTableTime); 
 
        if (hr != hrSuccess 
            || lpSRow == NULL 
            || CompareFileTime(&ftTableTime, &(ffd.ftLastWriteTime)) == -1) 
        { 
            fTableChanged = TRUE; 
 
            HrUpdateRow(pifld->pims, lptbl, peid, ptaga, &(ffd.ftLastWriteTime), 
                MAPI_MESSAGE); 
        } 
 
        LMFree(plmr, peid); 
        peid = NULL; 
        LMFree(plmr, lpSRow); 
        lpSRow = NULL; 
 
        hr = HrFindNextID(pifld, ulOffset, szFile, hFindFile, &ffd, &peid); 
    } 
 
    if (GetScode(hr) == MAPI_E_NOT_FOUND) 
        hr = hrSuccess; 
 
    if (hr == hrSuccess) 
    { 
        AssertSz(!IsBadWritePtr(pfTableChanged, sizeof(BOOL)), "Bad parameter" 
            " (pfTableChanged) given to HrAddMissingTableRows"); 
        *pfTableChanged = fTableChanged; 
    } 
 
exit: 
    CloseIDSearch(&hFindFile, &szFile); 
    LMFree(plmr, lpSRow); 
    LMFree(plmr, peid); 
 
    DebugTraceResult(HrAddMissingTableRows, hr); 
    return hr; 
}