home *** CD-ROM | disk | FTP | other *** search
/ Frozen Fish 1: Amiga / FrozenFish-Apr94.iso / bbs / alib / d8xx / d828 / disktest.lha / DiskTest / Source / dt.c < prev    next >
Encoding:
C/C++ Source or Header  |  2001-02-25  |  16.7 KB  |  653 lines

  1. /*-----------------------------------------*
  2.  | File: DT.c - Main disk testing routines |
  3.  +-----------------------------------------+---------------*
  4.  | Author:  Maurizio Loreti, aka MLO or I3NOO.             |
  5.  | Address: University of Padova - Department of Physics   |
  6.  |          Via F. Marzolo, 8 - 35131 PADOVA - Italy       |
  7.  | Phone:   (39)(49) 844-313         FAX: (39)(49) 844-245 |
  8.  | E-Mail:  loreti@padova.infn.it (TCP/IP)                 |
  9.  | Home: Via G. Donizetti 6 - 35010 CADONEGHE (PD) - Italy |
  10.  *---------------------------------------------------------*/
  11.  
  12. /**
  13.  | #includes
  14. **/
  15.  
  16. #include <stddef.h>                       /* Standard library */
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <stdarg.h>
  20. #include <string.h>
  21. #include <exec/types.h>                   /* Amiga specific */
  22. #include <exec/memory.h>
  23. #include <devices/trackdisk.h>
  24. #include <workbench/startup.h>
  25. #include <clib/exec_protos.h>
  26. #include <clib/gadtools_protos.h>
  27. #include <clib/intuition_protos.h>
  28. #include <clib/alib_protos.h>
  29. #include <clib/dos_protos.h>
  30. #include <clib/wb_protos.h>
  31. #include "main.h"
  32. #include "dt.h"
  33. #include "ext.h"
  34.  
  35. void EventLoop(void)
  36. {
  37. /**
  38.  | Main event loop.
  39.  | First, we initialise the AppWindow environment; then we Wait()
  40.  | for a signal from IDCMP or the AppWindow, and take the appropriate
  41.  | action.
  42. **/
  43.  
  44.   ULONG signalSet;
  45.  
  46.   if ((appWinPort = CreateMsgPort()) == NULL) {
  47.     Error("Error from CreateMsgPort");
  48.   }
  49.  
  50.   if ((pAWind = AddAppWindow(AW_ID, 0, pWind, appWinPort,
  51.                              TAG_END)) == NULL) {
  52.     Error("Error from AddAppWindow");
  53.   }
  54.  
  55.   signalSet =
  56.     (1L << appWinPort->mp_SigBit)  |  (1L << pWind->UserPort->mp_SigBit);
  57.  
  58.   FOREVER {
  59.     struct IntuiMessage *pIM;
  60.     struct AppMessage *pAM;
  61.  
  62.     (void) Wait(signalSet);
  63.  
  64.     while ((pIM = GT_GetIMsg(pWind->UserPort)) != NULL) {
  65.       ULONG class;
  66.       UWORD code;
  67.       struct Gadget *pG;
  68.  
  69.       class = pIM->Class;
  70.       code  = pIM->Code;
  71.       pG    = (struct Gadget *) pIM->IAddress;
  72.       GT_ReplyIMsg(pIM);
  73.  
  74.       switch (class) {
  75.         case GADGETDOWN:
  76.         case GADGETUP:
  77.           switch (pG->GadgetID) {
  78.             case BUT_QUIT:
  79.               Cleanup();
  80.               break;
  81.             case BUT_1:
  82.             case BUT_2:
  83.             case BUT_3:
  84.             case BUT_4:
  85.               StartTest();
  86.               DiskCheck(pG->GadgetID);
  87.               EndTest();
  88.               break;
  89.             case BUT_FN:
  90. /* NO see RKM listFilenames = (BOOL)(pG->Flags & GFLG_SELECTED; */
  91.               listFileNames = (BOOL)(!listFileNames);
  92.               break;
  93.             case SCROLLER:
  94.               SetTopLine(code);
  95.               break;
  96.             default:
  97.               break;
  98.           }
  99.           break;
  100.         case MOUSEMOVE:
  101.           SetTopLine(code);
  102.           break;
  103.         case REFRESHWINDOW:
  104.           GT_BeginRefresh(pWind);
  105.           RefreshView(TRUE);
  106.             GT_EndRefresh(pWind, TRUE);
  107.           break;
  108.         default:
  109.           break;
  110.       }
  111.     }
  112.  
  113.     while ((pAM = (struct AppMessage *) GetMsg(appWinPort)) != NULL) {
  114.       BPTR dirLock;
  115.       char dirName[FILENAME_MAX];
  116.       char filName[FILENAME_MAX];
  117.  
  118.       strcpy(filName, pAM->am_ArgList->wa_Name);
  119.       if (filName[0]) {
  120.         dirLock = DupLock(pAM->am_ArgList->wa_Lock);
  121.       } else {
  122.         (void) NameFromLock(pAM->am_ArgList->wa_Lock,
  123.                             dirName, FILENAME_MAX);
  124.       }
  125.       ReplyMsg((struct Message *) pAM);
  126.  
  127.       GetBuffer(FILBUF_DIM);
  128.       StartTest();
  129.       OutLine(FALSE, "File integrity check ...");
  130.  
  131.       if (filName[0]) {
  132.         BPTR oldLock;
  133.  
  134.         oldLock = CurrentDir(dirLock);
  135.         (void) NameFromLock(dirLock, dirName, FILENAME_MAX);
  136.         OutLine(FALSE, "  Sitting in directory %s ...", dirName);
  137.         CheckFile(filName);
  138.         (void) CurrentDir(oldLock);
  139.       } else {
  140.         char *pc;
  141.  
  142.         pc = dirName + strlen(dirName) - 1;
  143.         CheckDir(dirName, (BOOL)(*pc == ':'));
  144.       }
  145.       EndTest();
  146.     }
  147.   }
  148. }
  149.  
  150. /*-------------------------- Local Procedures --------------------------*/
  151.  
  152. static unsigned CheckBreak(void)
  153. {
  154. /**
  155.  | Called from time to time to check for user breaks; checks the
  156.  | CTRL-C signal (actually unused) and the "break window" gadgets.
  157.  | The returned value is the "abortDT" global variable itself.
  158. **/
  159.  
  160.   if (!abortDT  &&
  161.       (SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C))
  162.           abortDT |= BRK_DETECTED;
  163.  
  164.   if (pBWind != NULL) {
  165.     struct IntuiMessage *pIM;
  166.  
  167.     if ((pIM = GT_GetIMsg(pBWind->UserPort)) != NULL) {
  168.       GT_ReplyIMsg(pIM);
  169.       abortDT |= BRK_DETECTED;
  170.     }
  171.   }
  172.  
  173.   if (abortDT  &&  !(abortDT & WARN_PRINTED)) {
  174.     OutLine(FALSE, "*** DiskTest: BREAK ***");
  175.     abortDT |= WARN_PRINTED;
  176.   }
  177.  
  178.   return abortDT;
  179. }
  180.  
  181. static void CheckDir(
  182.   char *path,
  183.   const BOOL root
  184. ){
  185. /**
  186.  | This procedure checks (recursively) a directory.
  187.  | "path" contains the full directory name, and "root" is non-zero if
  188.  | this directory is at the top level (the difference is in the special
  189.  | handling of the ':', and in the replacement of the device name (e.g.
  190.  | "DH0:") with its label (e.g. "Workbench:"), in general more clear).
  191.  |
  192.  | CheckDir() scans the wanted directory, checking immediately the 'true'
  193.  | files; the subdirectories (if any) are checked recursively at the
  194.  | end, one by one.
  195.  | If an error is detected from CheckDir(), the directory/file test is
  196.  | stopped and the global flag "abortDT" is set; this makes possible, in
  197.  | the further steps, to recursively free() all the memory that has been
  198.  | allocated in order to store the subdirectory names.
  199.  |
  200.  | First: obtain a lock on the wanted directory, and Examine() the lock;
  201.  | since only one directory is being scanned at a time, we can use a
  202.  | single FileInfoBlock global buffer.
  203. **/
  204.  
  205.   dirEntry *rdE = NULL;
  206.   BPTR dlock;
  207.  
  208.   if ((dlock = Lock(path, ACCESS_READ)) == 0) {
  209.     OutLine(FALSE, "* Can't access directory %s !", path);
  210.     abortDT = INTERNAL_ERR;
  211.     nErFil++;
  212.   } else {
  213.     if (!Examine(dlock, pFIB)) {
  214.       OutLine(FALSE,
  215.               "* Error return from Examine(), directory %s", path);
  216.       abortDT = INTERNAL_ERR;
  217.       nErFil++;
  218.     } else {
  219.  
  220. /**
  221.  | Prepare in "fileName" the full directory name - to which local
  222.  | filenames will be appended.
  223. **/
  224.  
  225.       char fileName[FILENAME_MAX];
  226.       char *pc;
  227.  
  228.       if (root) {
  229.         pc = strcpy(fileName, pFIB->fib_FileName);
  230.         pc += strlen(fileName);
  231.         *pc++ = ':';
  232.         *pc = '\0';
  233.         OutLine(FALSE,
  234.                 "  checking files in root directory %s ...", fileName);
  235.       } else {
  236.         pc = strcpy(fileName, path);
  237.         pc += strlen(fileName);
  238.         OutLine(FALSE,
  239.                 "  checking files in directory %s ...", fileName);
  240.         *pc++ = '/';
  241.       }
  242.  
  243.       nDirs++;
  244.  
  245. /**
  246.  | Now, loop over all directory entries. As already said, all the
  247.  | 'real' files are immediately checked; the subdirectory names are
  248.  | stored in a linked list to be examined at the end. This list is
  249.  | implemented as a LIFO tree (the simplest type).
  250. **/
  251.  
  252.       while (ExNext(dlock, pFIB)) {
  253.         (void) strcpy(pc, pFIB->fib_FileName);
  254.         if (pFIB->fib_DirEntryType < 0) {
  255.           CheckFile(fileName);
  256.         } else {
  257.  
  258. /**
  259.  | If a memory allocation error is detected when asking space for
  260.  | our linked list, we exit the "while" loop; setting the "abortDT"
  261.  | flag before exiting ensures that all the memory we had from
  262.  | these malloc()'s will later be free()-ed recursively.
  263. **/
  264.  
  265.           dirEntry *pdE;
  266.  
  267.           if ((pdE = malloc(sizeof(dirEntry) + strlen(fileName))) == NULL) {
  268.             OutLine(FALSE, "* Can't allocate heap memory!");
  269.             abortDT = INTERNAL_ERR;
  270.             break;
  271.           }
  272.  
  273.           (void) strcpy(pdE->name, fileName);
  274.           pdE->next = rdE;
  275.           rdE = pdE;
  276.         }
  277.  
  278.         if (CheckBreak()) break;
  279.       }
  280.  
  281. /**
  282.  | We should check if ExNext() has failed, or if the last
  283.  | entry has been found.
  284. **/
  285.  
  286.       if (!abortDT) {
  287.         int errno;
  288.  
  289.         if ((errno = IoErr()) != ERROR_NO_MORE_ENTRIES) {
  290.           OutLine(FALSE, "* Error %d reading directory %s !",
  291.                   errno, path);
  292.           nErFil++;
  293.         }
  294.       }
  295.     }
  296.     UnLock(dlock);
  297.   }
  298.  
  299. /**
  300.  | Now, loop over all detected subdirectories (if any);
  301.  | freeing in the same time the memory used to store their names.
  302. **/
  303.  
  304.   while (rdE != NULL) {
  305.     dirEntry *pdE;
  306.  
  307.     if (!abortDT) CheckDir(rdE->name, FALSE);
  308.  
  309.     pdE = rdE->next;
  310.     free(rdE);
  311.     rdE = pdE;
  312.   }
  313. }
  314.  
  315. static void CheckFile(
  316.   char *name
  317. ){
  318. /**
  319.  | Check a file, opening and reading it record by record.
  320. **/
  321.  
  322.   BPTR pFH;
  323.  
  324.   nFiles++;
  325.   if (listFileNames) OutLine(FALSE, "    file %s ...", name);
  326.  
  327.   if ((pFH = Open(name, MODE_OLDFILE)) == 0) {
  328.     OutLine(FALSE, "* Error %d opening file \"%s\".", IoErr(), name);
  329.     nErFil++;
  330.   } else {
  331.     long ier;
  332.  
  333.     while (!abortDT  &&
  334.            (ier = Read(pFH, diskBuffer, (long) diskBufferLength)) > 0) {
  335.       (void) CheckBreak();
  336.     }
  337.     (void) Close(pFH);
  338.  
  339.     if (ier < 0) {
  340.       OutLine(FALSE, "* Error %d reading file \"%s\".", IoErr(), name);
  341.       nErFil++;
  342.     }
  343.   }
  344. }
  345.  
  346. static void DiskCheck(
  347.   const int drive
  348. ){
  349. /**
  350.  | Full test for the floppy unit "drive".
  351.  | Properly opened the device, we check for a disk in drive and,
  352.  | obtain the floppy disk parameters from various status commands;
  353.  | then obtain memory for our internal buffers.
  354. **/
  355.  
  356.   char driveName[] = "DF0:";
  357.   int error;
  358.   int cyl;
  359.   int head;
  360.   ULONG total;
  361.  
  362.   if ((diskPort = CreatePort(NULL, 0L)) == NULL) {
  363.     Error("Error from CreatePort");
  364.   }
  365.  
  366.   if ((diskReq = (struct IOExtTD *)
  367.       CreateExtIO(diskPort, sizeof(struct IOExtTD))) == NULL) {
  368.     Error("Error from CreateExtIo");
  369.   }
  370.  
  371.   driveName[2] += (char) drive;
  372.  
  373.   if ((error =
  374.       OpenDevice(TD_NAME, (ULONG) drive,
  375.                  (struct IORequest *) diskReq, 0L)) != 0) {
  376.     sprintf(slate, "Error 0x%X from OpenDevice", error);
  377.     Error(slate);
  378.   }
  379.  
  380.   diskReq->iotd_Req.io_Command = TD_CHANGESTATE;
  381.   (void) DoIO((struct IORequest *) diskReq);
  382.   if (diskReq->iotd_Req.io_Actual) {
  383.     OutLine(FALSE, "No disk in drive %d ...", drive);
  384.     return;
  385.   }
  386.  
  387.   diskReq->iotd_Req.io_Command = TD_CHANGENUM;
  388.   (void) DoIO((struct IORequest *) diskReq);
  389.  
  390.   diskReq->iotd_Req.io_Command = TD_GETGEOMETRY;
  391.   diskReq->iotd_Req.io_Data = &drGeom;
  392.   (void) DoIO((struct IORequest *) diskReq);
  393.  
  394.   cylSize = drGeom.dg_TrackSectors * drGeom.dg_SectorSize;
  395.   GetBuffer(cylSize);
  396.   total = drGeom.dg_TotalSectors * drGeom.dg_SectorSize;
  397.   total >>= 10;
  398.   OutLine(FALSE,
  399.           "Change number for drive %s (%ld KBytes) is %d;",
  400.           driveName, total,
  401.           (diskChangeCount = diskReq->iotd_Req.io_Actual));
  402.  
  403. #ifdef DT_DEBUG
  404.   OutLine(FALSE, "%ld heads, %ld cylinders",
  405.           drGeom.dg_Heads, drGeom.dg_Cylinders);
  406.   OutLine(FALSE, "%ld sec/cyl, %ld sec/trk, %ld bytes/sec",
  407.           drGeom.dg_CylSectors, drGeom.dg_TrackSectors,
  408.           drGeom.dg_SectorSize);
  409.   OutLine(FALSE, "%ld total sectors, %ld bytes/trk",
  410.           drGeom.dg_TotalSectors, cylSize);
  411. #endif
  412.  
  413. /**
  414.  | Pass 1
  415.  | Seek over the floppy.
  416. **/
  417.  
  418.   Motor(ON);
  419.   SeekFullRange(1);
  420.   if (nErFil) {
  421.     Motor(OFF);
  422.     OutLine(FALSE, "* %d hard error%s detected seeking drive %s",
  423.             nErFil, (nErFil == 1 ? "" : "s"), driveName);
  424.     return;
  425.   } else {
  426.     OutLine(FALSE, "  no errors detected seeking over full disk range.");
  427.   }
  428.  
  429. /**
  430.  | Pass 2
  431.  | Read all disk tracks.
  432. **/
  433.  
  434.   OutLine(FALSE, "Checking all disk tracks:");
  435.   for (cyl=0; cyl<(int)drGeom.dg_Cylinders; cyl++) {
  436.     for (head=0; head<(int)drGeom.dg_Heads; head++) {
  437.       OutLine(TRUE,
  438.               "  reading cylinder %d, head %d ...", cyl, head);
  439.       if (CheckBreak()) {
  440.         Motor(OFF);
  441.         return;
  442.       }
  443.  
  444.       ReadCyl(cyl, head);
  445.       if ((error = diskReq->iotd_Req.io_Error) != 0) {
  446.         OutLine(FALSE, "* Error 0x%X detected for cylinder %d, head %d",
  447.             error, cyl, head);
  448.         nErFil++;
  449.       }
  450.     }
  451.   }
  452.   Motor(OFF);
  453.  
  454.   if (nErFil) {
  455.     OutLine(FALSE, "* %d hard error%s detected reading drive %s.",
  456.         nErFil, (nErFil == 1 ? "" : "s"), driveName);
  457.     return;
  458.   } else {
  459.     OutLine(FALSE, "  no errors detected reading drive %s.", driveName);
  460.   }
  461.  
  462. /**
  463.  | Pass 3
  464.  | File integrity check.
  465. **/
  466.  
  467.   OutLine(FALSE, "Checking all files in drive %s", driveName);
  468.   CheckDir(driveName, TRUE);
  469. }
  470.  
  471. static void EndTest(void)
  472. {
  473. /**
  474.  | This procedure outputs some statistics on the test;
  475.  | and frees all unneeded resources. The buffer only is
  476.  | retained, just in case we want to check some other thing...
  477. **/
  478.  
  479.   if (pBWind != NULL) {
  480.     struct IntuiMessage *pIM;
  481.  
  482.     while ((pIM = GT_GetIMsg(pBWind->UserPort)) != NULL) {
  483.       GT_ReplyIMsg(pIM);
  484.     }
  485.     CloseWindow(pBWind);
  486.     pBWind = NULL;
  487.   }
  488.  
  489.   if (diskReq != NULL) {
  490.     CloseDevice((struct IORequest *) diskReq);
  491.     DeleteExtIO((struct IORequest *) diskReq);
  492.     diskReq = NULL;
  493.   }
  494.  
  495.   if (diskPort != NULL) {
  496.     DeletePort(diskPort);
  497.     diskPort = NULL;
  498.   }
  499.  
  500.   if (nDirs + nFiles)
  501.     OutLine(FALSE,
  502.             "%d director%s and %d file%s checked: %d error%s detected.",
  503.             nDirs,  (nDirs  == 1 ? "y" : "ies"),
  504.             nFiles, (nFiles == 1 ? ""  : "s"),
  505.             nErFil, (nErFil == 1 ? ""  : "s"));
  506.  
  507.   BusyState(FALSE);
  508. }
  509.  
  510. static void GetBuffer(
  511.   const ULONG size
  512. ){
  513. /**
  514.  | If no buffer has been previously allocated, GetBuffer() queries
  515.  | for memory from the heap; if the previously allocated buffer is
  516.  | too small, it is released and reallocated.
  517.  | N.B.: we ask for chip memory, because the buffer has to be used
  518.  |       for disk reading too; for file reading, MEMF_PUBLIC should
  519.  |       be enough.
  520. **/
  521.  
  522.   if (diskBuffer != NULL) {
  523.     if (diskBufferLength >= size) return;
  524.     FreeMem(diskBuffer, diskBufferLength);
  525.   }
  526.  
  527.   if ((diskBuffer = AllocMem(size, MEMF_CHIP)) == NULL) {
  528.     Error("Error from AllocMem");
  529.   }
  530.   diskBufferLength = size;
  531. }
  532.  
  533. static void Motor(
  534.   const ULONG action
  535. ){
  536. /**
  537.  | Issues to the drive a MOTOR ON or OFF request; "action"
  538.  | is the preprocessor symbol ON or OFF.
  539. **/
  540.  
  541.   diskReq->iotd_Req.io_Length = action;
  542.   diskReq->iotd_Req.io_Command = TD_MOTOR;
  543.   (void) DoIO((struct IORequest *) diskReq);
  544. }
  545.  
  546. static void OutLine(
  547.   const BOOL last,
  548.   char *fmt,
  549.   ...
  550. ){
  551. /**
  552.  | Formatted output on the screen; if "last" is non zero, the
  553.  | "special mode" for the output to be discarded is used.
  554.  | Currently only the track test calls OutLine() with last=TRUE.
  555. **/
  556.  
  557.   va_list vl;
  558.   size_t n;
  559.  
  560.   va_start(vl, fmt);
  561.   n = (size_t) vsprintf(slate, fmt, vl);
  562.   va_end(vl);
  563.  
  564.   if (last) {
  565.     LastLine(slate, n);
  566.   } else {
  567.     AddLine(slate, n, TRUE);
  568.   }
  569. }
  570.  
  571. static void ReadCyl(
  572.   const int cyl,
  573.   const int hd
  574. ){
  575. /**
  576.  | Issues the command to read a whole cylinder on the
  577.  | internal buffer.
  578. **/
  579.  
  580.   diskReq->iotd_Req.io_Length = cylSize;
  581.   diskReq->iotd_Req.io_Data = (APTR) diskBuffer;
  582.   diskReq->iotd_Req.io_Command = ETD_READ;
  583.   diskReq->iotd_Count = diskChangeCount;
  584.   diskReq->iotd_Req.io_Offset =
  585.     drGeom.dg_SectorSize * (drGeom.dg_TrackSectors *
  586.       ((ULONG) hd + drGeom.dg_Heads * (ULONG) cyl));
  587.   (void) DoIO((struct IORequest *) diskReq);
  588. }
  589.  
  590. static void SeekFullRange(
  591.   const SHORT howmany
  592. ){
  593. /**
  594.  | Performs "howmany" seeks first to the highest
  595.  | cylinder of the drive, and then to the lowest.
  596. **/
  597.  
  598.   int i;
  599.   SHORT error;
  600.  
  601.   for (i=1; i<=howmany; i++) {
  602.     diskReq->iotd_Req.io_Offset =
  603.       ((drGeom.dg_Cylinders - 1) * drGeom.dg_TrackSectors *
  604.         drGeom.dg_Heads - 1) * drGeom.dg_SectorSize;
  605.     diskReq->iotd_Req.io_Command = TD_SEEK;
  606.     (void) DoIO((struct IORequest *) diskReq);
  607.     if ((error = diskReq -> iotd_Req.io_Error) != 0) {
  608.       OutLine(FALSE, "* Seek cycle %d, error 0x%X ...", i, error);
  609.       nErFil++;
  610.     }
  611.  
  612.     diskReq->iotd_Req.io_Offset = 0;
  613.     diskReq->iotd_Req.io_Command = TD_SEEK;
  614.     (void) DoIO((struct IORequest *) diskReq);
  615.     if ((error = diskReq->iotd_Req.io_Error) != 0) {
  616.       OutLine(FALSE, "* Seek cycle %d, error 0x%X ...", i, error);
  617.       nErFil++;
  618.     }
  619.   }
  620. }
  621.  
  622. static void StartTest(void)
  623. {
  624. /**
  625.  | Zeroes the statistics variables, the break flag,
  626.  | and clears the output window. Then creates the
  627.  | "break" window.
  628. **/
  629.  
  630.   nDirs = nFiles = nErFil = 0;
  631.   abortDT = 0;
  632.   ClearText(TRUE);
  633.   BusyState(TRUE);
  634.  
  635.   if ((pBWind = OpenWindowTags(NULL,
  636.                                WA_Left,         pWind->LeftEdge + BKW_LEFT,
  637.                                WA_Top,          pWind->TopEdge  + BKW_TOP,
  638.                                WA_Width,        BKW_WIDTH,
  639.                                WA_Height,       BKW_HEIGHT,
  640.                                WA_Activate,     TRUE,
  641.                                WA_DragBar,      TRUE,
  642.                                WA_DepthGadget,  TRUE,
  643.                                WA_SmartRefresh, TRUE,
  644.                                WA_IDCMP,        BKW_IDCMP,
  645.                                WA_Title,        PROG_NAME " v" REVISION,
  646.                                WA_Gadgets,      pBGad,
  647.                                WA_PubScreen,    pScr,
  648.                                TAG_DONE)) == NULL) {
  649.     Error("Couldn't open the break window");
  650.   }
  651.   GT_RefreshWindow(pBWind, NULL);
  652. }
  653.