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 / TCJ / TCJ35BMM.WS < prev    next >
Text File  |  2000-06-30  |  39KB  |  907 lines

  1. TCJ #35
  2. _words in italics_
  3. .h1  main headings
  4. .h2  secondary headings
  5.  
  6.  
  7.                                 Advanced CP/M
  8.                             ZSDOS and File Systems
  9.                              B∙ Bridger Mitchell
  10.                         The Computer Journal, Issue 35
  11.                           Reproduced with permission
  12.                            of author and publisher
  13.  
  14.  
  15. .h1 DAWN OF A NEW DOS
  16.  
  17. Think of it as CP/M 4.0 -- an all-new, feature-packed,
  18. high-performance BDOS replacement for all Z80 computers running CP/M
  19. 2.2, ZRDOS, or other compatible dos's.
  20.  
  21. ZSDOS is its final name -- the cooperative product of Hal Bower, Cam
  22. Cotrill, and Carson Wilson that fuses their initially separate
  23. efforts.  The result is explosive: improved disk function performance,
  24. file datestamping with no reduction in program memory, files
  25. automatically accessible from other directories, and elimination of
  26. some notorious CP/M bugs.  Benefitting from Ten Brugge's P2DOS and
  27. Carson's first forays into Z80DOS, the finely-tuned final product is
  28. fully compatible with BackGrounder ii, NZ-COM and ZCPR34.  And the
  29. authors' thorough, extensive testing means highest quality; we are
  30. unlikely to ever see a ZSDOS 2.2, or 1.9!
  31.  
  32. ZSDOS is, foremost, an up-to-date DOS.  It fully supports the
  33. established DateStamper standard, and comes with preassembled
  34. relocatable clock routines from the Plu*Perfect Systems library to
  35. read virtually all of the popular (and many obscure) clocks.  And it
  36. breaks new ground, adding BDOS functions to get and set file
  37. datestamps as well as to get and set the system realtime clock.  In
  38. addition to Plu*Perfect's PUTDS, SDD, and DATSWEEP utilities, it is
  39. shipped with some nifty new tools that display files sorted by date
  40. and that automatically copy datestamps.  Best of all, perhaps, is that
  41. the "trim" version of ZSDOS includes datestamping within the BDOS with
  42. no loss of TPA memory (except possibly for BIOS space to hold a clock
  43. routine)!
  44.  
  45. The "full-up" ZSDOS version adds internal path-searching to the BDOS,
  46. enabling _programs_ to do what until now only the ZCPR command processor has
  47. been able to achieve when loading a command -- scan a path of
  48. directories to locate a needed file.  To do this, it places the
  49. datestamping code in a separate small, relocatable module somewhere --
  50. in or above the user's BIOS, in NZCOM's user buffer area, or in a
  51. resident system extension.
  52.  
  53. Both versions of ZSDOS provide English-language error messages
  54. complete with the name of the associated file, if one.  Error
  55. reporting is configurable, so that a program can field any errorèitself, if it chooses.  Other significant features include
  56. noticeably faster warmboots and disk resets on hard-disk machines.
  57.  
  58. The development team has made upgrading an existing CP/M or ZRDOS
  59. system a snap -- menu-driven installation and configuration utilities
  60. do all the work.  And the documentation is top-notch.
  61.  
  62. I'm enthusiastic!  ZSDOS boosts CP/M 2.2 computing to a new level of
  63. performance, increases reliability, and makes datestamping available
  64. to every Z80 computer.  If you are a CP/M 2.2 or ZRDOS user, you will
  65. benefit most by upgrading to ZSDOS without delay.  It's
  66. available from Plu*Perfect Systems.
  67.  
  68. .h1 BackGrounder ii Update
  69.  
  70. BackGrounder ii, as many readers of Jay Sage's column know, is a
  71. task-switching operating system system extension of CP/M 2.2, ZSDOS,
  72. and ZRDOS.  Simply put, it allows you to switch back and forth between
  73. virtually any two applications programs, literally in mid-sentence!
  74. One reviewer described it as windows for CP/M, other users refer to it
  75. a super-Sidekick (it provides a calculator, notepad, screendump and
  76. background printing).
  77.  
  78. As TCJ rolls off the press I expect to have BackGrounder ii updated to
  79. full compatibility with ZCPR version 3.4.  This will become the
  80. standard version, and currently licensed users can order
  81. an update from Plu*Perfect Systems.
  82.  
  83.  
  84.  
  85.  
  86. .h1 FILE SYSTEMS
  87.  
  88. The main topic for this issue's Advanced CP/M column is file systems.
  89. Operating systems separate the organization and maintenance of a _file
  90. system_ from the storage and retrieval of data on physical media.
  91. Files are most often stored on magnetic disks, and the portion of the
  92. CP/M operating system responsible for the file system is indeed called
  93. the basic _disk_ operating system (BDOS).
  94.  
  95. In contrast, the lower-level tasks of actually writing data to, and
  96. reading them from, the physical disk is delegated to a _disk driver_,
  97. code that is part of the BIOS -- the basic input/output system that
  98. must know the particulars of the specific hardware of the host
  99. computer.
  100.  
  101. The separation of file system functions and hardware-specific
  102. functions is fundamental to the design of any major operating system,
  103. and it has far-reaching implications.
  104.  
  105. First, it makes it possible to use the same programs on different
  106. computers, with different physical disk drives, provided that they run
  107. the same operating system.
  108. èSecond, by keeping the logical organization of a "disk" and its
  109. physical realization in separate layers of the operating system, we
  110. can use a wide variety of storage media with the same file system.  A
  111. ram "disk", after all, doesn't spin at 300 rpm, and a cassette tape or
  112. local area network is hardly a conventional disk, either.  Yet, to a
  113. program and the BDOS, a file is a file is a file.
  114.  
  115. Third, with some extensions of the operating system, it is possible to
  116. mount a _different_ file system on the same computer.  For example,
  117. some FORTH operating systems run on top of CP/M and provide access to
  118. both FORTH file screens and CP/M files.  In a different way, DosDisk
  119. provides direct, transparent program access to MSDOS files in a CP/M
  120. environment.
  121.  
  122.  
  123. .h1 FORMAT PROLIFERATION
  124.  
  125. The earliest CP/M computers had only a single format, the single-sided
  126. single-density 8" IBM "standard", and no provision for anything else.
  127. Then, as a few higher-performance and higher-capacity formats were
  128. introduced, they were hard-coded into the BIOS.  Each new format
  129. required re-coding and reassembly of a new system.
  130.  
  131. Today, CP/M suffers from a surfeit of physical floppy disk formats.
  132. It seems that every manufacturer felt impelled to put his own label on
  133. yet another non-compatible format, to the point where we have well
  134. over 100 different ways of storing the same file on one 5 1/4" disk!
  135. This has also created something of an identity crisis, because it is
  136. not always possible to unambiguously determine a disk's format by
  137. magnetically reading the data on it.
  138.  
  139. The most modern BIOSes rise above this morass with flexibility and a
  140. degree of intelligence.  They are able to identify a set of "native"
  141. formats, and automatically adapt themselves to the disk in each drive.
  142. In addition, they allow an external utility to set a drive to a
  143. "foreign" format, one that the BIOS cannot identify from its built-in
  144. data, but is known to the utility.
  145.  
  146. One such BIOS is the Advent TurboRom, written by Plu*Perfect Systems
  147. for the family of Kaypro computers.  It automatically identifies 11
  148. formats (Kaypro, Advent, Osborne, Ampro, Xerox, etc.).  A companion
  149. program, MULTICPY (sold separately), allow TurboRom-equipped Kaypros
  150. to format disks in foreign formats, and to make exact copies of entire
  151. disks in those formats.  And the TURBOSET utility allow the user to
  152. specify some 90 foreign formats, making nearly every 5 1/4" MFM-coded
  153. soft-sector format disk directly usable on a Kaypro computer.
  154.  
  155. MULTICPY and TURBOSET use a database (in dBase II format) of physical
  156. and logical disk formats.  Because the database is extensible, new
  157. formats can be added.  At Plu*Perfect Systems we use MULTICPY to
  158. produce distribution disks in many popular formats.  If you have
  159. an unusual one, and can supply the physical and logical disk
  160. parameters and a sample disk, we can probably add it.è
  161. If your BIOS isn't this up-to-date, it is possible to temporarily
  162. replace its disk driver functions with a special application program
  163. long enough to copy files to or from a foreign format disk.  Such a
  164. program must be written for your specific computer's hardware.  Two
  165. popular utilities of this sort are UniForm (MicroSolutions) and Media
  166. Master (Intersecting Concepts).
  167.  
  168. You will find a cross-format tool is essential if you need to exchange
  169. data on a format not supported by your computer.  The TURBOSET
  170. approach is the most flexible.  It lets you use the foreign format
  171. disk with any regular CP/M program, just like your native-format
  172. disks.  With the other tools you load the format-conversion utility,
  173. copy the needed file(s) to or from your native-format disk, remove the
  174. utility, and then run your regular programs.
  175.  
  176. There are a host of challenges that confront the programmer who seeks
  177. to upgrade his or her BIOS to this modern level of performance, and
  178. perhaps we can explore them in another column.  In the remainder of
  179. this issue, however, we will have our hands full covering the file
  180. system and its implementation in the CP/M BDOS.
  181.  
  182.  
  183. .h1 FILE STRUCTURE
  184.  
  185. Every file structure has two key properties -- a method of naming
  186. files, and a method of allocating space for storing data.
  187.  
  188. Each file has a unique name within the filename space on the disk.
  189. (In CP/M, the filename space is a user number; in MSDOS and UNIX it is
  190. a subdirectory).  With the name are usually a set of file attributes
  191. that may control permissions on access to the file, and perhaps
  192. datestamps as well.
  193.  
  194. Storage of data for the file is allocated in blocks -- chunks of 128
  195. or more bytes of data.  With each filename the file system associates
  196. an ordered list of blocks, and a total length of the file.
  197.  
  198. The file system must maintain this information in an orderly fashion
  199. for each file on the disk.  To do so, it uses a _directory_ of
  200. filenames, a _free list_ of unused data blocks, and an _allocated
  201. list_ of blocks in use by the files.
  202.  
  203. The directory contains (at least) one entry for each filename.  The
  204. entry will usually include permissions or attributes that control
  205. access to the file itself, and perhaps the datestamps for the file.
  206. And it will include some type of link to the file's data blocks.
  207.  
  208. The free list is some type of data structure that indicates which data
  209. blocks on the disk are not in use and can be allocated for writing
  210. data.  On a fresh disk it will include all blocks of the disk not
  211. reserved for the directory, the boot code, or other operating system
  212. purposes.  As a file is written, blocks are transferred from the freeèlist to the allocated list and assigned to the file.
  213.  
  214. .h2 The Allocated List
  215.  
  216. I haven't said anything yet about how the directory and allocated
  217. list are actually stored.  Those are key choices made by the
  218. designer of the operating system, and it's instructive to
  219. see how they can differ.
  220.  
  221. In MSDOS, the list of blocks is encoded in a file allocation table
  222. (FAT).  The FAT has an entry for each data block (called a cluster in
  223. MSDOS) on the disk.  An entry indicates that the block is unallocated
  224. (and is thus part of the free list), is allocated to a file, or
  225. is otherwise reserved.
  226.  
  227. The FAT is encoded in a way that allows it to serve two functions --
  228. it records the allocated and free blocks, and it shows which blocks are
  229. associated with which files.  Blocks that are allocated to one file
  230. form a _linked list_.  Each entry in the FAT is a pointer to the next
  231. block in that file's list, and the last entry is a special end-of-list
  232. mark.
  233.  
  234. The MSDOS directory entry includes only a pointer to the _first_ block
  235. of the file.  The rest of the blocks are obtained by following the
  236. linked list in the FAT.  The FAT itself is stored on the disk, and the
  237. MSDOS system keeps a copy of it in working system memory.  Thus,
  238. there are two separate data structures on an MSDOS disk -- the FAT
  239. (which is actually stored in duplicate) and the directory.
  240.  
  241. CP/M takes a different approach -- it includes the storage information
  242. as well as the filename information in the directory entry.  Each
  243. directory entry contains a set of data block numbers and there is no file
  244. allocation table.  To obtain the data blocks for a CP/M file, the
  245. system finds the first directory entry and reads off the block
  246. numbers.
  247.  
  248. Where is CP/M's free list?  It is implicit in the directory.  When a
  249. disk is logged in, the CP/M BDOS reads through the directory of a disk
  250. and keeps track of each data block that is allocated to a file.  It
  251. encodes this information in an _allocation bitmap_ for the disk,
  252. setting one bit for each block that is in use.  The bits that are not
  253. set then represent the free blocks.
  254.  
  255. The UNIX system uses aspects of each approach.  Each UNIX directory
  256. entry includes the filename and an _i-node_ number; this is much like
  257. MS-DOS.  An i-node is a list of the first 10 (512-byte) data blocks of
  258. a file, plus links to indirect lists of additional blocks.  Directly
  259. including the list of the first 10 blocks in the i-node (a bit like
  260. including the block numbers in the first CP/M directory entry) allows
  261. UNIX to rapidly retrieve smaller files and yet use linked lists to
  262. extend files to very large sizes.
  263.  
  264. .h2 How Much Space Left?
  265. èOne perennial disaster with many early CP/M programs, famous and
  266. obscure alike, was writing a file to an almost-full disk, running out
  267. of space during the operation, and having the program quit with the
  268. precious data lost forever.  Of course, a well-written program
  269. wouldn't quit when a BDOS error occurs; it would clean up its
  270. incomplete file, allow the user to change disks, reset the disk
  271. system, and re-write the file.
  272.  
  273. But a really well-crafted program wouldn't even attempt to write to
  274. the almost-full disk.  Instead, before writing, it would determine
  275. whether there is enough space left on the disk to hold the file.
  276.  
  277. To do this, the program must obtain the total number of free blocks.
  278. This is a natural function for the disk operating system to perform,
  279. and in CP/M Plus there is a system call for this purpose (46).  But it
  280. wouldn't fit into the space on the system tracks of the original 8"
  281. CP/M 2.2 systems, and so the BDOS includes another system call (27) to
  282. return the address of the drive's bitmap, and programs must count up
  283. the free blocks themselves.
  284.  
  285. Figure 1 show the Z80 routine, get_freek, that returns the number of
  286. unallocated kilobytes of space on the currently logged drive.  It is
  287. portable -- it works under CP/M 2.2, CP/M Plus and even for an MS-DOS
  288. disk when running DosDisk.  The code includes contributions from Jay
  289. Sage, Joe Wright, and others, and is used, in a slightly varied form,
  290. in the SP (space) command in Z3PLUS and NZ-COM.
  291.  
  292. The routine first determines which version of CP/M is running.
  293. If the system is CP/M Plus, the BDOS will do all of the work.  In
  294. fact, it's necessary to let it do the work, because in most CP/M Plus
  295. systems the allocation bitmap will be stored in a different memory bank
  296. and therefore not readily accessible to the program.  (If the routine
  297. did attempt to use the bitmap address, it would add up bits of
  298. whatever program or data happen to be in that part of the main memory,
  299. resulting in an incorrect value).
  300.  
  301. CP/M Plus function 46 returns the space remaining on the disk as a
  302. 24-bit number in the first three bytes of the dma, in units of
  303. 128-byte records.  So, to use this function, get_freek first sets the
  304. dma address to the temporary buffer at 80h and calls function 46.  The
  305. divide-by-8 code then converts this to kilobyte units.
  306.  
  307. If the routine is running under CP/M 2.2, it first calls function 31
  308. to get several disk parameters for the logged-in drive -- the
  309. block-shift factor, the extent mask, and the maximum number of blocks
  310. on the drive.  Next, it calls function 27 to get the address of the
  311. bitmap (allocation vector).  The code at label "cntfree" then counts the
  312. number of unset bits in the bitmap, accumulating the count in register
  313. DE.
  314.  
  315. Since each block represents some multiple of 1K (1024 = 2**10 bytes),
  316. the code at label "free2k" multiplies the free block count by the sizeèof one data block.  The block shift factor is the base-2 logarithm of
  317. the number of 128-byte records per data block.  In other words, it is
  318. the exponent in this equation:
  319.  
  320.     block size in records = 2 ** block-shift-factor
  321.  
  322. If the block size is 1K (8 records), the block shift factor is 3
  323. (i.e., 8 = 2**3), and the number of free blocks is already in 1K
  324. units.  Otherwise, we multiply by the number of K in one block; this
  325. calculation is simply a 16-bit left shift that results from doubling
  326. HL (blkshf-3) times.
  327.  
  328.  
  329. .h1 A CLOSER LOOK AT THE CP/M FILE STRUCTURE
  330.  
  331. One CP/M directory entry contains the following components:
  332.  
  333.     user number - a logical partition of the volume (disk)
  334.     file name
  335.     file attributes
  336.     directory entry number
  337.     size of (the portion of) file indexed by this entry
  338.     the data block numbers for this entry
  339.  
  340. A single directory entry can hold either 16 8-bit data block numbers,
  341. or 8 16-bit directory numbers.  A CP/M data block can be 1K, 2K, 4K,
  342. or 16K bytes (the blocking factor is part of the disk format
  343. specification), and the large blocks require 16-bit numbers.  So a
  344. single directory entry may refer to a maximum of from 16*1K to 8*16K =
  345. 128K bytes of data, depending on the blocking factor for the disk.
  346.  
  347. Clearly, a file might be larger than the number of bytes that can be
  348. recorded in a single directory entry.  To handle this case, CP/M
  349. creates _additional_ directory entries to hold additional data block
  350. numbers.  These entries have the same filename, user number and
  351. attributes as the initial entry, but they have unique directory entry
  352. numbers.  (Contrast this with MS-DOS, which has just one directory
  353. entry, but a longer linked list of FAT clusters for a large file.)
  354.  
  355. .h2 Reading a file.
  356.  
  357. The actual numbering of CP/M directory entries is somewhat torturous, and
  358. so we will discuss it later.  First, let's get a grip on the details.
  359. Assume we already have a large file and consider first what
  360. the operating system does when an application program is reading the file.
  361.  
  362. First, the program calls the BDOS to open the file named in the
  363. indicated file control block (fcb).  The CP/M BDOS searches for the
  364. initial directory entry, finds it, and stores the entry data,
  365. including the data block numbers, in the user's fcb.
  366.  
  367. Next, the program repeatedly calls the BDOS to read the file
  368. sequentially from the beginning.  The (CP/M 2.2) BDOS gets the first
  369. data block number from the fcb, converts that value to track andèsector numbers, and calls the BIOS to read one 128-byte record.  Next,
  370. it increments the sector number (adjusting for reaching the end of a
  371. track) and calls the BIOS again, repeating for the number of records
  372. in a data block (8 in a 1K block, etc.).  It then gets the second data
  373. block number from the fcb, converts to track/sector, and reads another
  374. set of records.
  375.  
  376. Eventually (after processing 8 or 16 blocks) all of the first
  377. directory entry's data blocks have been used, and the BDOS must search
  378. for and read in the next directory entry.  (At this point, on a
  379. physical disk the movement of the disk heads back to the directory
  380. track can often be heard; this extra motion significantly slows down
  381. access to large CP/M files.)  The BDOS then repeats the process of
  382. computing track/sector numbers and calling the BIOS to read records.
  383.  
  384.  
  385. .h2 Writing a file.
  386.  
  387. Writing a file involves reversing these steps, with a few key
  388. additions, because disk space must be allocated.  Let's assume
  389. our program is writing a new file.
  390.  
  391. First, the program calls the BDOS to create the file
  392. with the name stored in the fcb.  The BDOS searches the directory
  393. for an empty (unused) directory entry.  It then writes the
  394. new filename into that entry, with zeros for block numbers.
  395.  
  396. Now consider what the BDOS must do as the program sequentially writes
  397. the file. First, the BDOS must find a free data block on the disk.  To
  398. do this it consults its free list for the disk (the allocation bit
  399. map) and assigns one block to the new file.  It marks that block as
  400. used and puts the block number into the file control block.  Now that
  401. the block number known, the next steps are much like reading -- the
  402. BDOS translates from block number to track/sector numbers and calls
  403. the BIOS to write 128-byte records, until a block is full.  Then, when
  404. a new block is needed, the BDOS gets the next free block from the free
  405. list, and repeats the process.
  406.  
  407. Eventually, the file control block is filled up with 8 or 16 data
  408. block numbers, and the BDOS must make a second directory entry.  But
  409. before doing so, it "closes" the initial entry by writing the file
  410. control block values to that directory entry on the disk.  Then, it
  411. searches for another empty entry, creates the second directory entry
  412. for the file (with the same name, but a different entry number), and
  413. finally resumes the process of allocating a data block and writing
  414. records.
  415.  
  416. At last, when the entire file has been written, the program calls the
  417. BDOS to close the file.  Just as it did for the "internal" close of
  418. the initial directory entry, the BDOS writes the data block numbers in
  419. the file control block to the final directory entry on disk.
  420.  
  421. If an error occurs during the process of writing the file, you may seeèsome residue of the incomplete process.  Quickie Quiz: Explain how
  422. each of the following might result:
  423.  
  424. 1. Filename in directory, file is shown as 0K.
  425. 2. Filename in directory, file is shown as 16K (or 32K or ...),
  426.    but the end of the file is missing.
  427.  
  428.  
  429.  
  430.  
  431.  
  432. .h1 INTERNALS OF THE DIRECTORY ENTRY.
  433.  
  434. Now we turn to the nitty-grity, and it is unavoidably confusing!  It's
  435. also essential if you intend to really understand CP/M files.
  436.  
  437. The CP/M directory structure is like a tree house that grew as the
  438. kids got bigger.  First it was a simple platform (for CP/M 1.4 files).
  439. Rooms got rebuilt to handle larger files and larger disks, and the
  440. file control block got extended to provide random access (CP/M 2.2).
  441. And small passageways were crammed with filesize, datestamps, and
  442. passwords (CP/M 3).
  443.  
  444. Some of the confusion is simply terminological.  One directory entry
  445. is 32 bytes of data. Sometimes it is also called a physical directory
  446. extent -- "physical" because it refers to actual bytes on the disk.
  447. Whenever you see this topic discussed, read carefully -- I suggest you
  448. translate all references from "physical extents" to "directory entries",
  449. and reserve the term "extents" exclusively for "logical extents,"
  450. which we will examine soon.
  451.  
  452. The directory entry has several fields, shown in Figure 2.  The
  453. information is densly packed.  You can look at an actual sector, which
  454. contains 4 directory entries, with the DU (or DU3) utility, or by
  455. running the following bit of code under a debugger and then displaying
  456. the default buffer at 0080h.
  457.  
  458.     ld    c,11
  459.     ld    de,5C
  460.     ld    a,3F
  461.     ld    (de),a
  462.     call    5
  463.     rst    38
  464.  
  465. Byte 0 of a directory entry (labeled "u") is the file's user number.
  466. A value of E5 hex indicates that the entry is unused.  Otherwise, it
  467. can have a value of 0 to 31 in CP/M 2.2.  In CP/M Plus user numbers
  468. are restricted to 0 to 15, and higher numbers indicate special
  469. datestamp, password, and volume label entries.
  470.  
  471. Bytes 1-8 are the filename and bytes 9-11 the filetype.  They must be
  472. uppercase, 7-bit letters, numbers, or a few other symbols. Each of the
  473. 11 high (eighth) bits of the filename and filetype are fileèattributes.  Attributes 5-11 are reserved for the system to designate
  474. files are read-only, archived, and so forth.
  475.  
  476. The next four bytes encode the entry number and the length of the
  477. file.  They will get our full attention in a moment.
  478.  
  479. Bytes 16-31 (10h-1Fh) are where the data block numbers are stored.
  480. These are either 16 1-byte values, or 8 2-byte values, depending on
  481. the disk format.  If there are no more than 255 (FF hex) block numbers
  482. on a disk (for example, on a single-sided single density disk), it's
  483. possible to use 1-byte values.  Otherwise, 2-byte values are needed.
  484.  
  485.  
  486. .h2 The directory entry number.
  487.  
  488. Now, had the tree house been built in one day, the directory number
  489. would be a 16-bit word. Instead, we have to climb through some tangled
  490. vines.  So, hold on!
  491.  
  492. The CP/M file system has two fundamental units of measurement:
  493.  
  494.     1 record = 128 bytes
  495.     1 logical extent = 128 records = 16K bytes
  496.  
  497. Records and logical extents are numbered sequentially, beginning with
  498. 0.
  499.  
  500. Now consider a 17K file, with copies on several types of disks.
  501. Things might look like this.
  502.  
  503. On Disk #1, 16K of data blocks fill up one directory entry.  Then one
  504. entry corresponds to one logical extent.  The 17K file will have
  505. 2 logical extents, and 2 directory entries.
  506.  
  507. On Disk #2, 32K of data blocks fill up one directory entry.  (How
  508. might this occur?  Suppose a block is 4K, and block numbers are 2-byte
  509. values. 8*4K = 32K.)  Now, one entry can hold two logical extents.
  510. The 17K file will have 2 logical extents, but only one directory
  511. entry.
  512.  
  513. CP/M keeps track of logical extents with the EXtent byte, which
  514. can hold 0 to 31 (0 to 1F hex). After 31, it must again be 0.
  515.  
  516. Why, you may well ask, does CP/M not allow more than 32 extent values
  517. in this field?  Well, the tree house architect wasn't that farsighted.
  518. In the directory search functions, the BDOS uses a '?' character to
  519. indicate a "wild-card" search.  When a '?' appears in the EXtent byte
  520. of an fcb, the BDOS will match any extent number.  And since the '?'
  521. byte is 3F hex = 00111111 binary, only 5 bits are available to number
  522. logical extents!
  523.  
  524. If five bits were indeed all that is available, CP/M files would be
  525. restricted to a maximum size of 32*block size.  To allow larger files,èthe tree house added the S2 byte.  It holds the "overflow" from the
  526. EXtent byte.  Each unit of S2 thus represents 32 logical extents, and
  527. the the S2 byte can take a value from 0 to 3F hex.
  528.  
  529. The full logical extent number is, therefore obtained by combining
  530. the EXtent byte and the S2 byte as follows:
  531.  
  532.     log_ext = (EXT & 1Fh) + ((S2 & 3Fh) << 5)
  533.  
  534. (I use the c language operators: '&' is bitwise and, '<<' is shift-left).
  535.  
  536. Note well that the high-order bits must really be masked; while the
  537. directory entry is active in the fcb, the BDOS uses the higher bits of
  538. the EXTent and S2 bytes for internal BDOS flags.
  539.  
  540. Now, what is the directory entry number (the "physical extent")?  It is
  541. the logical extent number, divided by the number of logical extents
  542. per directory entry.  And that depends on the format, information that
  543. is _not_ in the directory, but in the BIOS's data structure for the
  544. drive -- the disk parameter block (dpb).
  545.  
  546.     entry_no = log_ext / extents_per_entry
  547.  
  548. The _extent mask_ byte in the dpb encodes the number of logical
  549. extents per directory entry.  Its value is
  550.  
  551.     extent_mask = 2 ** extents_per_entry - 1
  552.  
  553. A strange, but handy, representation, because it gives the number of
  554. times to right-shift the log_ext value to calculate the directory
  555. entry number.  And, simultaneously, it is a bitmask that, applied to
  556. the EXTent byte, yields the number of logical extents within
  557. the current directory entry that are in use.
  558.  
  559.     entry_no = log_ext >> extent_mask
  560.  
  561.              = ((EXT & 1fh) >> extmask) + ((s2 & 1fh) << (5 - extmask))
  562.  
  563.  
  564. .h1 FAST FILESIZE COMPUTATIONS.
  565.  
  566. How big is a file?  What is its size in records, or equivalently, what
  567. is the record number of the file?  It is the record count in the last
  568. directory entry (the number of records in the final logical extent),
  569. plus the size, in records, of all prior extents.  Since the RC byte
  570. may be 80 hex, we must mask it.  The formula is:
  571.  
  572.     recno = log_ext << 7 + (RC & 7Fh)
  573.  
  574. Before considering practical answers to that question, let's consider
  575. how large a record number can ever be.  The record count is 7 bits, the
  576. EXtent byte is 5 bits, and the S2 byte can be 6 bits, a total of 18
  577. bits.  The largest possible record number is therefore 2**18.  Sinceèthere are 8 = 2*3 records in 1 kilobyte, the maximum filesize is 2**15
  578. K = 32 MB, a large file indeed!
  579.  
  580. This is the limit under CP/M Plus and ZSDOS.  Regular CP/M 2.2, however, 
  581. limited the record number to a 16-bit quantity (with the largest S2 value
  582. being 0F hex), and thus a maximum filesize of 4 MB.  And I'm afraid
  583. most CP/M application programs expect that limit not to be exceeded.
  584.  
  585. We can determine a file's size in several ways.  BDOS function 35 will
  586. return the filesize in the random record number field of the fcb.
  587. This is the easiest method; the BDOS does all of the tedious
  588. arithmetic, and the random record number field is 3 bytes, so it will
  589. hold a full 18-bit record number, should we ever have a file so huge.
  590. But it's slow, because the BDOS must search the directory from the
  591. beginning each time it is called.
  592.  
  593. A second method is to have the program read the complete directory,
  594. storing the directory entries for the file as it goes, and then
  595. find the last one.  This is no faster for a single file, but it is
  596. a clear winner if the program is reading the complete directory anyway
  597. (in order to display it, for example).  In this case, the file size
  598. calculation is made after the entries are stored and sorted by entry
  599. number (as well as alphabetically, perhaps).
  600.  
  601. .h2 A single file's size
  602.  
  603. Often enough, a program needs a file's size as an adjunct to other
  604. file operations.  In this situation, the file can first be opened, or
  605. searched-for, and then its size quickly computed from the directory
  606. entry data.  Figure 3 shows the routine, get_filesize, to perform this
  607. service.
  608.  
  609. If the file has only one directory entry, all of the information
  610. needed to calculate its size in records is available in the EXtent,
  611. S2, and RecordCount bytes returned in the fcb by an open call, or in
  612. the dma buffer by a search-first call.  The routine first checks that
  613. that the fcb information is, indeed, for entry number 0.  It then
  614. determines that there are no others by checking the record count,
  615. because if it is 80h (128), the entry is full, and there may be
  616. another one.
  617.  
  618. If all of these tests get passed, it calculates:
  619.  
  620.     records = RecordCount + 128 * number of prior logical extents
  621.  
  622. Otherwise, it calls the BDOS, which returns the number of records in
  623. the random-record number field of the fcb.
  624.  
  625. The get_filesize routine returns the filesize as a 3-byte value in the
  626. A, H, and L registers.  Except for very large files, A will be zero,
  627. and the filesize can be used as the 16-bit value in the HL register pair.
  628.  
  629. è.h2 A list of file sizes
  630.  
  631. What if you need to get the sizes of several files?  If your routine
  632. has a lot of memory available to hold a large list of directory
  633. entries you can process them in a single batch.  But in some
  634. applications memory must be conserved.  The routine might be just a
  635. small part of a large program that need memory for other functions.
  636. Or perhaps it is a component of a Z-System resident command processor
  637. that wants to keep the TPA intact for the next GO command.
  638.  
  639. The most basic directory routine looks like this:
  640.  
  641.     set fcb to a wildcard mask
  642.     set dma to a buffer
  643.     search-first
  644.       if not found, quit
  645. loop:    if entry number is 0, display entry at offset in buffer
  646.     search-next
  647.       if found, loop
  648.  
  649. How can we add the fast filesize calculation to this routine?  Here's
  650. the sketch of the approach I used in the DIRectory command built into
  651. BackGrounder ii , and also later in JetFind.  That command must be
  652. able to run when a regular program has been suspended, without
  653. molesting that program's memory.  This is the special challenge.
  654.  
  655. We plan to modify the "loop" line to be:
  656.  
  657.     if directory-entry is not full, calculate filesize from entry.
  658.     else use BDOS function 35.
  659.  
  660. Hmmm.  Initially, this looks like it would be ok.  In fact, we're in
  661. trouble as soon as it's necessary to use the BDOS filesize function,
  662. because that call will change the BDOS's internal directory pointers
  663. and mess up the next search-next call.  This requires some discussion.
  664.  
  665. The BDOS search-first/search-next functions are unlike any other file
  666. functions, in that they are logically a single function that is called
  667. repeatedly at two entry points.  This operation says, in effect: Find
  668. the first entry in the directory matching the supplied fcb and return
  669. it in the dma buffer.  Thereafter, when entered at the search-next
  670. point, continue the search for the next matching entry.
  671.  
  672. The BDOS uses internal pointers to keep track of both the fcb and
  673. where it is in the directory search, and it presumes that there will be
  674. no intervening file operations except more search-next calls.
  675.  
  676. But, with some cleverness, we can get modify our routine further to
  677. get around this complication.  After making the BDOS 35 call, we do a
  678. search-first call for entry 0 of that file.  This resets the internal
  679. pointers to the spot where the previous search had last matched.
  680. Then, we search-next for the next entry.
  681. èThe routine now looks like this:
  682.  
  683.     set fcb to a wildcard mask
  684.     set dma to a buffer
  685.     search-first
  686.       if not found, quit
  687. loop:    If directory-entry is not full, calculate filesize from entry.
  688.     Else
  689.         call BDOS function 35
  690.         set fcb to last-found entry
  691.         search-first
  692.     search-next
  693.       if found, loop
  694.  
  695. .h1 What's next?
  696.  
  697. File systems are a big topic, we're out of space, and coding the
  698. little directory routine must be left as "an exercise for the reader."
  699.  
  700. I appreciate your comments and welcome suggestions for future columns.
  701. Topics I have in mind include stack and interrupt management and
  702. environmentally-aware programming.  What else would you like to see?
  703. Drop me a line at Plu*Perfect!
  704.  
  705.  
  706.  
  707.            Figure 1.  Free Space on a Disk
  708.                _______________________________
  709.  
  710.  
  711. bdos    equ    5
  712. tbuff    equ    0080h
  713.  
  714.  
  715. ; Enter:    a  = drive (0=A:, ..., 15=P:)
  716. ; Exit:        hl = free space on drive, in Kilobytes
  717. ;
  718. get_freek:
  719.     ld    (spacedrv),a    ; save drive
  720.     ld    e,a
  721.     ld    c,14        ; BDOS select disk function
  722.     call    bdos
  723. ;
  724. ; check for CP/M Plus
  725. ;
  726.     ld    c,12        ; get bdos version
  727.     call    bdos        ; if not cp/m 3 system
  728.     cp    30h
  729.     jr    c,dparams    ; ..jump to calculate from alv
  730. ;
  731. ; calculate free space for CP/M Plus
  732. ;
  733.     ld    de,tbuff    ; set default dmaè    ld    c,26
  734.     call    bdos
  735.     ld    c,46        ; get disk freespace
  736.     ld    a,(spacedrv)
  737.     ld    e,a        ; ..on this drive
  738.     call    bdos
  739. ;
  740. ; Disk space is returned by CPM+ at dma for 3 bytes.
  741. ;
  742.     ld    hl,(tbuff)    ; Low to L, Mid to H
  743.     ld    a,(tbuff+2)    ; High to A
  744.     ld    b,3        ; Divide by 8 (SHR 3)
  745. ;
  746. ; Shift everything right into HL (64 MB max reportable)
  747. ;
  748. div:    or    a        ; Clear carry
  749.     rra            ; High
  750.     rr    h        ; Mid
  751.     rr    l        ; Low
  752.     djnz    div
  753.     ret            ; hl = space free in Kbytes
  754.  
  755. ;
  756. ; For CP/M 2 use this method:
  757. ;
  758. dparams:
  759.     ld    c,31        ; BDOS get disk parameters function
  760.     call    bdos
  761.     inc    hl        ; point to block shift-factor byte
  762.     inc    hl
  763.     ld    a,(hl)        ; Get value and
  764.     ld    (blkshf),a    ; ..save it
  765.     inc    hl        ; point to max data block number
  766.     inc    hl
  767.     ld    a,(hl)
  768.     ld    (extmsk),a    ; save it 
  769.     inc    hl
  770.     ld    e,(hl)        ; Get (word) value into DE
  771.     inc    hl
  772.     ld    d,(hl)
  773.     inc    de        ; Add 1 for max number of blocks
  774.  
  775. ; Compute amount of free space left on disk
  776.  
  777. dfree:    ld    c,27        ; BDOS get allocation vector function
  778.     push    de        ; Save BLKMAX value
  779.     call    bdos        ; Get allocation vector into 
  780.     ld    b,h        ; ..BC
  781.     ld    c,l
  782.     pop    hl        ; Restore BLKMAX value to HL
  783.     ld    de,0        ; Inititialize count of free blocks
  784. è; At this point we have
  785. ;    BC = allocation vector address
  786. ;    DE = free block count
  787. ;    HL = number of data blocks on disk
  788.  
  789. cntfree:
  790.     push    bc        ; Save allocation map ptr
  791.     ld    a,(bc)        ; Get bit pattern of allocation byte
  792.     ld    b,8        ; Set to process 8 blocks
  793. ;
  794. cnt2:    rla            ; Rotate allocated block bit into carry flag
  795.     jr    c,cnt3        ; If set (bit=1), block is allocated
  796.     inc    de        ; If not set, block is not allocated, so
  797.                 ; ..increment free block count
  798. ;
  799. cnt3:    ld    c,a        ; Save remaining allocation bits in C
  800.     dec    hl        ; Count down number of blocks on disk
  801.     ld    a,l        ; if down to zero
  802.     or    h
  803.     jr    z,cnt4        ; ..branch
  804.     ld    a,c        ; Get back current allocation bit pattern
  805.     djnz    cnt2        ; Loop through 8 bits
  806.     pop    bc        ; Get ptr to allocation vector
  807.     inc    bc        ; Point to next allocation byte
  808.     jr    cntfree        ; Process next allocation byte
  809.  
  810. cnt4:    pop    bc        ; clear stack
  811.     ex    de,hl        ; Free block count to HL
  812. ;
  813.     ld    a,(blkshf)    ; Get block shift factor
  814.     sub    3        ; Convert to log2 of K per block
  815.     ret    z        ; Done if 1K per block
  816.  
  817. ; Convert for data blocks of more than 1K each
  818.  
  819. free2k:    add    hl,hl
  820.     dec    a
  821.     jr    nz,free2k
  822.     ret            ; HL = amount of free space on disk in K
  823. ;
  824. spacedrv:ds    1
  825. blkshf:    ds    1
  826. extmsk:    ds    1
  827.  
  828.  
  829.  
  830.  
  831.           Figure 2 . A CP/M Directory Entry
  832.                   _________________________________
  833.  
  834.  
  835.             + user number           +----EXtent byteè           /                       / +---S1 byte
  836.           /                       / / +--S2 byte
  837.          /     filename   type   / / / + record count
  838.         / --------------- ----- / / / /
  839. 00     u f i l e n a m  e t y p x 1 2 r
  840. 10     - - - - - - - -  - - - - - - - -  data blocks
  841.  
  842.  
  843.  
  844.  
  845.  
  846.         Figure 3.  Calculate a Single Filesize
  847.             ______________________________________
  848.  
  849. ;
  850. ; Enter:    de -> fcb (36 bytes), freshly opened or
  851. ;              copied from search-first buffer
  852. ;        extmsk contains extent mask for file's drive
  853. ;
  854. ; Exit:        a,hl = 24-bit file size value in 128-byte records
  855. ;
  856. get_filesize:
  857.     ld    hl,12        ; point to EXtent byte
  858.     add    hl,de
  859.     ld    a,(extmsk)    ; if not directory entry #0
  860.     cpl
  861.     and    (hl)
  862.     jr    nz,g_rd        ; ..call bdos
  863.     ld    b,(hl)        ; save logical extent #
  864.     inc    hl        ; point to S2
  865.     inc    hl
  866.     ld    a,(hl)        ; or if overflow into S2
  867.     and    7fh        ; (not directory entry #0)
  868.     jr    nz,g_rd        ; ..call bdos
  869.     inc    hl        ; or if Record Count
  870.     ld    a,(hl)
  871.     cp    80h        ; ..is full
  872.     jr    z,g_rd        ; ..call bdos
  873. ;
  874. ; calculate filesize from fcb data
  875. ;
  876.     ld    l,a        ; hl = rec. cnt. of last log. extent 
  877.     inc    b
  878.     ld    de,80h        ; + 80h = size of each prior log. extent
  879.     ld    h,d        ; h = 0
  880.     jr    g_dj
  881. g_lp:    add    hl,de
  882. g_dj:    djnz    g_lp
  883.     xor    a        ; clear high bits
  884.     ret
  885. ;
  886. ; call bdos to calculate filesizeè;
  887. g_rd:    push    de        ; save fcb ptr
  888.     ld    c,35        ; call bdos for filesize 
  889.     call    bdos
  890.     pop    de
  891.     ld    hl,33        ; point to random record #
  892.     add    hl,de
  893.     ld    e,(hl)        ; get it
  894.     inc    hl
  895.     ld    d,(hl)
  896.     inc    hl
  897.     ld    a,(hl)        ; high bits to A
  898.     ex    de,hl        ; low 16 bits in HL
  899.     ret
  900. ;-------------
  901.  
  902. [This article was originally published in issue 35 of The Computer Journal,
  903. P.O. Box 12, South Plainfield, NJ 07080-0012 and is reproduced with the
  904. permission of the author and the publisher. Further reproduction for non-
  905. commercial purposes is authorized. This copyright notice must be retained.
  906. (c) Copyright 1988, 1991 Socrates Press and respective authors]
  907.