Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1360 lines
39 KiB

  1. /*
  2. * recon.c - Reconciliation routines.
  3. */
  4. /* Headers
  5. **********/
  6. #include "project.h"
  7. #pragma hdrstop
  8. #include "stub.h"
  9. #include "oleutil.h"
  10. /***************************** Private Functions *****************************/
  11. /* Module Prototypes
  12. ********************/
  13. PRIVATE_CODE void GenerateShellEvents(PCRECITEM);
  14. PRIVATE_CODE TWINRESULT MyReconcileItem(PCRECITEM, RECSTATUSPROC, LPARAM, DWORD, HWND, HWND);
  15. PRIVATE_CODE void UpdateObjectTwinStates(PCRECITEM);
  16. PRIVATE_CODE TWINRESULT CopyFolder(PCRECITEM, RECSTATUSPROC, LPARAM);
  17. PRIVATE_CODE TWINRESULT DeleteFolder(PCRECITEM, RECSTATUSPROC, LPARAM);
  18. PRIVATE_CODE TWINRESULT DealWithCopy(PCRECITEM, RECSTATUSPROC, LPARAM, DWORD, HWND, HWND);
  19. PRIVATE_CODE TWINRESULT DealWithMerge(PCRECITEM, RECSTATUSPROC, LPARAM, DWORD, HWND, HWND);
  20. PRIVATE_CODE TWINRESULT DealWithDelete(PCRECITEM, RECSTATUSPROC, LPARAM);
  21. PRIVATE_CODE ULONG CountRECNODEs(PCRECITEM, RECNODEACTION);
  22. PRIVATE_CODE TWINRESULT UpdateRecNodeFileStamps(PCRECITEM);
  23. PRIVATE_CODE BOOL DeletedTwinsInRecItem(PCRECITEM);
  24. /*
  25. ** GenerateShellEvents()
  26. **
  27. ** Notifies the Shell about reconciliation events for a RECITEM.
  28. **
  29. ** Arguments: pcri - reconciled RECITEM to notify Shell about
  30. **
  31. ** Returns: void
  32. **
  33. ** Side Effects: None
  34. */
  35. PRIVATE_CODE void GenerateShellEvents(PCRECITEM pcri)
  36. {
  37. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  38. /* Any reconciliation events to report? */
  39. if (pcri->riaction == RIA_NOTHING ||
  40. pcri->riaction == RIA_COPY ||
  41. pcri->riaction == RIA_MERGE ||
  42. pcri->riaction == RIA_DELETE)
  43. {
  44. PRECNODE prn;
  45. /*
  46. * Yes. Send an appropriate notification to the Shell about the file
  47. * operations assumed carried out during reconciliation. The file system
  48. * unfortunately does not support notifications for some of the
  49. * interesting reconciliation operations. We also generate a specious
  50. * update notification for the source file in a copy operation to cause
  51. * the Briefcase ui to recalculate the status string for that file, even
  52. * though the file itself has not changed.
  53. */
  54. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  55. {
  56. BOOL bNotify;
  57. NOTIFYSHELLEVENT nse;
  58. LPCTSTR pcszPath;
  59. TCHAR rgchPath[MAX_PATH_LEN];
  60. /* How shall I notify you? Let me enumerate the ways. */
  61. bNotify = TRUE;
  62. if (IsFolderObjectTwinName(pcri->pcszName))
  63. {
  64. nse = NSE_UPDATE_FOLDER;
  65. pcszPath = prn->pcszFolder;
  66. switch (prn->rnaction)
  67. {
  68. /*
  69. * Notifications about folders that were copied or deleted
  70. * during reconciliation were sent during reconciliation. Don't
  71. * send redundant notifications.
  72. */
  73. case RNA_COPY_TO_ME:
  74. case RNA_DELETE_ME:
  75. bNotify = FALSE;
  76. break;
  77. default:
  78. ASSERT(prn->rnaction == RNA_NOTHING ||
  79. prn->rnaction == RNA_COPY_FROM_ME);
  80. break;
  81. }
  82. }
  83. else
  84. {
  85. nse = NSE_UPDATE_ITEM;
  86. ComposePath(rgchPath, prn->pcszFolder, pcri->pcszName, ARRAYSIZE(rgchPath));
  87. pcszPath = rgchPath;
  88. switch (prn->rnaction)
  89. {
  90. case RNA_COPY_TO_ME:
  91. if (prn->rnstate == RNS_DOES_NOT_EXIST ||
  92. prn->rnstate == RNS_DELETED)
  93. nse = NSE_CREATE_ITEM;
  94. break;
  95. case RNA_DELETE_ME:
  96. nse = NSE_DELETE_ITEM;
  97. break;
  98. default:
  99. ASSERT(prn->rnaction == RNA_NOTHING ||
  100. prn->rnaction == RNA_COPY_FROM_ME ||
  101. prn->rnaction == RNA_MERGE_ME);
  102. break;
  103. }
  104. }
  105. if (bNotify)
  106. NotifyShell(pcszPath, nse);
  107. }
  108. }
  109. }
  110. /*
  111. ** MyReconcileItem()
  112. **
  113. ** Reconciles a reconciliation item.
  114. **
  115. ** Arguments: pcri - pointer to reconciliation item to be reconciled
  116. **
  117. ** Side Effects:
  118. */
  119. PRIVATE_CODE TWINRESULT MyReconcileItem(PCRECITEM pcri, RECSTATUSPROC rsp,
  120. LPARAM lpCallbackData, DWORD dwFlags,
  121. HWND hwndOwner,
  122. HWND hwndProgressFeedback)
  123. {
  124. TWINRESULT tr;
  125. /* lpCallbackData may be any value. */
  126. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  127. ASSERT(! rsp ||
  128. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
  129. ASSERT(FLAGS_ARE_VALID(dwFlags, ALL_RI_FLAGS));
  130. ASSERT(IS_FLAG_CLEAR(dwFlags, RI_FL_ALLOW_UI) ||
  131. IS_VALID_HANDLE(hwndOwner, WND));
  132. ASSERT(IS_FLAG_CLEAR(dwFlags, RI_FL_FEEDBACK_WINDOW_VALID) ||
  133. IS_VALID_HANDLE(hwndProgressFeedback, WND));
  134. #ifdef DEBUG
  135. {
  136. LPCTSTR pcszGerund;
  137. switch (pcri->riaction)
  138. {
  139. case RIA_NOTHING:
  140. pcszGerund = TEXT("Taking no action on");
  141. break;
  142. case RIA_COPY:
  143. pcszGerund = TEXT("Copying");
  144. break;
  145. case RIA_MERGE:
  146. pcszGerund = TEXT("Merging");
  147. break;
  148. case RIA_BROKEN_MERGE:
  149. pcszGerund = TEXT("Broken merge for");
  150. break;
  151. case RIA_DELETE:
  152. pcszGerund = TEXT("Deleting");
  153. break;
  154. default:
  155. pcszGerund = TEXT("Unknown action specifed for");
  156. break;
  157. }
  158. TRACE_OUT((TEXT("MyReconcileItem(): %s %s."),
  159. pcszGerund,
  160. *(pcri->pcszName) ? pcri->pcszName : TEXT("folder")));
  161. }
  162. #endif
  163. switch (pcri->riaction)
  164. {
  165. case RIA_NOTHING:
  166. tr = TR_SUCCESS;
  167. break;
  168. case RIA_COPY:
  169. if (*(pcri->pcszName))
  170. tr = DealWithCopy(pcri, rsp, lpCallbackData, dwFlags, hwndOwner,
  171. hwndProgressFeedback);
  172. else
  173. tr = CopyFolder(pcri, rsp, lpCallbackData);
  174. if (tr == TR_SUCCESS)
  175. tr = UpdateRecNodeFileStamps(pcri);
  176. break;
  177. case RIA_MERGE:
  178. tr = DealWithMerge(pcri, rsp, lpCallbackData, dwFlags, hwndOwner,
  179. hwndProgressFeedback);
  180. if (tr == TR_SUCCESS || tr == TR_MERGE_INCOMPLETE)
  181. tr = UpdateRecNodeFileStamps(pcri);
  182. break;
  183. case RIA_DELETE:
  184. if (*(pcri->pcszName))
  185. tr = DealWithDelete(pcri, rsp, lpCallbackData);
  186. else
  187. {
  188. tr = DeleteFolder(pcri, rsp, lpCallbackData);
  189. if (tr == TR_DEST_WRITE_FAILED)
  190. tr = TR_SUCCESS;
  191. }
  192. if (tr == TR_SUCCESS)
  193. tr = UpdateRecNodeFileStamps(pcri);
  194. break;
  195. default:
  196. ASSERT(pcri->riaction == RIA_BROKEN_MERGE);
  197. tr = TR_NO_MERGE_HANDLER;
  198. break;
  199. }
  200. /*
  201. * Only update briefcase time stamps if the entire reconciliation operation
  202. * on this RECITEM is successful. However, the RECNODE time stamps in the
  203. * given RECITEM have been updated as they were changed.
  204. */
  205. if (tr == TR_SUCCESS)
  206. {
  207. UpdateObjectTwinStates(pcri);
  208. DetermineDeletionPendingState(pcri);
  209. DeleteTwinsFromRecItem(pcri);
  210. }
  211. else if (tr == TR_MERGE_INCOMPLETE)
  212. tr = TR_SUCCESS;
  213. if (tr == TR_SUCCESS)
  214. GenerateShellEvents(pcri);
  215. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  216. return(tr);
  217. }
  218. /*
  219. ** UpdateObjectTwinStates()
  220. **
  221. ** Updates the last reconciliation time stamp of the twin family and the object
  222. ** twins associated with a RECITEM that has just been successfully reconciled.
  223. **
  224. ** Arguments: pri - pointer to reconciliation item that has just been
  225. ** reconciled
  226. **
  227. ** Returns: TWINRESULT
  228. **
  229. ** Side Effects: Implicitly deletes twin family if the last known state of
  230. ** every component object twins is non-existence.
  231. **
  232. ** N.b., this function assumes that the actions specified in the RECNODEs of
  233. ** the RECITEM were carried out successfully.
  234. **
  235. ** This function assumes that all available RECNODES were reconciled.
  236. **
  237. ** This function assumes that the time stamp fields of the RECNODEs associated
  238. ** with objects that were modified during reconciliation were filled in
  239. ** immediately after each of those RECNODEs was reconciled. I.e., that all
  240. ** time stamp fields in reconciled RECNODEs are up-to-date with respect to any
  241. ** modifications that may have been made to them during reconciliation.
  242. */
  243. PRIVATE_CODE void UpdateObjectTwinStates(PCRECITEM pcri)
  244. {
  245. PRECNODE prn;
  246. BOOL bNewVersion = FALSE;
  247. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  248. /*
  249. * There is a new version if any changed or never reconciled RECNODEs were
  250. * reconciled as RNA_NOTHING, RNA_COPY_FROM_ME, or RNA_MERGE_ME.
  251. */
  252. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  253. {
  254. if ((prn->rnstate == RNS_NEVER_RECONCILED ||
  255. prn->rnstate == RNS_CHANGED) &&
  256. (prn->rnaction == RNA_NOTHING ||
  257. prn->rnaction == RNA_COPY_FROM_ME ||
  258. prn->rnaction == RNA_MERGE_ME))
  259. bNewVersion = TRUE;
  260. }
  261. /*
  262. * Save file stamps of available files. Mark unavailable object twins not
  263. * reconciled if any new versions exist in the reconciled set of files.
  264. */
  265. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  266. {
  267. POBJECTTWIN pot;
  268. pot = (POBJECTTWIN)(prn->hObjectTwin);
  269. if (prn->fsCurrent.fscond != FS_COND_UNAVAILABLE)
  270. {
  271. ClearStubFlag(&(pot->stub), STUB_FL_NOT_RECONCILED);
  272. pot->fsLastRec = prn->fsCurrent;
  273. /*
  274. * Remember not to delete object twins as requested. Treat any folder
  275. * that could not be deleted as implicitly kept as well.
  276. */
  277. if (IS_FLAG_SET(prn->dwFlags, RN_FL_DELETION_SUGGESTED) &&
  278. IsTwinFamilyDeletionPending((PCTWINFAMILY)(pcri->hTwinFamily)) &&
  279. (pcri->riaction == RIA_NOTHING ||
  280. pcri->riaction == RIA_DELETE) &&
  281. (prn->rnaction != RNA_DELETE_ME ||
  282. IsFolderObjectTwinName(pcri->pcszName)))
  283. {
  284. SetStubFlag(&(pot->stub), STUB_FL_KEEP);
  285. TRACE_OUT((TEXT("UpdateObjectTwinStates(): Object twin %s\\%s will be kept and not deleted."),
  286. prn->pcszFolder,
  287. prn->priParent->pcszName));
  288. }
  289. }
  290. else if (bNewVersion &&
  291. IsReconciledFileStamp(&(prn->fsLast)))
  292. {
  293. SetStubFlag(&(pot->stub), STUB_FL_NOT_RECONCILED);
  294. WARNING_OUT((TEXT("UpdateObjectTwinStates(): Marked %s\\%s not reconciled."),
  295. prn->pcszFolder,
  296. pcri->pcszName));
  297. }
  298. }
  299. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  300. return;
  301. }
  302. /*
  303. ** CopyFolder()
  304. **
  305. **
  306. **
  307. ** Arguments:
  308. **
  309. ** Returns:
  310. **
  311. ** Side Effects: none
  312. */
  313. PRIVATE_CODE TWINRESULT CopyFolder(PCRECITEM pcri, RECSTATUSPROC rsp,
  314. LPARAM lpCallbackData)
  315. {
  316. TWINRESULT tr;
  317. RECSTATUSUPDATE rsu;
  318. /* lpCallbackData may be any value. */
  319. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  320. ASSERT(! rsp ||
  321. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
  322. ASSERT(IsFolderObjectTwinName(pcri->pcszName));
  323. rsu.ulScale = CountRECNODEs(pcri, RNA_COPY_TO_ME);
  324. ASSERT(rsu.ulScale > 0);
  325. rsu.ulProgress = 0;
  326. if (NotifyReconciliationStatus(rsp, RS_BEGIN_COPY, (LPARAM)&rsu,
  327. lpCallbackData))
  328. {
  329. PRECNODE prn;
  330. tr = TR_SUCCESS;
  331. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  332. {
  333. if (prn->rnaction == RNA_COPY_TO_ME)
  334. tr = CreateFolders(prn->pcszFolder, (HPATH)(prn->hvid));
  335. }
  336. if (tr == TR_SUCCESS)
  337. {
  338. /* 100% complete. */
  339. rsu.ulProgress = rsu.ulScale;
  340. /* Don't allow abort. */
  341. NotifyReconciliationStatus(rsp, RS_END_COPY, (LPARAM)&rsu,
  342. lpCallbackData);
  343. }
  344. }
  345. else
  346. tr = TR_ABORT;
  347. return(tr);
  348. }
  349. /*
  350. ** DeleteFolder()
  351. **
  352. **
  353. **
  354. ** Arguments:
  355. **
  356. ** Returns:
  357. **
  358. ** Side Effects: none
  359. */
  360. PRIVATE_CODE TWINRESULT DeleteFolder(PCRECITEM pcri, RECSTATUSPROC rsp,
  361. LPARAM lpCallbackData)
  362. {
  363. TWINRESULT tr;
  364. RECSTATUSUPDATE rsu;
  365. /* lpCallbackData may be any value. */
  366. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  367. ASSERT(! rsp ||
  368. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
  369. ASSERT(IsFolderObjectTwinName(pcri->pcszName));
  370. rsu.ulScale = CountRECNODEs(pcri, RNA_DELETE_ME);
  371. ASSERT(rsu.ulScale > 0);
  372. rsu.ulProgress = 0;
  373. if (NotifyReconciliationStatus(rsp, RS_BEGIN_DELETE, (LPARAM)&rsu,
  374. lpCallbackData))
  375. {
  376. PRECNODE prn;
  377. tr = TR_SUCCESS;
  378. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  379. {
  380. if (prn->rnaction == RNA_DELETE_ME)
  381. tr = DestroySubtree(prn->pcszFolder, (HPATH)(prn->hvid));
  382. }
  383. if (tr == TR_SUCCESS)
  384. {
  385. /* 100% complete. */
  386. rsu.ulProgress = rsu.ulScale;
  387. /* Don't allow abort. */
  388. NotifyReconciliationStatus(rsp, RS_END_DELETE, (LPARAM)&rsu,
  389. lpCallbackData);
  390. }
  391. }
  392. else
  393. tr = TR_ABORT;
  394. return(tr);
  395. }
  396. /*
  397. ** DealWithCopy()
  398. **
  399. **
  400. **
  401. ** Arguments:
  402. **
  403. ** Returns:
  404. **
  405. ** Side Effects: none
  406. */
  407. PRIVATE_CODE TWINRESULT DealWithCopy(PCRECITEM pcri, RECSTATUSPROC rsp,
  408. LPARAM lpCallbackData, DWORD dwInFlags,
  409. HWND hwndOwner, HWND hwndProgressFeedback)
  410. {
  411. TWINRESULT tr;
  412. PRECNODE prnCopySrc;
  413. /* lpCallbackData may be any value. */
  414. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  415. ASSERT(! rsp ||
  416. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
  417. ASSERT(FLAGS_ARE_VALID(dwInFlags, ALL_RI_FLAGS));
  418. ASSERT(IS_FLAG_CLEAR(dwInFlags, RI_FL_ALLOW_UI) ||
  419. IS_VALID_HANDLE(hwndOwner, WND));
  420. ASSERT(IS_FLAG_CLEAR(dwInFlags, RI_FL_FEEDBACK_WINDOW_VALID) ||
  421. IS_VALID_HANDLE(hwndProgressFeedback, WND));
  422. tr = FindCopySource(pcri, &prnCopySrc);
  423. if (EVAL(tr == TR_SUCCESS))
  424. tr = CopyHandler(prnCopySrc, rsp, lpCallbackData, dwInFlags, hwndOwner,
  425. hwndProgressFeedback);
  426. return(tr);
  427. }
  428. /*
  429. ** DealWithMerge()
  430. **
  431. **
  432. **
  433. ** Arguments:
  434. **
  435. ** Returns:
  436. **
  437. ** Side Effects: none
  438. */
  439. PRIVATE_CODE TWINRESULT DealWithMerge(PCRECITEM pcri, RECSTATUSPROC rsp,
  440. LPARAM lpCallbackData, DWORD dwInFlags,
  441. HWND hwndOwner,
  442. HWND hwndProgressFeedback)
  443. {
  444. TWINRESULT tr;
  445. HRESULT hr;
  446. PRECNODE prnMergeDest;
  447. PRECNODE prnMergedResult;
  448. /* lpCallbackData may be any value. */
  449. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  450. ASSERT(! rsp ||
  451. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
  452. ASSERT(FLAGS_ARE_VALID(dwInFlags, ALL_RI_FLAGS));
  453. ASSERT(IS_FLAG_CLEAR(dwInFlags, RI_FL_ALLOW_UI) ||
  454. IS_VALID_HANDLE(hwndOwner, WND));
  455. ASSERT(IS_FLAG_CLEAR(dwInFlags, RI_FL_FEEDBACK_WINDOW_VALID) ||
  456. IS_VALID_HANDLE(hwndProgressFeedback, WND));
  457. ChooseMergeDestination(pcri, &prnMergeDest);
  458. hr = MergeHandler(prnMergeDest, rsp, lpCallbackData, dwInFlags, hwndOwner,
  459. hwndProgressFeedback, &prnMergedResult);
  460. if (hr == S_OK ||
  461. hr == REC_S_NOTCOMPLETEBUTPROPAGATE)
  462. {
  463. tr = CopyHandler(prnMergedResult, rsp, lpCallbackData, dwInFlags,
  464. hwndOwner, hwndProgressFeedback);
  465. if (tr == TR_SUCCESS)
  466. TRACE_OUT((TEXT("DealWithMerge(): Propagated merged result %s\\%s successfully."),
  467. prnMergedResult->pcszFolder,
  468. pcri->pcszName));
  469. else
  470. WARNING_OUT((TEXT("DealWithMerge(): Propagating merged result %s\\%s failed."),
  471. prnMergedResult->pcszFolder,
  472. pcri->pcszName));
  473. }
  474. else
  475. tr = TR_SUCCESS;
  476. return((tr == TR_SUCCESS) ? TranslateHRESULTToTWINRESULT(hr)
  477. : tr);
  478. }
  479. /*
  480. ** DealWithDelete()
  481. **
  482. **
  483. **
  484. ** Arguments:
  485. **
  486. ** Returns:
  487. **
  488. ** Side Effects: none
  489. */
  490. PRIVATE_CODE TWINRESULT DealWithDelete(PCRECITEM pcri, RECSTATUSPROC rsp,
  491. LPARAM lpCallbackData)
  492. {
  493. TWINRESULT tr;
  494. RECSTATUSUPDATE rsu;
  495. /* lpCallbackData may be any value. */
  496. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  497. ASSERT(! rsp ||
  498. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC));
  499. rsu.ulScale = CountRECNODEs(pcri, RNA_DELETE_ME);
  500. ASSERT(rsu.ulScale > 0);
  501. rsu.ulProgress = 0;
  502. if (NotifyReconciliationStatus(rsp, RS_BEGIN_DELETE, (LPARAM)&rsu,
  503. lpCallbackData))
  504. {
  505. PRECNODE prn;
  506. tr = TR_SUCCESS;
  507. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  508. {
  509. if (prn->rnaction == RNA_DELETE_ME)
  510. {
  511. TCHAR rgchPath[MAX_PATH_LEN];
  512. ComposePath(rgchPath, prn->pcszFolder, prn->priParent->pcszName, ARRAYSIZE(rgchPath));
  513. ASSERT(lstrlen(rgchPath) < ARRAYSIZE(rgchPath));
  514. if (MyIsPathOnVolume(rgchPath, (HPATH)(prn->hvid)))
  515. {
  516. if (DeleteFile(rgchPath))
  517. WARNING_OUT((TEXT("DealWithDelete(): Deleted file %s."),
  518. rgchPath));
  519. else
  520. {
  521. tr = TR_DEST_OPEN_FAILED;
  522. WARNING_OUT((TEXT("DealWithDelete(): Failed to delete file %s."),
  523. rgchPath));
  524. }
  525. }
  526. else
  527. tr = TR_UNAVAILABLE_VOLUME;
  528. }
  529. }
  530. if (tr == TR_SUCCESS)
  531. {
  532. /* 100% complete. */
  533. rsu.ulProgress = rsu.ulScale;
  534. /* Don't allow abort. */
  535. NotifyReconciliationStatus(rsp, RS_END_DELETE, (LPARAM)&rsu,
  536. lpCallbackData);
  537. }
  538. }
  539. else
  540. tr = TR_ABORT;
  541. return(tr);
  542. }
  543. /*
  544. ** CountRECNODEs()
  545. **
  546. **
  547. **
  548. ** Arguments:
  549. **
  550. ** Returns:
  551. **
  552. ** Side Effects: none
  553. */
  554. PRIVATE_CODE ULONG CountRECNODEs(PCRECITEM pcri, RECNODEACTION rnaction)
  555. {
  556. ULONG ulc = 0;
  557. PRECNODE prn;
  558. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  559. {
  560. if (prn->rnaction == rnaction)
  561. {
  562. ASSERT(ulc < ULONG_MAX);
  563. ulc++;
  564. }
  565. }
  566. return(ulc);
  567. }
  568. /*
  569. ** UpdateRecNodeFileStamps()
  570. **
  571. **
  572. **
  573. ** Arguments:
  574. **
  575. ** Returns:
  576. **
  577. ** Side Effects: none
  578. */
  579. PRIVATE_CODE TWINRESULT UpdateRecNodeFileStamps(PCRECITEM pcri)
  580. {
  581. TWINRESULT tr;
  582. PRECNODE prn;
  583. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  584. tr = TR_SUCCESS;
  585. for (prn = pcri->prnFirst; prn; prn = prn->prnNext)
  586. {
  587. ASSERT(IS_VALID_HANDLE(prn->hObjectTwin, OBJECTTWIN));
  588. /* Was the RECNODE supposed to be reconciled? */
  589. /*
  590. * FEATURE: We should avoid updating file stamps of copy sources here in
  591. * the SimpleCopy() case.
  592. */
  593. if (prn->rnaction != RNA_NOTHING)
  594. {
  595. ASSERT(prn->fsCurrent.fscond != FS_COND_UNAVAILABLE);
  596. /* Leave prn->fsLast as pot->fsLastRec here. */
  597. MyGetFileStampByHPATH(((PCOBJECTTWIN)(prn->hObjectTwin))->hpath,
  598. GetString(((PCOBJECTTWIN)(prn->hObjectTwin))->ptfParent->hsName),
  599. &(prn->fsCurrent));
  600. }
  601. }
  602. return(tr);
  603. }
  604. /*
  605. ** DeletedTwinsInRecItem()
  606. **
  607. **
  608. **
  609. ** Arguments:
  610. **
  611. ** Returns:
  612. **
  613. ** Side Effects: none
  614. */
  615. PRIVATE_CODE BOOL DeletedTwinsInRecItem(PCRECITEM pcri)
  616. {
  617. BOOL bResult = TRUE;
  618. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  619. /* Has the associated twin family been deleted? */
  620. if (IsStubFlagClear(&(((PTWINFAMILY)(pcri->hTwinFamily))->stub), STUB_FL_UNLINKED))
  621. {
  622. PRECNODE prn;
  623. /* No. Have any of the associated object twins been deleted? */
  624. for (prn = pcri->prnFirst;
  625. prn && IsStubFlagClear(&(((PCOBJECTTWIN)(prn->hObjectTwin))->stub), STUB_FL_UNLINKED);
  626. prn = prn->prnNext)
  627. ;
  628. if (! prn)
  629. bResult = FALSE;
  630. }
  631. return(bResult);
  632. }
  633. /****************************** Public Functions *****************************/
  634. /*
  635. ** CopyFileStampFromFindData()
  636. **
  637. **
  638. **
  639. ** Arguments:
  640. **
  641. ** Returns:
  642. **
  643. ** Side Effects: none
  644. */
  645. PUBLIC_CODE void CopyFileStampFromFindData(PCWIN32_FIND_DATA pcwfdSrc,
  646. PFILESTAMP pfsDest)
  647. {
  648. ASSERT(IS_VALID_READ_PTR(pcwfdSrc, CWIN32_FIND_DATA));
  649. ASSERT(IS_VALID_WRITE_PTR(pfsDest, FILESTAMP));
  650. pfsDest->dwcbHighLength = pcwfdSrc->nFileSizeHigh;
  651. pfsDest->dwcbLowLength = pcwfdSrc->nFileSizeLow;
  652. /* Convert to local time and save that too */
  653. if ( !FileTimeToLocalFileTime(&pcwfdSrc->ftLastWriteTime, &pfsDest->ftModLocal) )
  654. {
  655. /* Just copy the time if FileTimeToLocalFileTime failed */
  656. pfsDest->ftModLocal = pcwfdSrc->ftLastWriteTime;
  657. }
  658. pfsDest->ftMod = pcwfdSrc->ftLastWriteTime;
  659. pfsDest->fscond = FS_COND_EXISTS;
  660. ASSERT(IS_VALID_STRUCT_PTR(pfsDest, CFILESTAMP));
  661. return;
  662. }
  663. /*
  664. ** MyGetFileStamp()
  665. **
  666. **
  667. **
  668. ** Arguments:
  669. **
  670. ** Returns:
  671. **
  672. ** Side Effects: none
  673. */
  674. PUBLIC_CODE void MyGetFileStamp(LPCTSTR pcszFile, PFILESTAMP pfs)
  675. {
  676. WIN32_FIND_DATA wfd;
  677. HANDLE hff;
  678. ASSERT(IS_VALID_STRING_PTR(pcszFile, CSTR));
  679. ASSERT(IS_VALID_WRITE_PTR(pfs, FILESTAMP));
  680. ZeroMemory(pfs, sizeof(*pfs));
  681. hff = FindFirstFile(pcszFile, &wfd);
  682. if (hff != INVALID_HANDLE_VALUE)
  683. {
  684. if (! IS_ATTR_DIR(wfd.dwFileAttributes))
  685. CopyFileStampFromFindData(&wfd, pfs);
  686. else
  687. pfs->fscond = FS_COND_EXISTS;
  688. EVAL(FindClose(hff));
  689. }
  690. else
  691. pfs->fscond = FS_COND_DOES_NOT_EXIST;
  692. ASSERT(IS_VALID_STRUCT_PTR(pfs, CFILESTAMP));
  693. return;
  694. }
  695. /*
  696. ** MyGetFileStampByHPATH()
  697. **
  698. **
  699. **
  700. ** Arguments:
  701. **
  702. ** Returns:
  703. **
  704. ** Side Effects:
  705. */
  706. PUBLIC_CODE void MyGetFileStampByHPATH(HPATH hpath, LPCTSTR pcszSubPath,
  707. PFILESTAMP pfs)
  708. {
  709. ASSERT(IS_VALID_HANDLE(hpath, PATH));
  710. ASSERT(! pcszSubPath ||
  711. IS_VALID_STRING_PTR(pcszSubPath, CSTR));
  712. ASSERT(IS_VALID_WRITE_PTR(pfs, FILESTAMP));
  713. if (IsPathVolumeAvailable(hpath))
  714. {
  715. TCHAR rgchPath[MAX_PATH_LEN];
  716. /* The root of the file's path is accessible. */
  717. rgchPath[0] = TEXT('\0');
  718. GetPathString(hpath, rgchPath, ARRAYSIZE(rgchPath));
  719. if (pcszSubPath)
  720. CatPath(rgchPath, pcszSubPath, ARRAYSIZE(rgchPath));
  721. ASSERT(lstrlen(rgchPath) < ARRAYSIZE(rgchPath));
  722. MyGetFileStamp(rgchPath, pfs);
  723. }
  724. else
  725. {
  726. ZeroMemory(pfs, sizeof(*pfs));
  727. pfs->fscond = FS_COND_UNAVAILABLE;
  728. }
  729. ASSERT(IS_VALID_STRUCT_PTR(pfs, CFILESTAMP));
  730. return;
  731. }
  732. /*
  733. ** MyCompareFileStamps()
  734. **
  735. **
  736. **
  737. ** Arguments:
  738. **
  739. ** Returns:
  740. **
  741. ** Side Effects: none
  742. **
  743. ** Any FS_COND_UNAVAILABLE == any FS_COND_UNAVAILABLE.
  744. ** Any FS_COND_UNAVAILABLE < any FS_COND_DOES_NOT_EXIST.
  745. ** Any FS_COND_DOES_NOT_EXIST == any FS_COND_DOES_NOT_EXIST.
  746. ** Any FS_COND_DOES_NOT_EXIST < any FS_COND_EXISTS.
  747. ** Two FS_COND_EXISTS are compared by date and time.
  748. **
  749. ** Hack Warning: This function depends upon the constant values of
  750. ** FS_COND_UNAVAILABLE, FS_COND_DOES_NOT_EXIST, and FS_COND_EXISTS being in
  751. ** increasing order, i.e.,
  752. **
  753. ** FS_COND_UNAVAILABLE < FS_COND_DOES_NOT_EXIST < FS_COND_EXISTS
  754. */
  755. PUBLIC_CODE COMPARISONRESULT MyCompareFileStamps(PCFILESTAMP pcfs1, PCFILESTAMP pcfs2)
  756. {
  757. int nResult;
  758. ASSERT(IS_VALID_STRUCT_PTR(pcfs1, CFILESTAMP));
  759. ASSERT(IS_VALID_STRUCT_PTR(pcfs2, CFILESTAMP));
  760. nResult = (int)(pcfs1->fscond - pcfs2->fscond);
  761. if (! nResult && pcfs1->fscond == FS_COND_EXISTS)
  762. {
  763. /* File times are stored as UTC times. However, files on FAT
  764. ** file systems only store the local time. This means the UTC
  765. ** is derived from the local time, and fudged depending on the
  766. ** current timezone info. This means that the UTC time will
  767. ** differ between timezone changes.
  768. **
  769. ** For remote files, the time's derivation depends on the server.
  770. ** NTFS servers provide the absolute UTC time, regardless of timezone.
  771. ** These are the best. Likewise, NWServer keeps track of the
  772. ** timezone and puts the UTC time on the wire like NTFS. FAT
  773. ** systems convert the local time to UTC time based on the server's
  774. ** timezone, and places the UTC time on the wire. Netware 3.31
  775. ** and some SMB servers put the local time on the wire and have
  776. ** the client convert to UTC time, so it uses the client's timezone.
  777. **
  778. ** One way to cover most of the holes that occur due to timezone
  779. ** changes is store both the UTC time and the local time. If either
  780. ** are the same, then the file has not changed.
  781. */
  782. BOOL bModEqual = (pcfs1->ftMod.dwHighDateTime == pcfs2->ftMod.dwHighDateTime);
  783. BOOL bModLocalEqual = (pcfs1->ftModLocal.dwHighDateTime == pcfs2->ftModLocal.dwHighDateTime);
  784. if (bModEqual || bModLocalEqual)
  785. {
  786. if (bModEqual && pcfs1->ftMod.dwLowDateTime == pcfs2->ftMod.dwLowDateTime ||
  787. bModLocalEqual && pcfs1->ftModLocal.dwLowDateTime == pcfs2->ftModLocal.dwLowDateTime)
  788. {
  789. if (pcfs1->dwcbHighLength == pcfs2->dwcbHighLength)
  790. {
  791. if (pcfs1->dwcbLowLength == pcfs2->dwcbLowLength)
  792. nResult = CR_EQUAL;
  793. else if (pcfs1->dwcbLowLength < pcfs2->dwcbLowLength)
  794. nResult = CR_FIRST_SMALLER;
  795. else
  796. nResult = CR_FIRST_LARGER;
  797. }
  798. else if (pcfs1->dwcbHighLength < pcfs2->dwcbHighLength)
  799. nResult = CR_FIRST_SMALLER;
  800. else
  801. nResult = CR_FIRST_LARGER;
  802. }
  803. else if (pcfs1->ftMod.dwLowDateTime < pcfs2->ftMod.dwLowDateTime)
  804. nResult = CR_FIRST_SMALLER;
  805. else
  806. nResult = CR_FIRST_LARGER;
  807. }
  808. else if (pcfs1->ftMod.dwHighDateTime < pcfs2->ftMod.dwHighDateTime)
  809. nResult = CR_FIRST_SMALLER;
  810. else
  811. nResult = CR_FIRST_LARGER;
  812. }
  813. return(MapIntToComparisonResult(nResult));
  814. }
  815. /***************************** Exported Functions ****************************/
  816. /* RAIDRAID: (16205) AutoDoc RECSTATUSPROC messages below. */
  817. /******************************************************************************
  818. @doc SYNCENGAPI
  819. @api TWINRESULT | ReconcileItem | Reconciles a reconciliation item created by
  820. CreateRecList().
  821. @parm PCRECITEM | pcri | A pointer to a reconciliation item to be reconciled.
  822. @parm RECSTATUSPROC | rsp | A procedure instance address of a callback function
  823. to be called with status information during the reconciliation of the given
  824. RECITEM. rsp may be NULL to indicate that no reconciliation status callback
  825. function is to be called. (See the reconciliation handler SPI documentation
  826. for details.)
  827. @parm LPARAM | lpCallbackData | Callback data to be supplied to the
  828. reconciliation status callback function. If rsp is NULL, lpCallbackData is
  829. ignored.
  830. @parm DWORD | dwFlags | A bit mask of flags. This parameter may be any
  831. combination of the following values:
  832. RI_FL_ALLOW_UI - Allow interaction with the user during reconciliation.
  833. RI_FL_FEEDBACK_WINDOW_VALID - hwndProgressFeedback is valid, and may be used
  834. to communicate reconciliation progress information to the user during
  835. reconciliation.
  836. @parm HWND | hwndOwner | A handle to the parent window to be used when
  837. requesting user interaction. This parameter is ignored if the RI_FL_ALLOW_UI
  838. flag is clear.
  839. @parm HWND | hwndProgressFeedback | A handle to the window to be used to
  840. provide progress information to the user during reconciliation. This parameter
  841. is ignored if the RI_FL_FEEDBACK_WINDOW_VALID flag is clear.
  842. @rdesc If the reconciliation item was reconciled successfully, TR_SUCCESS is
  843. returned. Otherwise, the reconciliation item was not reconciled successfully,
  844. and the return value indicates the error that occurred.
  845. @comm All the fields in the given RECITEM and its child structures are left
  846. unchanged by ReconcileItem(), except for the fsCurrent fields of RECNODEs
  847. associated with objects that are overwritten during reconciliation. The
  848. fsCurrent field of each RECNODE associated with an object that is overwritten
  849. during reconciliation (i.e., RECNODEs with rnaction set to RNA_COPY_TO_ME or
  850. RNA_MERGE_ME) is updated to reflect the current time stamp of the object after
  851. it is overwritten. If ReconcileItem() returns TR_SUCCESS, all the available
  852. RECNODEs (i.e., all RECNODEs whose uState field is not RNS_UNAVAILABLE) in the
  853. RECITEM may be assumed to be up-to-date. If ReconcileItem() does not return
  854. TR_SUCCESS, no assumption may be made about the states of the RECNODEs in the
  855. RECITEM. If ReconcileItem() is called on a RECITEM that references a twin
  856. family that has been deleted or one or more object twins that have been
  857. deleted, TR_DELETED_TWIN is returned. In this case, no assumption may be made
  858. about what reconciliation actions have been carried out on the RECITEM. If
  859. TR_DELETED_TWIN is returned, the client may attempt to create a RECLIST for the
  860. twin family associated with the RECITEM in order to retry the reconciliation
  861. operation. (The client would call MarkTwin(), followed by CreateRecList().) If
  862. TR_DELETED_TWIN is returned by MarkTwin(), the entire twin family has been
  863. deleted. If TR_SUCCESS is returned by MarkTwin(), the client should be able to
  864. call CreateRecList() to create a RECLIST containing a more up-to-date RECITEM
  865. for the twin family.
  866. @xref CreateRecList
  867. ******************************************************************************/
  868. SYNCENGAPI TWINRESULT WINAPI ReconcileItem(PCRECITEM pcri, RECSTATUSPROC rsp,
  869. LPARAM lpCallbackData,
  870. DWORD dwFlags, HWND hwndOwner,
  871. HWND hwndProgressFeedback)
  872. {
  873. TWINRESULT tr;
  874. if (BeginExclusiveBriefcaseAccess())
  875. {
  876. DebugEntry(ReconcileItem);
  877. #ifdef EXPV
  878. /* Verify parameters. */
  879. /* lpCallbackData may be any value. */
  880. if (IS_VALID_STRUCT_PTR(pcri, CRECITEM) &&
  881. (! rsp ||
  882. IS_VALID_CODE_PTR(rsp, RECSTATUSPROC)) &&
  883. FLAGS_ARE_VALID(dwFlags, ALL_RI_FLAGS) &&
  884. (IS_FLAG_CLEAR(dwFlags, RI_FL_ALLOW_UI) ||
  885. IS_VALID_HANDLE(hwndOwner, WND)) &&
  886. (IS_FLAG_CLEAR(dwFlags, RI_FL_FEEDBACK_WINDOW_VALID) ||
  887. IS_VALID_HANDLE(hwndProgressFeedback, WND)))
  888. #endif
  889. {
  890. /* Check for any deleted twins referenced by this RECITEM. */
  891. if (! DeletedTwinsInRecItem(pcri))
  892. {
  893. InvalidatePathListInfo(GetBriefcasePathList(((PCTWINFAMILY)(pcri->hTwinFamily))->hbr));
  894. tr = MyReconcileItem(pcri, rsp, lpCallbackData, dwFlags,
  895. hwndOwner, hwndProgressFeedback);
  896. ASSERT(IS_VALID_STRUCT_PTR(pcri, CRECITEM));
  897. }
  898. else
  899. tr = TR_DELETED_TWIN;
  900. }
  901. #ifdef EXPV
  902. else
  903. tr = TR_INVALID_PARAMETER;
  904. #endif
  905. DebugExitTWINRESULT(ReconcileItem, tr);
  906. EndExclusiveBriefcaseAccess();
  907. }
  908. else
  909. tr = TR_REENTERED;
  910. return(tr);
  911. }
  912. /******************************************************************************
  913. @doc SYNCENGAPI
  914. @api TWINRESULT | BeginReconciliation | Indicates to the synchronization engine
  915. that the caller is about to make multiple calls to ReconcileItem().
  916. @parm HBRFCASE | hbr | A handle to the open briefcase about to be reconciled.
  917. @rdesc If reconciliation for the given briefcase was initialized successfully,
  918. TR_SUCCESS is returned. Otherwise, reconciliation for the given briefcase was
  919. not initialized successfully, and the return value indicates the error that
  920. occurred.
  921. @comm Synchronization engine clients need not call BeginReconciliation() before
  922. calling ReconcileItem(). BeginReconciliation() is simply provided to allow
  923. synchronization engine clients to give the synchronization engine a hint that
  924. multiple calls to ReconcileItem() are about to occur. Each call to
  925. EndReconciliation() should be followed by a call to EndReconciliation().
  926. @xref EndReconciliation ReconcileItem
  927. ******************************************************************************/
  928. SYNCENGAPI TWINRESULT WINAPI BeginReconciliation(HBRFCASE hbr)
  929. {
  930. TWINRESULT tr;
  931. if (BeginExclusiveBriefcaseAccess())
  932. {
  933. DebugEntry(BeginReconciliation);
  934. #ifdef EXPV
  935. /* Verify parameters. */
  936. if (IS_VALID_HANDLE(hbr, BRFCASE))
  937. #endif
  938. {
  939. BeginCopy();
  940. BeginMerge();
  941. tr = TR_SUCCESS;
  942. }
  943. #ifdef EXPV
  944. else
  945. tr = TR_INVALID_PARAMETER;
  946. #endif
  947. DebugExitTWINRESULT(BeginReconciliation, tr);
  948. EndExclusiveBriefcaseAccess();
  949. }
  950. else
  951. tr = TR_REENTERED;
  952. return(tr);
  953. }
  954. /******************************************************************************
  955. @doc SYNCENGAPI
  956. @api TWINRESULT | EndReconciliation | Indicates to the synchronization engine
  957. that the client has finished making multiple calls to ReconcileItem(),
  958. preceded by a call to BeginReconciliation().
  959. @parm HBRFCASE | hbr | A handle to the open briefcase whose reconciliation has
  960. been completed.
  961. @rdesc If reconciliation for the given briefcase was terminaterd successfully,
  962. TR_SUCCESS is returned. Otherwise, reconciliation for the given briefcase was
  963. not terminated successfully, and the return value indicates the error that
  964. occurred.
  965. @comm EndReconciliation() should only be called after a call to
  966. BeginReconciliation(). Each call to BeginReconciliation() should be followed
  967. by a matching call to EndReconciliation().
  968. @xref BeginReconciliation ReconcileItem
  969. ******************************************************************************/
  970. SYNCENGAPI TWINRESULT WINAPI EndReconciliation(HBRFCASE hbr)
  971. {
  972. TWINRESULT tr;
  973. if (BeginExclusiveBriefcaseAccess())
  974. {
  975. DebugEntry(EndReconciliation);
  976. #ifdef EXPV
  977. /* Verify parameters. */
  978. if (IS_VALID_HANDLE(hbr, BRFCASE))
  979. #endif
  980. {
  981. EndMerge();
  982. EndCopy();
  983. tr = TR_SUCCESS;
  984. }
  985. #ifdef EXPV
  986. else
  987. tr = TR_INVALID_PARAMETER;
  988. #endif
  989. DebugExitTWINRESULT(EndReconciliation, tr);
  990. EndExclusiveBriefcaseAccess();
  991. }
  992. else
  993. tr = TR_REENTERED;
  994. return(tr);
  995. }
  996. /******************************************************************************
  997. @doc SYNCENGAPI
  998. @api TWINRESULT | GetFileStamp | Retrieves the file stamp for an open file.
  999. @parm PCSTR | pcszFile | A pointer to a string indicating the file whose file
  1000. stamp is to be retrieved.
  1001. @parm PFILESTAMP | pcr | A pointer to a FILESTAMP to be filled in with the
  1002. file stamp of the given open file.
  1003. @rdesc If the comparison was successful, TR_SUCCESS is returned. Otherwise,
  1004. the comparison was not successful, and the return value indicates the error
  1005. that occurred.
  1006. @xref CompareFileStamps
  1007. ******************************************************************************/
  1008. SYNCENGAPI TWINRESULT WINAPI GetFileStamp(LPCTSTR pcszFile, PFILESTAMP pfs)
  1009. {
  1010. TWINRESULT tr;
  1011. /* No need for exclusive access here. */
  1012. DebugEntry(GetFileStamp);
  1013. #ifdef EXPV
  1014. /* Verify parameters. */
  1015. if (IS_VALID_STRING_PTR(pcszFile, CSTR) &&
  1016. IS_VALID_WRITE_PTR(pfs, FILESTAMP))
  1017. #endif
  1018. {
  1019. MyGetFileStamp(pcszFile, pfs);
  1020. tr = TR_SUCCESS;
  1021. }
  1022. #ifdef EXPV
  1023. else
  1024. tr = TR_INVALID_PARAMETER;
  1025. #endif
  1026. DebugExitTWINRESULT(GetFileStamp, tr);
  1027. return(tr);
  1028. }
  1029. /******************************************************************************
  1030. @doc SYNCENGAPI
  1031. @api COMPARISONRESULT | CompareFileStamps | Compares two file stamps.
  1032. @parm PCFILESTAMP | pcfs1 | A pointer to the first FILESTAMP to be compared.
  1033. @parm PCFILESTAMP | pcfs2 | A pointer to the second FILESTAMP to be compared.
  1034. @parm PCOMPARISONRESULT | pcr | A pointer to a COMPARISONRESULT to be filled in
  1035. with the result of the file stamp comparison. *pcr is only valid if TR_SUCCESS
  1036. is returned.
  1037. @rdesc If the file stamp was retrieved successfully, TR_SUCCESS is returned.
  1038. Otherwise, the file stamp was not retrieved successfully, and the return value
  1039. indicates the error that occurred.
  1040. @comm File stamps are compared by fields as follows:
  1041. 1) condition
  1042. Any FS_COND_UNAVAILABLE equals any FS_COND_UNAVAILABLE.
  1043. Any FS_COND_UNAVAILABLE is less than any FS_COND_DOES_NOT_EXIST.
  1044. Any FS_COND_DOES_NOT_EXIST equals any FS_COND_DOES_NOT_EXIST.
  1045. Any FS_COND_DOES_NOT_EXIST is less than any FS_COND_EXISTS.
  1046. Two FS_COND_EXISTS are compared by date and time.
  1047. 2) date and time of last modification
  1048. 3) length
  1049. @xref GetFileStamp
  1050. ******************************************************************************/
  1051. SYNCENGAPI TWINRESULT WINAPI CompareFileStamps(PCFILESTAMP pcfs1,
  1052. PCFILESTAMP pcfs2,
  1053. PCOMPARISONRESULT pcr)
  1054. {
  1055. TWINRESULT tr;
  1056. /* No need for exclusive access here. */
  1057. DebugEntry(CompareFileStamps);
  1058. #ifdef EXPV
  1059. /* Verify parameters. */
  1060. if (IS_VALID_STRUCT_PTR(pcfs1, CFILESTAMP) &&
  1061. IS_VALID_STRUCT_PTR(pcfs2, CFILESTAMP) &&
  1062. IS_VALID_WRITE_PTR(pcr, COMPARISONRESULT))
  1063. #endif
  1064. {
  1065. *pcr = MyCompareFileStamps(pcfs1, pcfs2);
  1066. tr = TR_SUCCESS;
  1067. }
  1068. #ifdef EXPV
  1069. else
  1070. tr = TR_INVALID_PARAMETER;
  1071. #endif
  1072. DebugExitTWINRESULT(CompareFileStamps, tr);
  1073. return(tr);
  1074. }