home *** CD-ROM | disk | FTP | other *** search
/ The CDPD Public Domain Collection for CDTV 4 / CDPD_IV.bin / utilities / cli / fill / source / fill.e < prev   
Encoding:
Text File  |  1994-06-22  |  16.7 KB  |  480 lines

  1. /*--------------------------------------------------------*
  2.  
  3.             Fill V1.2a.  Floppy Storage Optimizer.
  4.    Copyright ©1993-1994 Barry Wills.  All rights reserved.
  5.  
  6.  *--------------------------------------------------------*/
  7.  
  8. MODULE 'dos/dos',
  9.        'libraries/arpbase',
  10.        'arp'
  11.  
  12. PMODULE 'PMODULES:commandLineArgs'
  13. PMODULE 'PMODULES:upperChar'
  14.  
  15. /*-- Runtime exceptions. --*/
  16. ENUM ER_NONE,
  17.      ER_OPEN_ARPLIBRARY,
  18.      ER_USAGE,
  19.      ER_DEST_LOCK,
  20.      ER_SOURCE_LOCK,
  21.      ER_SOURCE_SPEC,
  22.      ER_SOURCE_EXAM,
  23.      ER_DEST_EXAM,
  24.      ER_DEST_INFO,
  25.      ER_FILES_TOO_LARGE,
  26.      ER_MEM,
  27.      ER_WONT_FIT,
  28.      ER_USER_ABORT
  29.  
  30. RAISE ER_MEM IF New()=NIL,
  31.       ER_MEM IF String()=NIL,
  32.       ER_USER_ABORT IF CtrlC()=TRUE
  33.  
  34. CONST SIZEOF_A_FILE_BLOCK=35136,
  35.       NUMBLOCKS_USED_ON_BLANK_DISK_AMIGA_880K=2,
  36.       NUMBLOCKS_USED_ON_BLANK_DISK_IBM_720K=14
  37.  
  38. /*=== List definitions. ==================================================*/
  39.  
  40. OBJECT fl_ElementType
  41.   fileName      :LONG  /*  PTR TO CHAR */
  42.   fileSize      :LONG  /*  LONG        */
  43.   fileProtection:LONG  /*  LONG        */
  44.   days          :LONG
  45.   minute        :LONG
  46.   tick          :LONG
  47. ENDOBJECT
  48.     /* fl_ElementType */
  49.  
  50. OBJECT fl_NodeType
  51.   element :LONG  /*  PTR TO fl_ElementType */
  52.   nextNode:LONG  /*  PTR TO fl_NodeType    */
  53. ENDOBJECT
  54.     /* fl_NodeType */
  55.  
  56. OBJECT fl_ListType
  57.   head   :LONG  /* all PTR TO fl_NodeType */
  58.   tail   :LONG
  59.   current:LONG
  60. ENDOBJECT
  61.     /* fl_ListType */
  62.  
  63.  
  64. /*=== Command-line argument defs. ========================================*/
  65.  
  66. CONST MAX_ARG_ERRORMARGIN=20,
  67.       MAX_DEPTH_OF_COMPARISON=2
  68.  
  69. DEF optionIsSet_CopyOnly=FALSE,
  70.     optionIsSet_Clone=TRUE,
  71.     optionIsSet_NoPro=FALSE,
  72.     argErrorMargin=0,
  73.     argSourceSpec=NIL,
  74.     argDestPath=NIL,
  75.     argNoDosOverHead=FALSE,
  76.     sourceDir[108]:STRING,
  77.     sourcePathAndFilename[108]:STRING,
  78.     destPathAndFilename[108]:STRING,
  79.     numblocksUsedOnABlankDisk=NUMBLOCKS_USED_ON_BLANK_DISK_AMIGA_880K
  80.  
  81. DEF dosCopy_segList,
  82.     dosCopy_args,
  83.     stackSize
  84.  
  85.  
  86. /*=== Command-line Argument Parser =======================================*/
  87.  
  88. PROC getSourceDir()
  89.   DEF c
  90.   c:=StrLen(argSourceSpec)
  91.   WHILE (c-->0) AND (argSourceSpec[c]<>":") AND (argSourceSpec[c]<>"/") DO NOP
  92.   IF c=0
  93.     SetStr(sourceDir, 0)
  94.   ELSE
  95.     INC c
  96.     WHILE c-->=0 DO sourceDir[c]:=argSourceSpec[c]
  97.     SetStr(sourceDir, StrLen(sourceDir))
  98.   ENDIF
  99. ENDPROC
  100.   /* getSourceDir */
  101.  
  102. PROC parseCommandLineArguments()
  103.   DEF index=1, char, theArg:PTR TO CHAR
  104.   theArg:=String(StrLen(arg))
  105.   WHILE getArg(theArg, index)
  106.     INC index
  107.     char:=theArg[0]
  108.     IF char="-"
  109.       char:=theArg[1]
  110.       SELECT char
  111.         CASE "c"
  112.           optionIsSet_CopyOnly:=TRUE
  113.         CASE "x"
  114.           optionIsSet_Clone:=FALSE
  115.         CASE "p"
  116.           optionIsSet_NoPro:=TRUE
  117.         CASE "e"
  118.           argErrorMargin:=Val(theArg+2, NIL)
  119.           IF argErrorMargin<=0 THEN Raise(ER_USAGE)
  120.           IF argErrorMargin>MAX_ARG_ERRORMARGIN THEN argErrorMargin:=MAX_ARG_ERRORMARGIN
  121.         CASE "n"
  122.           argNoDosOverHead:=TRUE
  123.           numblocksUsedOnABlankDisk:=NUMBLOCKS_USED_ON_BLANK_DISK_IBM_720K
  124.       ENDSELECT
  125.     ELSEIF argSourceSpec=NIL
  126.       argSourceSpec:=String(StrLen(theArg))
  127.       StrCopy(argSourceSpec, theArg, ALL)
  128.     ELSEIF argDestPath=NIL
  129.       argDestPath:=String(StrLen(theArg))
  130.       StrCopy(argDestPath, theArg, ALL)
  131.     ELSE  /*-- Too many args. --*/
  132.       Raise(ER_USAGE)
  133.     ENDIF
  134.   ENDWHILE
  135.   IF (argSourceSpec=NIL) OR (argDestPath=NIL) THEN Raise(ER_USAGE)
  136.   DisposeLink(theArg)
  137.   getSourceDir()
  138. ENDPROC
  139.   /* parseCommandLineArguments */
  140.  
  141.  
  142. /*=== Begin File List Implementation =====================================*/
  143.  
  144. /*------------------------------------------------------------------------*
  145.    These functions are used to gain easy access to the list substructures.
  146.  *------------------------------------------------------------------------*/
  147.  
  148. PROC fl_FileSizeFrom(theElement:PTR TO fl_ElementType) RETURN theElement.fileSize
  149. PROC fl_ElementFrom(theNode:PTR TO fl_NodeType) RETURN theNode.element
  150. PROC fl_NextNodeFrom(theNode:PTR TO fl_NodeType) RETURN theNode.nextNode
  151. PROC fl_IsLessThan(thisElement, thatElement) RETURN fl_FileSizeFrom(thisElement)<
  152.                                                     fl_FileSizeFrom(thatElement)
  153. PROC fl_IsEmpty(list:PTR TO fl_ListType) RETURN fl_NextNodeFrom(list.head)=list.tail
  154.  
  155. /*------------------------------------------------------------------------*
  156.    These functions are used to manipulate the list.
  157.  *------------------------------------------------------------------------*/
  158.  
  159. PROC fl_New()
  160.   DEF newFileList:PTR TO fl_ListType,
  161.       head:PTR TO fl_NodeType,
  162.       tail:PTR TO fl_NodeType
  163.   newFileList:=New(SIZEOF fl_ListType)
  164.   newFileList.head:=New(SIZEOF fl_NodeType)
  165.   newFileList.tail:=New(SIZEOF fl_NodeType)
  166.   head:=newFileList.head
  167.   tail:=newFileList.tail
  168.   head.nextNode:=newFileList.tail
  169.   tail.nextNode:=NIL
  170.   head.element:=NIL
  171.   tail.element:=NIL
  172.   newFileList.current:=newFileList.head
  173. ENDPROC newFileList
  174.   /* fl_New */
  175.  
  176. PROC fl_Insert(element:PTR TO fl_ElementType, list:PTR TO fl_ListType)
  177.   DEF newNode:PTR TO fl_NodeType,
  178.       current:PTR TO fl_NodeType,
  179.       newElement:PTR TO fl_ElementType
  180.   list.current:=list.head
  181.   WHILE (fl_NextNodeFrom(list.current)<>list.tail) AND
  182.         fl_IsLessThan(element, fl_ElementFrom(fl_NextNodeFrom(list.current)))
  183.     list.current:=fl_NextNodeFrom(list.current)
  184.   ENDWHILE
  185.   current:=list.current
  186.   newNode:=New(SIZEOF fl_NodeType)
  187.   newNode.element:=New(SIZEOF fl_ElementType)
  188.   newElement:=newNode.element
  189.   newElement.fileName:=element.fileName
  190.   newElement.fileSize:=element.fileSize
  191.   newElement.fileProtection:=element.fileProtection
  192.   newElement.days:=element.days
  193.   newElement.minute:=element.minute
  194.   newElement.tick:=element.tick
  195.   element.fileName:=NIL  /*-- detach pointer so that list owns it --*/
  196.   newNode.nextNode:=current.nextNode
  197.   current.nextNode:=newNode
  198. ENDPROC TRUE
  199.   /* fl_Insert */
  200.  
  201. PROC fl_RetrieveFirst(list:PTR TO fl_ListType)
  202.   IF fl_IsEmpty(list) THEN RETURN NIL
  203.   list.current:=fl_NextNodeFrom(list.head)
  204. ENDPROC fl_ElementFrom(list.current)
  205.   /* fl_RetrieveFirst */
  206.  
  207. PROC fl_RetrieveNext(list:PTR TO fl_ListType)
  208.   IF fl_IsEmpty(list) THEN RETURN NIL
  209.   IF fl_NextNodeFrom(list.current)=list.tail THEN RETURN NIL
  210.   list.current:=fl_NextNodeFrom(list.current)
  211. ENDPROC fl_ElementFrom(list.current)
  212.   /* fl_RetrieveNext */
  213.  
  214. PROC fl_RemoveCurrent(list:PTR TO fl_ListType)
  215.   DEF current:PTR TO fl_NodeType,
  216.       node:PTR TO fl_NodeType,
  217.       element:PTR TO fl_ElementType
  218.   IF fl_IsEmpty(list) THEN RETURN NIL
  219.   /*-- find node --*/
  220.   IF list.current=list.head THEN RETURN NIL
  221.     /*-- current undefined; must call one   --*/
  222.     /*-- of the functions that set current. --*/
  223.   IF list.current=list.tail THEN RETURN NIL
  224.     /*-- At end of list. --*/
  225.   current:=list.head
  226.   WHILE current.nextNode<>list.current DO current:=current.nextNode
  227.   /*-- detach node --*/
  228.   node:=list.current
  229.   current.nextNode:=node.nextNode
  230.   list.current:=current  /*-- set up for possible subsequent call to fl_RetrieveNext. --*/
  231.   /*-- remove element and deallocate node --*/
  232.   element:=node.element
  233.   Dispose(node)
  234. ENDPROC element
  235.   /* fl_RemoveCurrent */
  236.  
  237. /*=== End File List Implementation =======================================*/
  238.  
  239.  
  240. PROC enoughRoomOnDest(theDestInfo:PTR TO infodata, theElement:PTR TO fl_ElementType)
  241.   DEF numBytesFree, numBytesRequired, numFileExtensionBlocks,
  242.       numBytesForFileExtensionBlocks
  243.   IF theElement=NIL THEN RETURN FALSE
  244.   /*-- Compute what DOS says is free. --*/
  245.   numBytesFree:=Mul(theDestInfo.numblocks-theDestInfo.numblocksused,
  246.                     theDestInfo.bytesperblock)
  247.   IF argNoDosOverHead
  248.     numBytesRequired:=theElement.fileSize
  249.   ELSE
  250.     /*------------------------------------------*
  251.       Storage required by DOS filesystem =
  252.         file_size_in_bytes +
  253.         one_block_for_file_header +
  254.         number_file_extension_blocks_required *
  255.         bytes_per_block
  256.      *------------------------------------------*/
  257.     numFileExtensionBlocks:=Div(theElement.fileSize, SIZEOF_A_FILE_BLOCK)+1
  258.     numBytesForFileExtensionBlocks:=Mul(numFileExtensionBlocks,
  259.                                         theDestInfo.bytesperblock)
  260.     numBytesRequired:=theElement.fileSize+            /* file size        */
  261.                       theDestInfo.bytesperblock+      /* file header      */
  262.                       numBytesForFileExtensionBlocks+ /* extension blocks */
  263.                       (argErrorMargin*theDestInfo.bytesperblock)
  264.   ENDIF
  265. /*-- LEAVE THESE IN JUST IN CASE SOMEONE REPORTS ERRORS ------------------*
  266. WriteF('\n\nfilename                      \s', theElement.fileName)
  267. WriteF('\n filesize                       \d', theElement.fileSize)
  268. WriteF('\n numblocks                      \d', theDestInfo.numblocks)
  269. WriteF('\n numblocksused                  \d', theDestInfo.numblocksused)
  270. WriteF('\n bytesperblock                  \d', theDestInfo.bytesperblock)
  271. WriteF('\n numbytesfree                   \d', numBytesFree)
  272. WriteF('\n numFileExtensionBlocks         \d', numFileExtensionBlocks)
  273. WriteF('\n numBytesForFileExtensionBlocks \d', numBytesForFileExtensionBlocks)
  274. WriteF('\n numBytesRequired               \d', numBytesRequired)
  275. Raise(0)
  276.  *------------------------------------------------------------------------*/
  277. ENDPROC numBytesRequired<=numBytesFree
  278.   /* enoughRoomOnDest */
  279.  
  280. PROC moveFile(theElement:PTR TO fl_ElementType) HANDLE
  281.   DEF rc, sLock=NIL, dLock=NIL, sFib:fileinfoblock, dFib:fileinfoblock
  282.   StringF (sourcePathAndFilename, '\s\s', sourceDir, theElement.fileName)
  283.   StringF (destPathAndFilename, '\s\s\s',
  284.            argDestPath,
  285.            IF Char(argDestPath+StrLen(argDestPath)-1)=":" THEN '' ELSE '/',
  286.            theElement.fileName)
  287.   WriteF ('\n   \s \s ...',
  288.           IF optionIsSet_CopyOnly THEN 'Copying' ELSE 'Moving',
  289.           sourcePathAndFilename)
  290.   IF dosCopy_args=NIL THEN dosCopy_args:=String(512)
  291.   StringF(dosCopy_args, '\s \s\s\s QUIET\n',
  292.           sourcePathAndFilename, destPathAndFilename,
  293.           IF optionIsSet_Clone THEN ' CLONE' ELSE '',
  294.           IF optionIsSet_NoPro THEN ' NOPRO' ELSE '')
  295.   rc:=RunCommand(dosCopy_segList, stackSize, dosCopy_args, StrLen(dosCopy_args))
  296.   IF rc=-1
  297.     Raise("STAK")
  298.   ELSE
  299.     rc:=IoErr()
  300.     IF (sLock:=Lock(sourcePathAndFilename, SHARED_LOCK))=NIL THEN Raise(ER_SOURCE_LOCK)
  301.     IF Examine(sLock, sFib)=FALSE THEN Raise(ER_SOURCE_EXAM)
  302.     IF (dLock:=Lock(destPathAndFilename, SHARED_LOCK))=NIL
  303.       Raise(IF rc=0 THEN ER_USER_ABORT ELSE ER_WONT_FIT)
  304.     ELSE
  305.       IF Examine(dLock, dFib)=FALSE THEN Raise(ER_DEST_EXAM)
  306.       IF dFib.size<>sFib.size
  307.         IF rc=0 THEN Raise(ER_USER_ABORT)
  308.         Raise(ER_WONT_FIT)
  309.       ENDIF
  310.     ENDIF
  311.   ENDIF
  312.   UnLock(sLock)
  313.   UnLock(dLock)
  314.   IF optionIsSet_CopyOnly
  315.     WriteF(' copied.')
  316.   ELSE
  317.     DeleteFile(sourcePathAndFilename)
  318.     WriteF(' moved.')
  319.   ENDIF
  320. EXCEPT
  321.   IF sLock THEN UnLock(sLock)
  322.   IF dLock THEN UnLock(dLock)
  323.   DeleteFile(destPathAndFilename)
  324.   IF exception=ER_WONT_FIT
  325.     WriteF('\n didn\at fit.  Trying a smaller one.')
  326.     RETURN FALSE
  327.   ELSE
  328.     Raise(exception)
  329.   ENDIF
  330. ENDPROC TRUE
  331.   /* moveFile */
  332.  
  333. PROC ds_daysFrom(dateStamp:PTR TO datestamp) RETURN dateStamp.days
  334. PROC ds_minuteFrom(dateStamp:PTR TO datestamp) RETURN dateStamp.minute
  335. PROC ds_tickFrom(dateStamp:PTR TO datestamp) RETURN dateStamp.tick
  336.  
  337. PROC loadDosCopyCommand()
  338.   IF (dosCopy_segList:=LoadSeg('C:Copy'))=NIL THEN Raise("LSEG")
  339.   stackSize:=4000
  340. ENDPROC
  341.   /* loadDosCopyCommand */
  342.  
  343. PROC unloadDosCopyCommand()
  344.   IF dosCopy_segList THEN UnLoadSeg(dosCopy_segList)
  345. ENDPROC
  346.   /* unloadDosCopyCommand */
  347.  
  348. PROC main() HANDLE
  349.   DEF anchorPath:anchorpath,  /* Arp object. */
  350.       sourceFib:fileinfoblock,
  351.       destLock=NIL,
  352.       destInfo:infodata,
  353.       fileList:PTR TO fl_ListType,
  354.       element:PTR TO fl_ElementType,
  355.       sourceFindSuccess, fileName,
  356.       checkingForFile, char, newDisk=TRUE, filesMoved=0
  357.   '$VER: Fill 1.2a (5.24.94)'
  358.   WriteF('\n   Fill V1.2a.  Floppy Storage Optimizer.' +
  359.          '\n   Copyright ©1993-1994 Barry Wills.  All rights reserved.')
  360.   /*-- Init external support. --*/
  361.   IF (arpbase:=OpenLibrary('arp.library', 39))=NIL THEN Raise(ER_OPEN_ARPLIBRARY)
  362.   loadDosCopyCommand()
  363.   /*-- Get command line arguments. --*/
  364.   IF arg[]=0 THEN Raise(ER_USAGE)
  365.   fileList:=fl_New()
  366.   parseCommandLineArguments()
  367.   /*-- Check destination validity. --*/
  368.   IF (destLock:=Lock(argDestPath, SHARED_LOCK))=NIL THEN Raise(ER_DEST_LOCK)
  369.   /*-- Check source validity. --*/
  370.   anchorPath.breakbits:=SIGBREAKF_CTRL_C  /*-- Arp: allow user to abort. --*/
  371.   anchorPath.strlen:=0
  372.   IF (sourceFindSuccess:=FindFirst(argSourceSpec, anchorPath))<>0 THEN Raise (ER_SOURCE_SPEC)
  373.   /*-- Get source file list. --*/
  374.   WriteF('\n\n   Getting file list.')
  375.   /*-- Put filenames and sizes in a list, sorted on filesize by fl_Insert(). --*/
  376.   WHILE sourceFindSuccess=0
  377.     sourceFib:=anchorPath.info
  378.     IF sourceFib.direntrytype<0
  379.       /*fileName:=String(108)*/
  380.       fileName:=String(StrLen(sourceFib.filename))
  381.       StrCopy(fileName, sourceFib.filename, ALL)
  382.       fl_Insert([fileName, sourceFib.size, sourceFib.protection,
  383.                  ds_daysFrom(sourceFib.datestamp),
  384.                  ds_minuteFrom(sourceFib.datestamp),
  385.                  ds_tickFrom(sourceFib.datestamp)],
  386.                 fileList)
  387.     ENDIF
  388.     sourceFindSuccess:=FindNext(anchorPath)
  389.   ENDWHILE
  390.   /*-- Finished with ARP. --*/
  391.   FreeAnchorChain(anchorPath); anchorPath:=NIL
  392.   CloseLibrary(arpbase); arpbase:=NIL
  393.   /*-- Move files. --*/
  394.   WHILE fl_IsEmpty(fileList)=FALSE
  395.     IF newDisk OR (element=NIL)
  396.       element:=fl_RetrieveFirst(fileList)
  397.       newDisk:=FALSE
  398.       filesMoved:=0
  399.       WriteF('\n')
  400.     ELSE
  401.       element:=fl_RetrieveNext(fileList)
  402.     ENDIF
  403.     IF Info(destLock, destInfo)=FALSE THEN Raise(ER_DEST_INFO)
  404.     checkingForFile:=TRUE
  405.     WHILE checkingForFile
  406.       IF element=NIL
  407.         checkingForFile:=FALSE
  408.       ELSEIF enoughRoomOnDest(destInfo, element)
  409.         checkingForFile:=FALSE
  410.       ELSE
  411.         element:=fl_RetrieveNext(fileList)
  412.       ENDIF
  413.     ENDWHILE
  414.     IF element<>NIL
  415.       element:=fl_RemoveCurrent(fileList)
  416.       moveFile(element)
  417.       INC filesMoved
  418.       Dispose(element)
  419.     ELSEIF destInfo.numblocksused=numblocksUsedOnABlankDisk
  420.       Raise(ER_FILES_TOO_LARGE)
  421.     ELSE
  422.       /*-- Disk filled; prompt for another. --*/
  423.       WriteF('\n\s', IF filesMoved THEN '' ELSE '\nNo files moved/copied.')
  424.       WriteF('\nUnused bytes = \d.',
  425.              Mul(destInfo.numblocks-destInfo.numblocksused,
  426.                  destInfo.bytesperblock))
  427.       WriteF('\nInsert next volume.  Press RETURN to proceed, \aQ\a or \aq\a to discontinue...')
  428.       char:=Inp(stdout)
  429.       IF char<>10 THEN WHILE Inp(stdout)<>10 DO NOP
  430.       IF upperChar(char)="Q" THEN Raise(ER_USER_ABORT)
  431.       UnLock(destLock)
  432.       IF (destLock:=Lock(argDestPath, SHARED_LOCK))=NIL THEN Raise(ER_DEST_LOCK)
  433.       newDisk:=TRUE
  434.     ENDIF
  435.   ENDWHILE
  436.   /*-- Display unused bytes on destination before leaving program. --*/
  437.   IF Info(destLock, destInfo)=FALSE THEN Raise(ER_DEST_INFO)
  438.   WriteF('\n\nUnused bytes = \d.',
  439.          Mul(destInfo.numblocks-destInfo.numblocksused, destInfo.bytesperblock))
  440.   /*-- Clean up. --*/
  441.   UnLock(destLock)
  442.   unloadDosCopyCommand()
  443.   WriteF('\n\n')
  444.   CleanUp(0);
  445. EXCEPT
  446.   WriteF('\n\n')
  447.   SELECT exception
  448.     CASE ER_OPEN_ARPLIBRARY;
  449.       WriteF('Error opening arp.library V39+')
  450.     CASE ER_USAGE
  451.       WriteF('   Usage:  Fill [<options>] <source> <dest>' +
  452.              '\n     <source>  Any valid DOS "dev:dir/filepat", ARP wildcards supported' +
  453.              '\n     <dest>    Any valid DOS "dev:dir"' +
  454.              '\n     [<options>]' +
  455.              '\n      -c    Copy files only, don''t delete source (default MOVE FILES)' +
  456.              '\n      -e##  Error margin, add blocks to storage estimate (1-\d; default 0)' +
  457.              '\n      -n    No DOS overhead considerations (use on MS-DOS floppies)' +
  458.              '\n      -p    Don''t transfer protection bits (use standard rwed)' +
  459.              '\n      -x    Don''t clone protection bits, date, and time',
  460.              MAX_ARG_ERRORMARGIN)
  461.     CASE ER_DEST_LOCK;    WriteF(' *** Destination \s does not exist', argDestPath)
  462.     CASE ER_SOURCE_SPEC;  WriteF(' *** No entries found')
  463.     CASE ER_MEM;          WriteF(' *** Insufficient memory')
  464.     CASE ER_DEST_INFO;    WriteF(' *** Error occurred while getting destination Info')
  465.     CASE ER_FILES_TOO_LARGE
  466.                           WriteF(' *** Remaining file(s) too large to fit on destination')
  467.     CASE ER_USER_ABORT;   WriteF(' *** Program aborted by request')
  468.     CASE "LSEG";          WriteF(' *** Could not load DOS Copy command')
  469.     CASE "STAK";          WriteF(' *** Out of stack space')
  470.     DEFAULT;              WriteF(' *** ERROR:  result \d', exception)
  471.   ENDSELECT
  472.   IF destLock THEN UnLock(destLock)
  473.   unloadDosCopyCommand()
  474.   /*-- Arp stuff. --*/
  475.   IF anchorPath THEN FreeAnchorChain(anchorPath)
  476.   IF arpbase THEN CloseLibrary(arpbase)
  477.   WriteF('\n\n')
  478.   CleanUp(RETURN_WARN);
  479. ENDPROC
  480.