home *** CD-ROM | disk | FTP | other *** search
/ ftp.barnyard.co.uk / 2015.02.ftp.barnyard.co.uk.tar / ftp.barnyard.co.uk / cpm / walnut-creek-CDROM / JSAGE / ZSUS / PROGPACK / MEYERTUT.LBR / MEYER11.TZT / MEYER11.TXT
Text File  |  2000-06-30  |  25KB  |  778 lines

  1.         CP/M Asssembly Language: Part XI
  2.             (a) Enhanced Console I/O
  3.              (b) The Disk Directory
  4.               by Eric Meyer
  5.  
  6.      This month's installment is mostly about the structure of
  7. the CP/M disk directory, and how to use it. As an exercise we
  8. will construct a utility, XREN, that can rename a group of files
  9. using wildcards. But first, I want to return to the subject of
  10. console I/O and offer some enhancements to what we have
  11. previously covered.
  12.  
  13.  
  14. 1. Console Status
  15.      Long ago, we described how to use BDOS 1 and 2 to get
  16. console input, and to send a character to the console. However,
  17. there are a few refinements of which you should be aware.
  18.      First, it is useful to be able to tell whether or not a key
  19. has been pressed, without actually reading it in if it has.
  20.      Suppose a program is displaying a real-time clock on screen
  21. while waiting for user input. If it does this:
  22.  
  23.     CALL    CKDISP    ; Display the clock time
  24.     MVI    C,1
  25.     CALL    BDOS    ; Get key input
  26.  
  27. everything (including its clock display) will stop until a
  28. key is pressed! The solution is to use BDOS 11 (Console
  29. Status) in a loop that only goes to read a character when it
  30. knows there's one waiting:
  31.  
  32. LOOP:    CALL    CKDISP    ; Display the clock time
  33.     MVI    C,11    ; Check console status
  34.     CALL    BDOS    ; Has a key been pressed?
  35.     ORA    A    ; If not, loop and
  36.     JZ    LOOP    ;   redisplay until one has
  37.  
  38. BDOS 11 returns zero in A if there is no key waiting to
  39. be read from the keyboard, and nonzero if there is one. Similar
  40. tricks are used by text editors that mustn't take the time to
  41. redisplay the screen while you're still typing in text, games
  42. that want to keep the enemy closing in on you while you're still
  43. deciding which direction to fire in, and so on.
  44.  
  45.  
  46. 2. Direct Console I/O
  47.      BDOS 1 and 2 are not "direct" I/O functions: they both
  48. do some extra processing, which you may sometimes not want.
  49. BDOS 1 echos the character it gets to the console;
  50. BDOS 2 does some interpreting of the outgoing character
  51. (e.g., expanding tabs to spaces). The BDOS 6 function
  52. provides "direct" console I/O, for times when you want to read in
  53. a character without echo, or to send out a character without
  54. translation.
  55.      For output, you place the character in the E register; for
  56. input, you put 0FFh there. Thus the sequence:
  57.  
  58.     MVI    E,CHAR
  59.     MVI    C,6    ; Direct console I/O
  60.     CALL    BDOS
  61.  
  62. will send "CHAR" directly to the console.
  63.      The input function is kind of a combination of BDOS 1
  64. and 11 (input and status):
  65.  
  66.     MVI    E,0FFH
  67.     MVI    C,6
  68.     CALL    BDOS
  69.  
  70. will return 0 if no key has been pressed. If there is a key,
  71. it will return it without echoing it.
  72.      So if you want to wait and get a character without echo, you
  73. can do this:
  74.  
  75. LOOP:    MVI    E,0FFH
  76.     MVI    C,6
  77.     CALL    BDOS
  78.     ORA    A    ; Keep trying until key ready
  79.     JZ    LOOP
  80.  
  81.      This is very useful: there are times when you don't want
  82. the character typed to show on the screen at all, or at least not
  83. until you determine whether it was legal.
  84.      The above method works under both CP/M 2.2 and 3.0.
  85. In addition, CP/M 3.0 adds two more features to BDOS
  86. 6:  if you MVI E,0FEH, you get a plain console status function
  87. (like BDOS 11), and if you MVI E,0FDH, you get plain
  88. console input.
  89.      You may be tempted to use the "FD" option in place of the
  90. LOOP above, as it's simpler, but remember that CP/M
  91. 2.2 doesn't support this. In fact, if you run either of these
  92. options under CP/M 2.2, they will send a funny character
  93. (probably "}" or "~") to the console, and not read in anything at
  94. all! So it may be best to stick with the "FF" option alone.
  95.  
  96.  
  97. 3. The Disk Directory
  98.      Now back to our main topic: the CP/M disk directory.
  99. You need to know how this is put together if you want to write
  100. programs to display a directory, rename files, etc. You also can
  101. use a "disk doctor" program like DU to UNerase files, move them
  102. to different user areas, and so on, if you know how a directory
  103. is organized.
  104.      CP/M divides a disk into three parts:
  105.  
  106. *  The "system tracks", used to "boot" the CP/M system;
  107. *  The "directory"; and
  108. *  The "data area".
  109.  
  110.      On an Osborne SSDD (200k) disk, for example, the first 15K
  111. is the system tracks, then 2K for the directory, then 183K of
  112. data space, allocated in 1K blocks. Each directory entry takes 32
  113. bytes, so 2K will hold 64 entries.
  114.      The entry looks very much like the File Control Block (FCB)
  115. whose structure we've already examined. (This is no coincidence;
  116. when you open a file, CP/M uses the FCB as a "working copy" of
  117. the directory entry.)
  118.  
  119. FCB:    d F I L E N A M E T Y P e x x x
  120.     x x x x x x x x x x x x x x x x
  121.     c r r r
  122.  
  123.      The actual directory entry structure is:
  124.  
  125.     u F I L E N A M E T Y P e x x f
  126.     g g g g g g g g g g g g g g g g
  127.  
  128.      The filename and "e"xtent byte are in the same place --
  129. we'll talk about the "f"illed byte in a minute. The "d"rive byte
  130. has turned into a "u"ser byte. This disk may be logged in as any
  131. CP/M drive, so this position is used to store the user area of
  132. the file. These run from 00 to 0F (15). If you see "E5" in the
  133. user byte, the file has been erased, and its allocated groups may
  134. now be in use by another program, so better not mess with it, for
  135. now.
  136.      CP/M Plus also uses some other values, like "20" (32), to
  137. indicate disk labels, password XFCBs, and time/date stamp areas,
  138. all of which occupy directory space. We will ignore these.
  139.      The "g"roup bytes record where on the disk the file data has
  140. been stored. An allocation "group" is a block of space on the
  141. disk, excluding the system tracks. On an Osborne SSDD disk, each
  142. block is 1k; the directory occupies groups 0-1, and the rest (2 .
  143. . .) are used for file storage.
  144.      When you first copy files onto a disk, the groups will be
  145. nicely sequential, e.g.,
  146.  
  147. 0  P  I  P  _  _  _  _    _  C  O  M  0  x  x  x
  148. 02 03 04 05 06 07 00 00 00 00 00 00 00 00 00 00
  149. 0  X  D  I  R  _  _  _    _  C  O  M  0  x  x  x
  150. 08 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  151.  
  152.      But after you've erased a few files and overwritten them,
  153. causing the groups to be reassigned, things will get more
  154. complicated. Notice that there's room for 16 groups, or 16K, also
  155. called one "extent". (It is possible for a larger disk to have a
  156. 2K or 4K block size, in which case each entry holds 32K or 64K,
  157. two or four extents.) What happens when a file grows larger than
  158. this? It has to continue into another directory entry.
  159.  
  160. 0  H  U  G  E  _  _  _    _  F  I  L  0  x  x  80
  161. 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11
  162. 0  H  U  G  E  _  _  _    _  F  I  L  1  x  x  17
  163. 12 13 14 15 00 00 00 00 00 00 00 00 00 00 00 00
  164.  
  165.      Note how the "e"xtent byte shows that the first entry is
  166. extent 0, the second is extent 1. Observe also the "f"illed byte:
  167. this tells you how many records in the extent are occupied. If
  168. the extent is full, you'll see 80h (128), if not (as is usually
  169. the case for the last extent of a file), you'll see something
  170. less.
  171.      One final caveat. You may find that the high bit is set on
  172. one or more of the file name characters in the directory. This is
  173. where CP/M stores the file "attributes" like SYStem. You
  174. will often want to mask this off when manipulating filenames
  175. gotten from a directory.
  176.  
  177.  
  178. 4. Searching for Files
  179.      You can't read or write the directory tracks directly using
  180. BDOS calls. All you can do is search for particular
  181. filenames using the BDOS functions 17 (Search First) and 18
  182. (Search Next).
  183.      Suppose you want to know whether HUGE.FIL is in the
  184. directory of disk A:. You would set up an FCB for
  185. A:HUGE.FIL:
  186.  
  187. FCB:    1 H U G E _ _ _ _ F I L 0 x x x
  188.     x x x x x x x x x x x x x x x x
  189.     0 x x x
  190.  
  191.      Now you could try to open the file; an error would
  192. indicate that it didn't exist. But let's use a more
  193. sophisticated method:
  194.  
  195.     LXI    D,FCB
  196.     MVI    C,17    ; Search First
  197.     CALL    BDOS
  198.  
  199.      Now we get a whole wealth of information!
  200.      If the file didn't exist, the Accumulator will contain 0FFh.
  201. But if it did, A will contain a value from 0-3, and the current
  202. DMA (0080h, if you haven't changed it) will contain the record
  203. from the directory that contains this entry (and three others).
  204.      The code 0-3 tells you which one of these is the one you
  205. wanted: multiply by 32, then add the DMA address, and you will be
  206. pointing to it. Here's the whole sequence (assuming
  207. DMA=0080):
  208.  
  209.     LXI    D,FCB
  210.     MVI    C,17    ; Search First
  211.     CALL    BDOS
  212.     CPI    0FFH
  213.     JZ    NOFILE    ; Quit if not found
  214.     ADD    A
  215.     ADD    A
  216.     ADD    A
  217.     ADD    A
  218.     ADD    A    ; Code * 32
  219.     MOV    E,A
  220.     D,0        ; Put it in DE
  221.     LXI    H,0080H
  222.     DAD    D    ; Add DMA, pointer to entry
  223.  
  224.      At this point, HL points to the start of the desired
  225. directory entry. You could now do all sorts of things: examine
  226. the attribute bits to see whether the file is SYStem or R/O; try
  227. to figure out how big the file is . . .
  228.      But this is most commonly used in conjunction with the
  229. Search Next command, because you can find any number of specific
  230. files that match some ambiguous (general) pattern. The character
  231. "?" in an FCB serves as a "wildcard" to match any letter of a
  232. filename. When you type a "*" in a filename, CP/M fills out
  233. all the remaining places with "?".
  234.      So for example, if you type:
  235.  
  236. A>DIR B:*.COM
  237.  
  238. CP/M parses this into the default FCB (at 005Ch) as follows:
  239.  
  240. 005C:    2 ? ? ? ? ? ? ? ? C O M 0 x x x
  241.     x x x x x x x x x x x x x x x x
  242.     0 x x x
  243.  
  244.      It will then use BDOS 17 and 18 to find, one at a
  245. time, every file that matches that description. After BDOS
  246. 17 is used once to establish that some files exist, BDOS 18
  247. can be used repeatedly to find all the rest that match. This is
  248. how CP/M's "DIR" function works.
  249.      It's important to note that most BDOS file functions
  250. do not work with "ambiguous" filenames (wildcards). The only ones
  251. that do work are these two Search functions, and Delete File. (Be
  252. careful with that one!) As an exercise, you might consider
  253. writing your own little "XDIR" program. Use the code above
  254. (remember to substitute BDOS 18 for 17 after the first
  255. find) to find each file and point to the name, then print out the
  256. string.
  257.      Once it works, you can refine it by deciding for yourself
  258. how to separate one from another, whether to add a "." between
  259. the file name and type, and how to keep track of when to start a
  260. new line to keep the display neat.
  261.      If you get lazy, peek ahead to parts of the XREN program
  262. coming up. If you really want a challenge, consider trying to
  263. print the names out in alphabetical order.
  264.  
  265.  
  266. 5. A File by Any Other Name . . .
  267.      Of course, you already have at least one program like XDIR
  268. that does this, and more. So our programming example here will be
  269. something more complicated -- an XREN command, that allows
  270. renaming groups of files.
  271.      As you may already know, there is a BDOS function 23,
  272. Rename File. To use it, you put the original filename at the
  273. start of the FCB, and the new one in the middle, e.g.,
  274.  
  275. FCB:    d O L D N A M E _ T Y P x x x x
  276.     d N E W N A M E _ T Y P x x x x
  277.     x x x x
  278.  
  279. and then you call BDOS 23:
  280.  
  281.     LXI    D,FCB
  282.     MVI    C,23    ; Rename File
  283.     CALL    BDOS
  284.  
  285.      This should return 0 if successful. You will get an
  286. error if the old name didn't exist, or the new one already does,
  287. or the file is read/only, and so forth. (A word about these
  288. errors: you want to avoid them wherever possible, because they
  289. will often cause an immediate BDOS error message and warm
  290. boot. CP/M is rather uncivil about these things.)
  291.      There are times when you would like to rename files en
  292. masse; e.g.,
  293.  
  294. A>REN B:*.COM=B:*.OBJ
  295.  
  296. really ought to be able to rename all the .OBJ files on B: to COM.
  297. Unfortunately this won't work: the simple CP/M REN command
  298. (and BDOS 23, which it uses) only work with "unambiguous"
  299. names.
  300.      Of course, now we know enough to write our own program to
  301. successively find each specific filename that matches, and rename
  302. them one at a time with BDOS 23. We'll call it XREN, and
  303. its syntax will be nearly the same as REN:
  304.  
  305. A>XREN NEWNAME OLDNAME
  306.  
  307. except that now the names can contain wildcards. (Note that no
  308. "=" is used; more on this below.)
  309.      But before the listing, a few preliminary remarks. First,
  310. one thing about "Search Next". It has to be used more or less
  311. consecutively, i.e., most disk operations will disturb the search
  312. sequence if used. This includes trying to open files, or starting
  313. another search. (XREN, for example, is going to have to check
  314. each time whether a file by the new name already exists, and ask
  315. whether you want to overwrite it.)
  316.      For this reason, the best technique is to get all the names
  317. at once, saving them in a list to be processed afterwards. Mass
  318. renaming presents some problems that aren't apparent at first
  319. glance. One is the fact just alluded to, that (some) of the
  320. destination file names may already exist.
  321.      Besides this, unless both source and destination names have
  322. wildcards in exactly the same places, it can be challenging to
  323. decide exactly what you meant to do!  For example, consider:
  324.  
  325. A>XREN MAIL*.* LETTER*.*
  326.  
  327.      When this comes upon the file LETTER06.BAK, what
  328. should it call it? You might expect MAIL06.BAK, but can you
  329. see that this requires some guesswork, and moving characters from
  330. one position to another in the filename?
  331.      You can try to implement this if you want, but for now I
  332. will stick with a simpler alternative: a one-to-one
  333. correspondence between letters. So XREN in this case will
  334. rename LETTER06.BAK to MAILER06.BAK. Similarly, what
  335. are we to do when there are more wildcards in the source, for
  336. example:
  337.  
  338. A>XREN LETTER*.* MAIL*.*
  339.  
  340.      Again, you might want this to rename MAIL06.BAK to
  341. LETTER06.BAK, but this is not straightforward. What would
  342. happen to MAIL1234? (LETTER1234 won't fit.) Would you
  343. want it to produce LETTER12? LETTER34?
  344.      Again I will opt for a simple correspondence, so that in
  345. this case MAIL1234 will be renamed to LETTER34. (Note
  346. a potential problem: XREN also would try to rename
  347. MAIL6534, etc., to the same thing!)
  348.      I will leave you the option of playing it really safe, and
  349. aborting with an error if the source has more wildcards than the
  350. destination. See the optional bit of code in the VERIFY
  351. routine below.
  352.      I've gone into all this detail so you will understand why
  353. the simple CCP "REN" command can't handle mass renaming. Since
  354. the whole process can be so confusing, we will have XREN
  355. list out each file renamed at the console, like this:
  356.  
  357. A:MAIL01  .BAK = A:LETR01  .BAK
  358.  
  359.      If the destination already exists, we will ask "Overwrite?"
  360. and get a "Y"es/"N"o answer. And in any case we will keep an eye
  361. on the keyboard so you can abort at any time with ^C if you don't
  362. like what you see. (Note how our new BDOS 6 call is ideal
  363. for this.)
  364.      One final remark: if you have CP/M 3.0, the "transient"
  365. portion of the REN command (RENAME.COM) is in fact capable of
  366. mass renaming, and you may not want to go to the trouble of
  367. generating XREN for yourself. But you may still find it
  368. interesting to see how it's done.
  369.  
  370.  
  371. 6. Mass Renaming: XREN
  372.      Here, then, is the complete source code for XREN. Most
  373. of it should seem familiar by now. Comments are still provided,
  374. but not as extensively as in the past.
  375.  
  376. ;*** XREN.ASM - Global Rename utility
  377. ;*** Version 1.0 - for 8080 CP/M 2.2
  378. ;
  379. BDOS    EQU    0005H     ; Page Zero equates
  380. FCB    EQU    005CH
  381. FCB2    EQU    006CH
  382. DMA    EQU    0080H
  383. ;
  384.     ORG    0100H
  385. ;
  386.     CALL    VERIFY    ; Ensure legal arguments
  387.     JC    ERROR
  388.     LXI    H,FCB2    ; copy source into SFCB
  389.     LXI    D,SFCB
  390.     LXI    B,16
  391.     CALL    LDIR
  392.     SUB    A    ; Initialize count to 0
  393.     STA    FILES
  394.     MVI    C,17    ; Search First
  395.     JMP    FIND0
  396. ;
  397. FIND:    MVI    C,18    ; Loop to find the files (Search next)
  398. ;
  399. FIND0:    LXI    D,SFCB    ; Entry for first time
  400.     CALL    BDOS
  401.     CPI    0FFH
  402.     JZ    GOTALL    ; Quit if not found
  403.     LXI    H,DMA    ; Add 32*A to DMA to
  404.     CALL    A32HL    ;   point to found file
  405.     XCHG
  406.     LXI    H,FILES ; Get file count
  407.     MOV    A,M
  408.     INR    M    ; Increment it
  409.     JZ    TOOMNY    ; Error if overflow past 255
  410.     LXI    H,FLIST
  411.     CALL    A16HL    ; Point to next list entry
  412.     XCHG
  413.     LXI    B,16    ; Put the name there
  414.     CALL    LDIR
  415.     JMP    FIND
  416. ;
  417. GOTALL: LDA    FILES    ; Were there any?
  418.     ORA    A
  419.     JZ    NOFMSG    ; If not, say so and quit
  420. ;
  421. RENAME: LDA    FILES    ; Loop to rename the files
  422.     DCR    A
  423.     LXI    H,FLIST
  424.     CALL    A16HL    ; Point to last source
  425.     LXI    D,RFCB
  426.     LDA    FCB    ; Copy drive number from FCB,
  427.     STAX    D    ;   NOT user from directory
  428.     INX    D
  429.     INX    H
  430.     LXI    B,15    ; Copy rest of source entry
  431.     CALL    LDIR
  432.     LXI    H,FCB    ; Copy in destination mask
  433.     LXI    D,RFCB2
  434.     LXI    B,16
  435.     CALL    LDIR
  436.     CALL    NEWNAM    ; Construct new name from mask
  437.     CALL    SHOWIT    ; Show "NEWNAME=OLDNAME"
  438.     LXI    H,RFCB2
  439.     LXI    D,SFCB    ; Copy new name to SFCB for test
  440.     LXI    B,16
  441.     CALL    LDIR
  442.     CALL    EXTEST    ; Free to overwrite exiting file?
  443.     JC    NORENM    ; Skip following if "No"
  444.     LXI    D,RFCB
  445.     MVI    C,23    ; Rename File
  446.     CALL    BDOS
  447.     ORA    A
  448.     JNZ    ERROR
  449. ;
  450. NORENM: CALL    INKEY    ; Check keyboard
  451.     CPI    3    ; Abort on ^C
  452.     JZ    ABORT
  453.     CALL    CRLF    ; Show new line
  454.     LXI    H,FILES
  455.     DCR    M    ; Count down on FILES
  456.     JNZ   R  ENAME
  457. ;
  458. DONE:    RET        ; All finished, exit quietly
  459. ;
  460. ; --- Messages ---
  461. ;
  462. NOFMSG: CALL    SPMSG    ; Source file was not found
  463.     DB    '<No File>',0
  464.     JMP    DONE
  465. ;
  466. ERROR:      CALL    SPMSG    ; Something wrong w/ arguments
  467.       DB    '<Error>',0
  468.       JMP    DONE
  469. ;
  470. TOOMNY: CALL    SPMSG    ; Over 255 files match source
  471.     DB    '<Too many>',0
  472.     JMP    DONE
  473. ;
  474. ABORT:    CALL    SPMSG    ; You pressed ^C
  475.     DB    '<Abort>',0
  476.     JMP    DONE
  477. ;
  478. ; --- subroutines ---
  479. ;
  480. VERIFY: MVI    C,25    ; Verify argts are legal, Carry=bad
  481.     CALL    BDOS    ; Get logged drive
  482.     INR    A    ; Adjust from 0 to 1=A:
  483.     MOV    C,A
  484.     LXI    D,FCB
  485.     LXI    H,FCB2
  486.     LDAX    D
  487.     ORA    A    ; If either argt has no drive,
  488.     JNZ    VERF01    ; (0 = default drive)
  489.     MOV    A,C    ; Insert the correct drive
  490.     STAX    D
  491. ;
  492. VERF01: MOV    A,M
  493.     ORA    A
  494.     JNZ    VERF02
  495.     MOV    M,C
  496. ;
  497. VERF02: LDAX    D
  498.     CMP    M    ; Drives must be same
  499.     JNZ    VERFNG
  500. ;
  501. ;---Omit or include as desired: ---
  502. ;
  503. ;    INX    H    ; "Play it safe" code
  504. ;    INX    D
  505. ;    MVI    B,11
  506. ;
  507. ;VERFLP:MOV    A,M    ; If "?" in source,
  508. ;    CPI    '?'
  509. ;    JNZ    VERFOK
  510. ;    LDAX    D
  511. ;    CPI    '?'    ; Must have in dest too
  512. ;    JNZ    VERFNG
  513. ;
  514. ;VERFOK:INX    H
  515. ;    INX    D
  516. ;    DCR    B
  517. ;    JNZ    VERFLP
  518. ;----------------------------------
  519.     ORA    A    ; OK, clear Carry
  520.     RET
  521. ;
  522. VERFNG: STC        ; No good, set Carry
  523.     RET
  524. ;
  525. NEWNAM: LXI    H,RFCB    ; Construct new name from dest. mask
  526.     MVI    B,32
  527. ;
  528. NEWN00: MOV    A,M    ; First strip parity from both
  529.     ANI    7FH    ;   (for matching and display)
  530.     MOV    M,A
  531.     INX    H
  532.     DCR    B
  533.     JNZ    NEWN00
  534.     LXI    H,RFCB+1 ;Then check mask for "?"s
  535.     LXI    D,RFCB2+1
  536.     MVI    B,11
  537. ;
  538. NEWNLP: LDAX    D    ; Got a "?"?
  539.     CPI    '?'
  540.     JNZ    NEWN01
  541.     MOV    A,M    ; Yes, replace from old name
  542.     STAX    D
  543. ;
  544. NEWN01: INX    H    ; Continue
  545.     INX    D
  546.     DCR    B
  547.     JNZ    NEWNLP
  548.     RET
  549. ;
  550. SHOWIT: LXI    H,RFCB2 ; Show "NEWNAME=OLDNAME" msg
  551.     CALL    SHOSUB    ; Do first name
  552.     CALL    SPMSG    ; Then separator
  553.     DB    ' = ',0
  554.     LXI    H,RFCB    ; Then second name
  555. ;
  556. SHOSUB: MOV    A,M    ; Subroutine to show one name
  557.     ADI    'A'-1
  558.     CALL    CONOUT    ; Show drive
  559.     MVI    A,':'
  560.     CALL    CONOUT    ; Then colon
  561.     INX    H
  562.     MVI    B,8    ; Then name
  563.     CALL    SHOSTR
  564.     MVI    A,'.'    ; Then period
  565.     CALL    CONOUT
  566.     MVI    B,3    ; Finally, type
  567. ;
  568. SHOSTR: MOV    A,M    ;Subroutine to show string
  569.     CALL    CONOUT
  570.     INX    H
  571.     DCR    B    ; Do B characters
  572.     JNZ    SHOSTR
  573.     RET
  574. ;
  575. EXTEST: LXI    D,SFCB    ; Check for existing file NEWNAM
  576.     MVI    C,17    ;   (return Carry to avoid overwrite
  577.     CALL    BDOS    ; Search First
  578.     CPI    0FFH
  579.     RZ        ; Okay, doesn't exist (carry clear)
  580.     LXI    H,SFCB
  581.     LXI    D,RFCB    ; Hmm, does exist...
  582.     MVI    B,11
  583. ;
  584. EXTLP:    LDAX    D    ; Are names same?
  585.     CMP    M
  586.     JNZ    EXTST0
  587.     INX    H
  588.     INX    D
  589.     DCR    B
  590.     JNZ    EXTLP
  591.     CALL    SPMSG    ; Yes, don't try to change
  592.     DB    ' (Unchanged)',0
  593.     STC        ; Carry set
  594.     RET
  595. ;
  596. EXTST0: CALL    SPMSG    ;Different, ask what to do
  597.     DB    ' Overwrite? ',0
  598.     CALL    CONIN
  599.     CPI    3    ; CTL-C
  600.     JZ    ABORT
  601.     CALL    UCASE    ; Uppercase "y"="Y"
  602.     CPI    'Y'
  603.     JZ    EXTST1
  604.     STC        ; Said No, don't overwrite
  605.     RET
  606. ;
  607. EXTST1: LXI    D,SFCB    ; Said YES, overwrite
  608.     MVI    C,19    ; Delete File
  609.     CALL    BDOS
  610.     CPI    0FFH
  611.     CMC        ; Set carry if error
  612.     RET
  613. ;
  614. SPMSG:    XTHL        ; Print inline message
  615.     SUB    A
  616.     ADD    M
  617.     INX    H
  618.     XTHL
  619.     RZ
  620.     CALL    CONOUT
  621.     JMP    SPMSG
  622. ;
  623. CRLF:    MVI    A,0DH    ; Print a CR,LF
  624.     CALL    CONOUT
  625.     MVI    A,0AH
  626.     JMP    CONOUT
  627. ;
  628. INKEY:    MVI    A,0FFH    ; Get key, if waiting
  629. ;
  630. CONOUT: MOV    E,A    ; Show character in A
  631.     MVI    C,6    ; Direct console I/O
  632.     PUSH    B
  633.     PUSH    H
  634.     CALL    BDOS    ; Call BDOS preserving BC,HL
  635.     POP    H
  636.     POP    B
  637.     RET
  638. ;
  639. CONIN:    MVI    C,1    ; Console input
  640.     JMP    BDOS
  641. ;
  642. UCASE:    CPI    'a'    ; Uppercase char in A
  643.     xRC
  644.     CPI    'z'+1
  645.     RNC
  646.     ANI    5FH    ; If it was "a...z"
  647.     RET
  648. ;
  649. A32HL:    ADD    A    ; Add 32*A to HL
  650. ;
  651. A16HL:    ADD    A    ; Add 16*A to HL
  652.     ADD    A    ;   (used to point to FCBs)
  653.     ADD    A
  654.     ADD    A    ; 32 (or 16) *A
  655.     ADD    L    ; +L
  656.     MOV    L,A
  657.     RNC
  658.     INR    H    ; May carry 1 into H
  659.     RET
  660. ;
  661. LDIR:    MOV    A,M    ; Move BC bytes from HL to DE
  662.     STAX    D    ;   (can replace with Z80 LDIR)
  663.     INX    D
  664.     INX    H
  665.     DCX    B
  666.     MOV    A,B
  667.     ORA    C
  668.     JNZ    LDIR
  669.     RET
  670. ;
  671. ;--- uninitialized storage ---
  672. ;
  673. ;SFCB:    DS    36    ; FCB for source
  674. ;
  675. RFCB:    DS    16    ; Dual FCB for renaming
  676. RFCB2:    DS    20
  677. ;
  678. FILES:    DS    1    ; Count files
  679. FLIST    EQU    $    ;   and list them starting here
  680. ;
  681.     END
  682.  
  683.  
  684. 7. Further Details
  685.      Briefly, here's how XREN works: the source filespec is
  686. copied from FCB2 to SFCB, where it is used with Search First/Next
  687. (in the loop at FIND) to build up a list of filenames that
  688. match the source. This is kept at FLIST, and
  689. a count kept in FILES (note that we actually
  690. copy the whole top row, 16 bytes, of the FCB for
  691. convenience).
  692.      Then we come to the second loop, at RENAME, where we
  693. go back through the list (in reverse order, as it happens) to
  694. rename each file. The new filename is constructed
  695. by combining the old one and the ambiguous
  696. destination, as discussed above. If a file by this
  697. name already exists, we ask whether you want to
  698. overwrite it. (If the new name is the same as the
  699. old, we pass on to the next, to avoid destroying
  700. the file.)
  701.      A few points are worth noting. Pay attention to the use of
  702. flags on return from subroutines. Setting the Carry flag to
  703. indicate an error condition (and making sure it's clear
  704. otherwise) is a common convention.
  705.      In the RENAME loop, remember that the directory
  706. entries found contain user numbers, not the drive numbers we want
  707. in the FCBs, so we have to replace these. In
  708. VERIFY, we have to decide whether the source
  709. and destination drives are the same. Remember that
  710. the first byte of an FCB holds 1=A:, 2=B:, and so
  711. forth, but you can also have 0, for "logged
  712. drive".
  713.      So we ask BDOS what the logged drive is (it will
  714. return 0 for A:, 1 for B: etc, so we have to increment this to
  715. agree with the 1=A: FCB convention), and if either FCB has a 0 we
  716. replace it with the actual logged drive. This also
  717. ensures that the drive names will display
  718. correctly.
  719.      We use both types of console input here: BDOS 1 for
  720. the answer to the "Overwrite?" question, when we want to wait for
  721. a key and have it echo; and BDOS 6 to check for ^C from the
  722. console, where we don't want to wait or to echo it.
  723.      All the storage areas are labeled at the end, where they
  724. won't add to the size of the COM file. You
  725. might wonder whether the FLIST may grow so
  726. long that it will overwrite the CCP in high
  727. memory, but at 16 bytes per entry, there's room
  728. for thousands of filenames.
  729.      Actually, XREN is going to give up if it encounters
  730. more than 255 files, because FILES is a one-byte variable,
  731. but you could change it to two bytes easily
  732. enough. (Note the test and branch to TOOMNY:
  733. if you INcRement a byte and it goes past 255 to
  734. zero, the Zero flag is set.)
  735.  
  736.  
  737. 8. Conclusion
  738.      You'll notice that we didn't include an "=" in the
  739. XREN command line, as we would with REN. In fact, if you
  740. try:
  741.  
  742. A>XREN NEWFILE=OLDFILE
  743.  
  744. you'll probably see the message "<No File>", regardless.
  745.      This is because most CP/M 2.2 systems don't parse an
  746. argument into the second FCB unless it's separated from the first
  747. by a space. (Astute readers will recall that earlier [Part VI] I
  748. said that they do . . . my mistake. Interestingly enough,
  749. CP/M 3.0 does parse the second argument when an "=" is
  750. used; this is just one of many subtle differences.)
  751.      We've been handling arguments the lazy way up to now, by
  752. letting the CCP parse them for us. But there are
  753. times when you may need to do this yourself (e.g.,
  754. if you are writing a program that needs more than
  755. two filenames as arguments, or that wants
  756. something other than a filename, like a long text
  757. string).
  758.      We may cover the subject of parsing arguments later, but for
  759. now, if you want to try to modify XREN to accept the "=",
  760. here's what you have to know. In addition to the
  761. FCBs, the whole command line as entered is
  762. available for examination when a program begins.
  763.      It's in the DMA at 0080, and it starts with a byte telling
  764. you the length of the string, for example:
  765.  
  766. 0080:    0A 202A2E4F424A3D2A2E434F4D [...]
  767.     (12) * . O B J = * . C O M
  768.  
  769.      You could add code to the beginning of XREN to
  770. scan this string for an "=", and then process the rest into FCB2
  771. as the second filename. You'll want to experiment
  772. with variations on the theme of displaying and
  773. renaming files from the disk directory.
  774.      These are constantly needed tasks, and difficult to
  775. accomplish in higher level languages. It's good to
  776. be able to write utilities to get them done, with
  777. minimal effort, the way you want.
  778.