TweetFollow Us on Twitter

Volume Manager
Volume Number:2
Issue Number:4
Column Tag:Advanced Mac'ing

Nested Volume Manager DA
in Megamax C

By Mike Schuster, Adobe Systems Engineer, MacTutor Contributing Editor

A Nested Volume Manager

Most third party Macintosh hard disk suppliers provide mount manager applications that allow you to partition their disks into several logical volumes, all sharing the same storage medium. These schemes were implemented primarily as work arounds for several limitations in the original Macintosh File System (MFS). For large volumes, MFS suffers from slow file directory searches and poor volume space management. Apple's solution to these problems is the new Hierarchical File System (HFS), introduced in the Fall 1985 on its own 20 megabyte hard disk HD20 product, and again in January 1986 on its 800k 3.5" disk drive in the Macintosh Plus.

Since HFS provides an better mechanism for handling large storage devices, Apple saw no need to implement a volume partitioning scheme on its HD20. The HD20 is shipped pre-formatted as a single large volume. Other suppliers have done the same, such as SuperMac Technology for their DataFrame 20 megabyte external SCSI hard disk.

In this month's article, I show you how you can partition your HD20 or other third party HFS hard disk into independent, variable sized nested volumes. Each of these volumes is built by creating a host file whose contents look like a properly formatted MFS or HFS volume. Once the host file is created and properly initialized, its nested volume is available on the Finder's desktop and in application's open and save MiniFinder dialogs just like any other ordinary volume.

Why Use Nested Volumes?

I've found the "nested volume in a host file" capability to be useful in a variety of ways. You can port MFS-only systems, such as Apple's MDS and third party development systems, onto an HD20 or other HFS disk simply by creating one or more MFS nested volumes. (Of course, as development system suppliers release HFS upgrades, this capability will be less useful. As of March 1986, the only shipping HFS C language systems I'm aware of are Consulair Mac C and Megamax C). Another potential use is as a simple volume level security system. You might want to associated a password with each nested volume, in a manner similar to that done by General Computer on its Hyperdrive product. A final use is purely academic. The manager provides a good excuse for an inside look at the organization of the Macintosh File and Disk Driver operating system components. An understanding of these components is especially useful now with the new opportunities available in the Macintosh Plus - SCSI based peripheral market.

File System and Disk Driver Organization

The Macintosh File System is the part of the Operating System that manages the communication between an application and files on a block oriented storage medium, such as a hard disk drive. Each storage medium is formatted into one or more volumes, each of which contains some descriptive information along with a set of files.

Fig. 1: File System and Disk Driver Organization

Before an application can access the files on a volume, the volume must be mounted. The File System mounts a volume by reading the volume's descriptive information into memory and using it to build a new volume control block, which is inserted into the volume control block queue. The File System performs this mounting process automatically both at boot time and whenever a 3.5" disk is inserted into a drive. Usually, non-ejectable volumes, such as those on hard disks, are always mounted. However, an application may direct the File System to mount or unmount any volume at any time. The third party mount managers mentioned above perform just these functions.

In order to mount a volume, as well as to access its files, the File System must first determine which storage medium contains the volume. (Typically, the medium is a disk drive, but it may be a section of memory allocated to a RAM disk.) Furthermore, once the medium is located, the File System must be able to read and write any accessible block on that medium. The File System does not do these things itself, instead it relies on the drive queue and on a disk driver. The drive queue contains one drive queue element for each available storage medium. Each drive queue element contains, among other things, a drive number uniquely identifying the medium and a disk driver number uniquely identifying the disk driver program that controls that medium. The drive number of the drive containing a volume is also placed in the volume's control block. Hence, given a reference to a volume, the File System can easily find its corresponding drive queue element and the identity of its disk driver.

Once the File Manager has determined the drive number and the controlling disk driver of the medium containing the volume, it calls the disk driver to transfer blocks of 512 characters to and from the device. The disk driver takes the positioning information and data from the File System and converts them into commands for the disk drive's hardware controller and interface electronics. Any resulting data and completion codes are returned to the File System when the transfer operation completes. The biggest advantage of this scheme is that new RAM based disk drivers can be added to the system to support new storage media without any modification to the fixed, ROM based File System.

Fig. 2: Creating an 800k HFS Nested Volume Named Editor

Normally, the drive queue contains one element for each physical storage medium connected to the Macintosh, including the internal and external drives, RAM disks, and hard disks, etc. In the case where several logical volumes share the same storage medium, convention dictates that one element be placed in the drive queue for each logical volume. This convention guarentees that each mounted volume has associated with it a unqiue drive number. Although the drive queue elements of these volumes have differing drive numbers, they all share the same disk driver number. Hence, given a drive number of a particular mounted volume, a disk driver should be able to determine both the identity of the storage medium containing the volume, as well as the location of the volume's data within that medium. In the case of a volume nested within a host file, this information consists of the drive number of the volume that contains the nested volume's host file, as well as the location within the volume of the host file's contents.

Nested Volumes

Two pieces of software are required to implement nested volumes:

• A mount manager, and

• A disk driver.

The mount manager, which I implemented as a desk accessory named Nest, presents a user interface that allows you to create, mount and unmount nested volumes. The desk accessory is responsible for establishing the necessary drive queue elements and other data structures that allow File System and the disk driver to correctly locate and transfer information to and from the volume's host file. For those of you familiar with General Computer's Hyperdrive product, Nest incorporates the functions of both their Drawers desk accessory and their Manager application.

The second component of the system, the disk driver .Nest, is responsible for redirecting and translating the File System's block level read and write requests to and from the nested volume into reads and writes to and from the contents of the volume's host file. The driver is shared by all of the mounted nested volumes, and uses information defined by the desk accessory to associate each volume with the contents of its host file.

When creating a volume, the desk accessory displays a standard save dialog allowing you to name the volume, as well as specify its size and whether it should be initialized as an MFS or HFS volume. In the example below, I show the creation of the 800K HFS nested volume Editor. The host file containing the new volume, which is also named Editor, will be saved in the Nest folder of the volume DataFrame. (Normally, the name of a nested volume is the same as the name of its host file, however, you may later change the name of either one in the Finder with no ill effects other than your own confusion.) I implemented the dialog's extra buttons and text items in the standard way using the standard file packages documented hooks.

The desk accessory creates the nested volume's host file by calling the routine newnest. Its arguments are the file name and volume reference number returned by the standard file save dialog, along with the desired size of the volume in blocks of 512 characters. Newnest first uses the File Manager's routine create to create the file, and then the new HFS routine alloccontig to allocate the desired number of blocks in one contiguous area. Alloccontig is identical to the original File System routine allocate, except that if there isn't enough contiguous, empty space on the volume to satisfy the allocation request, alloccontig will do nothing and will return dskfulerr as its function result. By allocating the space in one contiguous area, we greatly simplify the implementation of the disk driver.

If the allocation succeeds, the routine seteof is called to set the file's logical end-of-file equal to the desired size of the volume. Finally, newnest closes the file and flushes the volume on which the file resides. If the allocation request cannot be satisfied, newnest deletes the file before returning.

int newnest(fname, vrefnum, blocks)
 char *fname;
 int vrefnum;
 int blocks;
 {
 long count;
 int frefnum;
 int result;

 /* create and open file */
 fsdelete(fname, vrefnum);
 create(fname, vrefnum, NESTCREATOR, NESTTYPE);
 fsopen(fname, vrefnum, &frefnum);

 /* allocate contiguous area, save result */
 count = (long) blocks * 512;
 result = alloccontig(frefnum, &count);

 /* set logical end-of-file */
 count = (long) blocks * 512;
 seteof(frefnum, count);

 /* close, flush, delete if allocation failed, and */
 /* return result */
 fsclose(frefnum);
 if (result)
 fsdelete(fname, vrefnum);
 flushvol(0l, vrefnum);
 return result;
 }

When mounting a volume, the desk accessory displays a standard open dialog allowing you to select one of the previously created host files. In the example below, I show the selection of the nested volume contained in the host file Editor from the Nest folder of DataFrame.

In this dialog, the button labeled Mount actually switches between the three titles Mount, Unmount and Open. It is Mount if the selected item is a closed host file whose volume is not mounted. It is Unmount if the selected item is an open host file whose volume is mounted. Finally, it is Open if the selected item is a folder. The New button brings up the save dialog discussed above.

Fig. 3: Desk Accessory file selection

The desk accessory mounts the volume contained within a host file by calling the routine mountnest. Its arguments are the file name and volume reference number returned by the standard file open dialog, along with the a flag that indicates whether or not the volume should be initialized.

After calling newnest to create a new host file and nested volume, the desk accessory assumes you want to immediately mount the volume and hence calls mountnest directly, rather than displaying the open dialog. In this case, the volume needs to be initialized and hence the initialize flag is set true. Otherwise, the desk accessory assumes that the volume is already initialized and sets the initialize flag false.

Mountnest begins by opening the file with fsopen and determining the size of the volume with geteof. Then a new drive queue element is allocated in the system heap. In addition to the standard fields, the drive queue elements contains a set of custom fields shown in the definition below. These fields are used by the disk driver when it translates block level data transfers into operations on the host file.

/* customized drive queue element */
typedef struct _drvqel
 {
 /* standard fields */
 char dqwriteprot; /* write protected */
 char dqdiskinplace; /* = 8 for non-eject media */
 char dqinstalled; /* unused */
 char dqsides;   /* unused */
 struct _drvqel *qlink; /* next drive que element */
 int qtype; /* unused */
 int dqdrive;    /* drive number */
 int dqrefnum;   /* disk driver ref number */
 int dqfsid;/* = 0 for Mac File System */
 int dqdrvsize;  /* size of volume in blocks */
 
 /* custom fields */
 int dqfrefnum;  /* host file reference number */
 int dqvrefnum;  /* nested volume reference number */
 int dqdqrefnum; /* host disk driver ref number */
 int dqdqdrive;  /* host volume drive number */
 long dqvstart;  /* host byte position of first block */
 long dqvmark;   /* host byte position current block */
 long dqvend;    /* host byte position of last block */
 } drvqel, *drvqelptr;

Mountnest then initializes the standard fields of this drive queue element. The field dqdiskinplace is set to the standard value of 8 to indicate that the volume is non-ejectable. This causes the Finder's Eject command and the MiniFinder's Eject button to be dimmed when the nested volume is selected.

Mountnest then finds an unused drive number by calling the routine finddrvnum and saves the returned value in the field dqdrive. Finddrvnum looks at all of the existing drive queue elements for an available number.

Next, the disk driver reference number of the disk driver .Nest is saved in the dqrefnum field. The desk accessory obtains this value when it opens the .Nest with the call

 opendriver(".Nest", &dqrefnum);

After initializing the standard fields, mountnest saves the file reference number of the volume's host file in the field dqfrefnum, the disk driver reference number of the volume containing the host file in the field dqdqrefnum, and the drive number of the volume containing the host file in the field dqdqdrive. The first of these three fields is used to close the host file when the nested volume is unmounted. The second and third of these fields, which are returned by the call to the routine infonest, are used by the disk driver .Nest. They allow the disk driver to translate a block level data transfer request to the nested volume into a block level request to the disk driver of the volume containing the host file.

Mountnest then determines dqvstart, the byte position of the first byte in the first block of the nested volume's host file. The value of this field is the key to the implementation of nested volumes. Whenever the File System requests the first block of a nested volume, the disk driver must return the first block of the host file. Similarly, when the second block of the volume is requested, the second block of the host file must be returned, and so on. Since the host file's blocks are allocated in one contiguous section on the host volume, the disk driver only needs to know the byte position of the host file's first block. All other positioning can be implemented by adding the appropriate offset to this base location. [Remember, we allocated space for this nested volume as a continguous section to simplify this operation.]

This byte position is determined by three values:

• the number of the first allocation block of the host file,

• the size of an allocation block on the host volume, and

• the block number of the host volume's first allocation block.

The first of these values is contained in the fcbextrec[0] field of the host file's file control block, whose structure is outlined below. The File System maintains one file control block for each open file. Given a file reference number of an open file, the define FCBPTR returns a pointer to its file control block. The second and third of the above values are obtained by the File System's routine pbgetvolinfo. Hence, the byte position of the host file's first block equals the product of the first two of these values added to 512 times the value of the third.

The file control block's fcbextrec field contains the first three file extent descriptors of the open file. A file extent is a series of contiguous allocation blocks. Ideally, a file would be stored in a single extent, as our host files are, but in general, the contents of a file are stored in more than one extent in different places on the storage medium. Each extent is identified by an extent descriptor, which specifies the number of the first allocation block of the extent and the length of the extent in blocks. Space on a volume is allocated by the File System in units of allocation blocks, each equal to some number of 512 character blocks. Hence, fcbextrec[0] and fcbextrec[1] form the extent descriptor of the file's first extent, where the first field is an allocation block number and the second a block length. In a similar manner, fcbextrec[2] and fcbextrec[3] describe the file's second extent; fcbextrec[4] and fcbextrec[5] describe the file's third extent, if they exist. Since all host file's are contiguous, we only need to be concerned with the value of fcbextrec[0].

/* file control block */
typedef struct
 {
 long fcbFlNum;  /* file number */
 char fcbMdRByt; /* flags */
 char fcbTypByt; /* version number */
 int fcbSBlk;    /* first allocation block of file */
 long fcbEOF;    /* logical end-of-file */
 long fcbPLen;   /* physical end-of-file */
 long fcbCrPs;   /* current position */
 ptr fcbVPtr;    /* pointer to volume control block */
 ptr fcbBfAdr;   /* pointer to access path buffer */
 int fcbFlPos;   /* used internally */
 long fcbclmpsiz;/* file clump size */
 ptr fcbbtcbptr; /* pointer to B*-tree control block */
 int fcbextrec[6]; /* first three file extents */
 long fcbftype;  /* file's finder type bytes */
 long fcbcatpos; /* used internally */
 long fcbdirid;  /* file's parent id */
 char fcbcname[32];/* name of open file */
 } fcb, *fcbptr;
#define FCBPTR(refnum) \
 ((fcbptr) ((*(char **) 0x34e) + (refnum)))

Once the value of dqvstart is computed, mountnest initializes the fields dqvmark and dqvend. These fields are used by the disk driver when translating positioning information, and correspond to the byte position of the next block to be read or written and the position of the byte just beyond the end of the host file, respectively.

Mountnest then inserts the initialized drive queue element into the drive queue by calling the routine enqueue. Finally, mountnest call the File System routine mountvol to mount the volume, unless the volume must be first initialized, in which case mountnest calls zeronest. In either case, the new drive queue element is ready to provide the File System with the information it needs to locate the disk driver responsible for data transfer operations.

int mountnest(fname, vrefnum, zero)
 char *fname;
 int vrefnum;
 int zero;
 {
 int frefnum;
 int blocks;
 long alblksize;
 int alblstart;
 drvqelptr thedrvqel;

 /* open file and determine size of volume */
 fsopen(fname, vrefnum, &frefnum);
 geteof(frefnum, &alblksize);
 blocks = alblksize / 512;
 
 /* allocate standard drive queue element */
 thedrvqel = (drvqelptr) newsysptr((long) sizeof(drvqel));
 thedrvqel->dqdiskinplace = 8;
 thedrvqel->dqdrive = finddrvnum(1024);
 thedrvqel->dqrefnum = dqrefnum;
 thedrvqel->dqdrvsize = blocks;
 
 /* customize drive queue element */
 thedrvqel->dqfrefnum = frefnum;
 infonest(0l, vrefnum, &thedrvqel->dqdqrefnum, 
 &thedrvqel->dqdqdrive, &alblksize, &alblstart);
 thedrvqel->dqvstart = FCBPTR(frefnum)->fcbextrec[0] * 
 alblksize + alblstart * 512l;
 thedrvqel->dqvmark = thedrvqel->dqvstart;
 thedrvqel->dqvend = 
 thedrvqel->dqvstart + (long) blocks * 512;
 
 /* enqueue element on drive queue */
 enqueue(&thedrvqel->qlink, getdrvqhdr());
 
 /* optionally zero, then mount volume */
 if (zero)
 return zeronest(thedrvqel, fname, putformat);
 else
 return mountvol(thedrvqel->dqdrive, 
 &thedrvqel->dqvrefnum);
 }

When unmounting a nested volume, the desk accessory displays a standard open dialog allowing you to select one of the currently open host files. The desk accessory unmounts the volume contained within the selected host file by calling the routine unmountnest. Its arguments are the file name and volume reference number of the selected host file, along with its file reference number. This latter number is discovered with a call to the File System routine pbgetfileinfo, which returns, among other things, a file reference number if the file is open.

Unmountnest first calls finddrvqel, which searches the drive queue for the element corresponding to the selected host file's nested volume. Finddrvqel looks for an element with the appropriate disk driver reference number in the dqrefnum field as well as a matching file reference number in dqfrefnum. Then the File System routine unmountvol is called to unmount the nested volume. Finally, the host file is closed and the drive queue element is removed from the drive queue.

int unmountnest(fname, vrefnum, frefnum)
 char *fname;
 int vrefnum;
 int frefnum;
 {
 drvqelptr thedrvqel;
 
 thedrvqel = finddrvqel(dqrefnum, frefnum);
 unmountvol(0l, thedrvqel->dqvrefnum);
 fsclose(frefnum);
 dequeue(&thedrvqel->qlink, getdrvqhdr());
 flushvol(0l, vrefnum);
 return 0;
 }

Initializing Nested Volumes

When a host file is created, its contents must be initialized to that of a properly formatted volume before it can be mounted. The routine zeronest performs this function. Its arguments are a pointer to the volume's drive queue element, the volume's name, the a flag indicating whether the volume should be formated as an HFS or MFS volume. If HFS formatting is desired, zeronest simply calls the disk initialization package routine dizero. Its arguments are the volume's drive number and name. Dizero will first format and then mount the volume. (In fact, dizero will HFS format only those volumes that are larger than 400k; smaller volumes are forced to be MFS.) It is important to realize that dizero will call on the disk driver directly to write formatting information to the volume. This is ok since the desk accessory has already opened the disk driver and mountnest has inserted a proper drive queue element into the drive queue.

If a MFS volume is desired, zeronest writes the appropriate information described in the "Data Organization on Volumes" section of the "The File Manager" chapter of Inside Macintosh. First, the system startup, master directory, block map, and file directory blocks are zeroed with direct write calls to the disk driver. Then the volume information area in the master directory block is written. The allocation block size is chosen to minimize the size of the master directory's block map, and then the number of allocation blocks on the volume is determined. The constant DRDIRST defines the block number of the first block in the file directory. DRBLLEN defines the number of blocks in the file directory. Finally, the routine mountvol is called to mount the volume.

int zeronest(thedrvqel, fname, format)
 drvqelptr thedrvqel;
 char *fname;
 int format;
 {
 paramblockrec pb;
 int block;
 volinfoptr thevolinfo;
 long size;
 
 if (format == puthfs)
 {
 dizero(thedrvqel->dqdrive, fname);
 return getvinfo(thedrvqel->dqdrive, fname, 
 &thedrvqel->dqvrefnum, &size);
 }
 else
 {
 pb.paramunion.ioparam.iorefnum = 
 thedrvqel->dqrefnum;
 pb.iovrefnum = thedrvqel->dqdrive;
 pb.paramunion.ioparam.ioposmode = fsfromstart;
 pb.paramunion.ioparam.ioposoffset = 0l;
 pb.paramunion.ioparam.ioreqcount = 512l;
 pb.paramunion.ioparam.iobuffer = 
 newappptr(512l);
 for (block = DRDIRST + DRBLLEN; block; block--, pb.paramunion.ioparam.ioposoffset 
+= 512l)
 pbwrite(&pb, 0);
 
 thevolinfo = (volinfoptr) 
 pb.paramunion.ioparam.iobuffer;
 thevolinfo->drsigword = 0xd2d7;
 getdatetime(&thevolinfo->drcrdate);
 thevolinfo->drlsbkup = thevolinfo->drcrdate;
 thevolinfo->dratrb = 0;
 thevolinfo->drnmfls = 0;
 thevolinfo->drdirst = DRDIRST;
 thevolinfo->drbllen = DRBLLEN;
 thevolinfo->dralblst = thevolinfo->drbllen + 
 thevolinfo->drdirst;
 thevolinfo->dralblksiz = ((thedrvqel->dqdrvsize - 
 thevolinfo->dralblst) / 400) * 1024l;
 if (thevolinfo->dralblksiz < 1024l)
 thevolinfo->dralblksiz = 1024l;
 thevolinfo->drclpsiz = thevolinfo->dralblksiz * 8;
 thevolinfo->drfreebks = thevolinfo->drnmalblks = 
 ((thedrvqel->dqdrvsize - thevolinfo->dralblst) * 
 512l) / thevolinfo->dralblksiz;
 thevolinfo->drnxtfnum = 1l;
 strcpy(thevolinfo->drvn, fname);
 ctopstr(thevolinfo->drvn);
 pb.paramunion.ioparam.ioposoffset = 1024l;
 pbwrite(&pb, 0);
 disposptr(pb.paramunion.ioparam.iobuffer);
 
 return mountvol(thedrvqel->dqdrive, 
 &thedrvqel->dqvrefnum);
 }
 }

Nested Disk Driver

The nested volume disk driver .Nest is built as a standard device driver consisting of five routines drvropen, drvrclose, drvrcontrol, drvrstatus and drvrprime. Parameters to these routines are passed in standard low level parameter blocks, along with a pointer to the driver's device control entry, which are both described in detail in the File Manager and Device Driver chapters of Inside Macintosh.

Each of these five routines returns a long result, which is rather unusual. The lower 16-bits of this result is the result code returned to the Device Manager. The high order bit of the upper 16-bits is used distinguished completed I/O requests from those that are still pending. In this version of the disk driver, all of the requests are completed synchronously, so the high order bit is aways set, except for the special case of a killio control call.

The routines drvropen and drvrclose are especially simple. They simply return the noerr result code.

Drvrstatus is a bit more complex. It redirects any status calls to the disk driver of the drive containing the host file's volume. The procedure is quite simple. First the argument parameter block is copied into a local variable. Then the copy's iorefnum field is changed to the disk driver number of the host file's volume, and its iovrefnum is changed to the drive number of the host file's volume. This information is found by searching the drive queue for the nested volume's element and uses the information saved in that element by the desk accessory when the volume was mounted. Next, the redirected request is executed with a call to the routine pbstatus. Finally, the results of the pbstatus call are copied back into the proper place in the orginal, argument parameter block.

This scheme of redirecting the requests to the disk driver of the host file's volume is used in drvrcontrol and drvrprime, as well. In drvrcontrol, the calls to format and verify the nested volume, and the call to return icon information are handled as special cases. The calls to format and verify a volume are made by the Finder when its Erase Disk command is used. (The first time I tried the Erase Disk command on a nested volume, I ended up erasing my whole hard disk!) Since we only want the contents of the host file to change, not the volume containing the host file, we are careful not to forward either of these requests. Drvrcontrol simply returns the result code noerr. The third case, when the parameter's cscode field equals icondesccode, is the call used by the Finder to discover the volume's desktop icon and its Get Info information string. This request is redirected, and its result, which consist of a single pointer to an an area of memory containing an icon, an icon mask, and a pascal string are appropriately returned.

Finally, drvrcontrol intercepts the killio call and returns noerr, using the return macro IORESULT rather than IODONE, as required by a warning in the Device Manager chapter in Inside Macintosh.

The heart of the driver is, of course, the drvrprime routine. It implements the actual data transfer operations to and from the nested volume. With the help of the information saved in the drive queue element by the desk accessory, its job is relatively straightforward. First it determines which block of the volume's host file is to be accessed. It does this by checking the positioning information contained in the parameter block's ioposmod and ioposoffset fields. Depending on the value of ioposmode, drvrprime adds the appropriate starting, ending, or current byte position values saved in the drive queue element to the value of ioposoffset. Then the I/O operation is performed by calling the disk driver of the volume containing the host file. Finally, the appropriate information is returned in the argument parameter block, which consists of the actual number of bytes transfered in the ioactcount field, and the new current position, in the ioposoffset field. This latter value is also used the current position saved in the the drive queue element itself.

The rest of the source implements the parameter block copying and drive queue search utility routines initpb and finddrvqel.

/*
 * Nest Disk Driver
 *
 * Copyright (c) 1986 by Mike Schuster
 * All Rights Reserved
 */

#include "drvr.h"
#include "file.h"
#include "nest.h"

DRVR
 (
 0x4f00,
 0,
 0,
 0,
 5,
 ".Nest"
 )

extern drvqelptr finddrvqel();
extern drvqelptr initpb();

/* handle open request */
long drvropen(thepb, thedctl)
 paramblkptr thepb;
 dctlentryptr thedctl;
 {
 return noerr;
 }

/* handle close request */
long drvrclose(thepb, thedctl)
 paramblkptr thepb;
 dctlentryptr thedctl;
 {
 return noerr;
 }

/* handle status request */
long drvrstatus(thepb, thedctl)
 paramblkptr thepb;
 dctlentryptr thedctl;
 {
 paramblockrec pb;
 drvqelptr thedrvqel;
 
 /* initialize parameter block */
 thedrvqel = initpb(&pb, thepb);

 /* pass on all requests to host */
 pbstatus(&pb, 0);
 blockmove(&pb.paramunion.cntrlparam.csparam, 
 &thepb->paramunion.cntrlparam.csparam, 22l);
 
 return IODONE(pb.ioresult);
 }

/* handle control request */
long drvrcontrol(thepb, thedctl)
 paramblkptr thepb;
 dctlentryptr thedctl;
 {
 paramblockrec pb;
 drvqelptr thedrvqel;
 
 /* initialize parameter block */
 thedrvqel = initpb(&pb, thepb);
 
 /* handle format code */
 if (pb.paramunion.cntrlparam.cscode == formatcode)
 pb.ioresult = noerr;
 
 /* handle verify code */
 else if (pb.paramunion.cntrlparam.cscode == verifycode)
 pb.ioresult = noerr;

 else if (pb.paramunion.cntrlparam.cscode == killcode)
 return IORESULT(pb.ioresult);
 
 /* handle finder's icon description code */
 else if (pb.paramunion.cntrlparam.cscode == 
 icondesccode)
 {
 pbcontrol(&pb, 0);
 *(ptr *) &thepb->paramunion.cntrlparam.csparam = 
 *(ptr *) &pb.paramunion.cntrlparam.csparam;
 }
 
 /* pass on all other requests to host */
 else
 {
 pbcontrol(&pb, 0);
 blockmove(&pb.paramunion.cntrlparam.csparam, 
 &thepb->paramunion.cntrlparam.csparam, 22l);
 }
 
 return IODONE(pb.ioresult);
 }

/* handle read and write requests */
long drvrprime(thepb, thedctl)
 paramblkptr thepb;
 dctlentryptr thedctl;
 {
 paramblockrec pb;
 drvqelptr thedrvqel;
 
 /* initialize parameter block */
 thedrvqel = initpb(&pb, thepb);
 
 /* remap position relative to start of volume */
 switch (pb.paramunion.ioparam.ioposmode)
 {
 case fsfromstart:
 pb.paramunion.ioparam.ioposoffset += 
 thedrvqel->dqvstart;
 break;
 case fsfrommark:
 pb.paramunion.ioparam.ioposoffset += 
 thedrvqel->dqvmark;
 break;
 case fsfromleof:
 pb.paramunion.ioparam.ioposoffset += 
 thedrvqel->dqvend;
 break;
 case fsatmark:
 pb.paramunion.ioparam.ioposoffset = 
 thedrvqel->dqvmark;
 break;
 }
 pb.paramunion.ioparam.ioposmode = fsfromstart;
 
 /* perform the request */
 if (pb.iotrap & 0x1)
 pbwrite(&pb, 0);
 else
 pbread(&pb, 0);
 
 /* update current position */
 thedrvqel->dqvmark = 
 pb.paramunion.ioparam.ioposoffset;
 
 /* return results */
 thepb->paramunion.ioparam.ioactcount = 
 pb.paramunion.ioparam.ioactcount;
 thepb->paramunion.ioparam.ioposoffset = 
 pb.paramunion.ioparam.ioposoffset - 
 thedrvqel->dqvstart;
 
 return IODONE(pb.ioresult);
 }

/* find drvqel, given driver reference number and drive number */
drvqelptr finddrvqel(dqrefnum, dqdrive)
 int dqrefnum;
 int dqdrive;
 {
 drvqelptr thedrvqel;
 
 for (thedrvqel = ((qhdrptr) getdrvqhdr())->qhead; 
 thedrvqel && (DRVQELPTR(thedrvqel)->dqrefnum != 
 dqrefnum || DRVQELPTR(thedrvqel)->dqdrive != 
 dqdrive); 
 thedrvqel = DRVQELPTR(thedrvqel)->qlink)
 ;
 return thedrvqel ? DRVQELPTR(thedrvqel) : 0l;
 }

/* initialize parameter block */
drvqelptr initpb(pb, thepb)
 paramblkptr pb;
 paramblkptr thepb;
 {
 drvqelptr thedrvqel;
 
 *pb = *thepb;
 
 /* find drive queue element */
 thedrvqel = finddrvqel(pb->paramunion.ioparam.iorefnum, 
 pb->iovrefnum);
 
 /* install driver reference number and drive number */
 pb->paramunion.ioparam.iorefnum = 
 thedrvqel->dqdqrefnum;
 pb->iovrefnum = thedrvqel->dqdqdrive;
 
 return thedrvqel;
 }

The remaining source listed below shows the glue code that interfaces the C language implementation of the disk driver to the Device Manager. They replace the "acc.h" and "acc.c" files in the Megamax C language development system. The fundamental difference between these files and those provided in the Megamax system is that the IODone routine is called appropriately by checking the high-order bit of the result returned by the C language routines. (I hope development system suppliers will standardize their interfaces so that porting drivers and desk accessories is will become easier. The idea of returning a long result originated with Bill Croft in his SUMACC C language UNIX-based cross development system.)


/*
 * Driver Interface
 *
 * Copyright (c) 1986 by Mike Schuster
 * All Rights Reserved
 */

int _ACCDUMMY;

extern int _drvropen();
extern int _drvrprime();
extern int _drvrcontrol();
extern int _drvrstatus();
extern int _drvrclose();

#define IORESULT(result) \
 ((long) (result) & 0x0000ffffl)
#define IODONE(result) \
 (((long) (result) & 0x0000ffffl) | 0x80000000l)

#define DRVR(F, D, E, M, L, S) \
asm { \
 dc.w   F \
 dc.w   D \
 dc.w   E \
 dc.w   M \
 dc.w   _drvropen+8 \
 dc.w   _drvrprime+10 \
 dc.w   _drvrcontrol+12 \
 dc.w   _drvrstatus+14 \
 dc.w   _drvrclose+16 \
 dc.b   L \
 dc.b   S \
 }

/*
 * Driver Interface
 *
 * Copyright (c) 1986 by Mike Schuster
 * All Rights Reserved
 */

extern int _GLBLSIZE;
#define SAVEA4 694

extern int _drvropen();
extern int _drvrprime();
extern int _drvrcontrol();
extern int _drvrstatus();
extern int _drvrclose();

extern int _opendrvr();
extern int _calldrvr();
extern int _closedrvr();

extern long drvropen();
extern long drvrprime();
extern long drvrcontrol();
extern long drvrstatus();
extern long drvrclose();

asm 
 {
_drvropen:
 lea    drvropen(PC),A2
 jmp    _opendrvr(PC)
_drvrclose:
 lea    drvrclose(PC),A2
 jmp    _closedrvr(PC)
_drvrcontrol:
 lea    drvrcontrol(PC),A2
 jmp    _calldrvr(PC)
_drvrstatus:
 lea    drvrstatus(PC),A2
 jmp    _calldrvr(PC)
_drvrprime:
 lea    drvrprime(PC),A2
 jmp    _calldrvr(PC)
 }

asm
 {
_context:
 move.l 20(A1),A4; dctlstorage
 move.l (A4),A4  ; globals
 dc.w 0x98fc; suba.w #_GLBLSIZE,A4
 dc.w _GLBLSIZE
 move.l A4,SAVEA4; save A4
 rts
 }

asm
 {
_opendrvr:
 tst.l  20(A1)   ; dctlstorage
 bne  _calldrvr
 move.l SAVEA4,-(A7) ; save context
 movem.lD4-D7,-(A7); save D registers
 movem.lA0-A4,-(A7); save A registers
 
 moveq  #0,D0
 dc.w 0x907c; sub.w #_GLBLSIZE,D0
 dc.w _GLBLSIZE
 dc.w 0xa322; newhandle()
 dc.w 0xa029; hlock()
 move.l A0,20(A1); save dctlstorage
 jsr    _context
 
 move.w 24(A1),D3; compute owned resource id
 not.w  D3
 asl.w  #5,D3
 ori.w  #0xc000,D3
 
 subq.l #4,A7    ; get data
 move.l #'DATA',-(A7)
 move.w D3,-(A7)
 dc.w 0xa9a0; data = getresource('DATA', ownid)
 move.l (A7)+,D0
 beq  _nodata
 
 move.l 20(A1),A1; dctlstorage
 move.l D0,A0
 move.l D0,-(A7) ; save data
 dc.w 0xa9e4; handandhand()
 dc.w 0xa9a3; releaseresource(data)

_nodata:
 suba.l #4,A7    ; get init code
 move.l #'INIT',-(A7)
 move.w D3,-(A7)
 dc.w 0xa9a0; init = getresource('INIT', ownid)
 move.l (A7)+,D0
 beq  _callopen

 move.l D0,A0
 move.l D0,-(A7) ; save init
 move.l (A0),A0
 jsr    (A0); call init
 dc.w 0xa9a3; releaseresource(init)
 bra    _callopen

_calldrvr:
 move.l SAVEA4,-(A7) ; save context
 movem.lD4-D7,-(A7); save D registers
 movem.lA0-A4,-(A7); save A registers
 jsr    _context
_callopen:
 jsr    (A2)
_openrts:
 movem.l(A7)+,A0-A4; restore A registers
 movem.l(A7)+,D4-D7; restore D registers
 move.l (A7)+,SAVEA4 ; restore context
 tst.l  D0; call iodone?
 bge  _callrts
 move.l 0x8fc,-(A7); jiodone address
_callrts:
 tst.w  D0; test result
 rts

_closedrvr:
 move.l SAVEA4,-(A7) ; save context
 movem.lD4-D7,-(A7); save D registers
 movem.lA0-A4,-(A7); save A registers
 jsr    _context
 jsr    (A2)
 move.l D0,-(A7) ; save result
 movem.l(A7),D0/A0-A1; restore arguments
 move.l 20(A1),A0; dctlstorage
 clr.l  20(A1)   ; restore dctlstorage
 dc.w 0xa023; deallocate
 move.l (A7)+,D0 ; restore result
 bra    _openrts
 }

A Few Comments

I hope this look at the File System and Device Drivers has been useful to you. If you have any questions or notice any blunders, please send them to MacTutor and I'll try to answer or fix them in a future column. The source and object of the nested volume manager Nest as well as the DA and all the supporting files are available on source code disk #7 from MacTutor's mail order store for $8. Remember, this software is only an example, and definitely is not bullet proof. Don't try it on a hard disk until you have backed-up its contents. Here is the procedure you might use to install it:

• Use Apple's Font/DA Mover application to copy the desk accessory Nest from the Nest suitcase and paste into your system folder.

• Use Apple's ResEdit application to copy the .Nest DRVR resource from the Nest suitcase and paste into your system folder. You may have to change the DRVR's resource number before pasting the resource. After pasting the resource, set its system heap bit.

Before using Nest, you should be aware of several problems I have discovered while using Version 1.0a1 on a SuperMac hard disk:

• HFS Systems Only: Nest will run only if the HFS file system has been installed on your Macintosh. This means that you either own an HD20, a Macintosh Plus, or your third party hard disk supplier provided you with an HFS update. I have tested Version 1.0a1 on both an HD20 and a SuperMac Technology DataFrame 20.

• Don't Switch Launch: Your system will crash if you attempt to switch launch to a different system folder since the disk driver .Nest is not detached from the system resource file when opened by opendriver. (I consider this an easily fixed bug in opendriver.)

• Don't Mount Non-Contiguous Volumes: Although the original allocated host file space is contiguous, it may not remain so if you duplicate the file or copy it to another volume. (Finder Version 5.2 does not seem to attempt contiguous allocations.) The mount manager checks for contiguous allocation, but does not attempt to re-copy a non-contiguous file to a contiguous area. (This is also an easily fixed bug.)

• Don't Unmount Volumes With Open Files: When unmounting volumes, the mount manager does not check to see if there exist open files on the volume. If your in the Finder, it suffices to drag the nested volume to the trash can. The Finder will update the desktop and unmount the volume automatically. If you unmount the volume using the desk accessory in the Finder, the desktop file may not be correctly updated. If you unmount a volume with an open application or document file, you'll may crash your system and lose your document.

• Beware of Finder's Erase Disk: The Finder's Erase Disk command will format a nested volumes with 400k bytes or fewer as an MFS volume; larger volumes will get HFS directories. This is done independently of the original formatting of the nested volume.

• Beware of Finder's Trask Can: When you drag a nested volume to the trash can, the volume will be unmounted, but the volume's host file will not be automatically closed. The mount manager will notice this and properly clean up the next time it is opened.

 
AAPL
$107.65
Apple Inc.
+0.67
MSFT
$46.80
Microsoft Corpora
+0.75
GOOG
$557.07
Google Inc.
+6.76

MacTech Search:
Community Search:

Software Updates via MacUpdate

Vitamin-R 2.20b1 - Personal productivity...
Vitamin-R creates the optimal conditions for your brain to work at its best by structuring your work into short bursts of distraction-free, highly focused activity alternating with opportunities for... Read more
Dropbox 2.10.44 - Cloud synchronization...
Dropbox is an application that creates a special Finder folder that automatically syncs online and between your computers. It allows you to both backup files and keep them up-to-date between systems... Read more
Sandvox 2.9.2 - Easily build eye-catchin...
Sandvox is for Mac users who want to create a professional looking website quickly and easily. With Sandvox, you don't need to be a Web genius to build a stylish, feature-rich, standards-compliant... Read more
Cocktail 8.0.1 - General maintenance and...
Cocktail is a general purpose utility for OS X that lets you clean, repair and optimize your Mac. It is a powerful digital toolset that helps hundreds of thousands of Mac users around the world get... Read more
LibreOffice 4.3.3.2 - Free Open Source o...
LibreOffice is an office suite (word processor, spreadsheet, presentations, drawing tool) compatible with other major office suites. The Document Foundation is coordinating development and... Read more
VMware Fusion 7.0.1 - Run Windows apps a...
VMware Fusion allows you to create a Virtual Machine on your Mac and run Windows (including Windows 8.1) and Windows software on your Mac. Run your favorite Windows applications alongside Mac... Read more
OneNote 15.3.2 - Free digital notebook f...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
Audio Hijack Pro 2.11.4 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Iridient Developer 3.0.0 beta 3 - Powerf...
Iridient Developer (was RAW Developer) is a powerful image conversion application designed specifically for OS X. Iridient Developer gives advanced photographers total control over every aspect of... Read more
TextWrangler 4.5.11 - Free general purpo...
TextWrangler is the powerful general purpose text editor, and Unix and server administrator's tool. Oh, and also, like the best things in life, it's free. TextWrangler is the "little brother" to... Read more

Latest Forum Discussions

See All

Monster Flash Review
Monster Flash Review By Jordan Minor on October 31st, 2014 Our Rating: :: ALONE IN THE DARKUniversal App - Designed for iPhone and iPad Solid shooting and a surprising amount of spooky tension make Monster Flash a great portable... | Read more »
Retry Review
Retry Review By Rob Thomas on October 31st, 2014 Our Rating: :: SOARING HIGHUniversal App - Designed for iPhone and iPad Flappy who? Let Retry wash all those bad bird-related memories away on a cool retro-flavored flight… right... | Read more »
Dementia: Book of the Dead Review
Dementia: Book of the Dead Review By Lee Hamlet on October 31st, 2014 Our Rating: :: A TOUGH READUniversal App - Designed for iPhone and iPad A witch hunter is sent after a demonic book in the spooky but short-lived Dementia: Book... | Read more »
Card Dungeon, the Semi-Board Game Roguel...
Card Dungeon, the Semi-Board Game Roguelike, Has Been Renovated Posted by Jessica Fisher on October 31st, 2014 [ permalink ] | Read more »
Logitech Protection + Power iPhone5/5S C...
Made by: Logitech Price: $99.99 Hardware/iOS Integration Rating: 3 out of 5 stars Usability Rating: 0.5 out of 5 stars Reuse Value Rating: 0.75 out of 5 stars Build Quality Rating: 0.75 out of 5 stars Overall Rating: 1.25 out of 5 stars | Read more »
This Is Not a Test Goes Free, Permanentl...
This Is Not a Test Goes Free, Permanently Posted by Jessica Fisher on October 31st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Swap Heroes Review
Swap Heroes Review By Campbell Bird on October 31st, 2014 Our Rating: :: STRATEGIC SWAPPINGUniversal App - Designed for iPhone and iPad Rotate a cast of heroes to fend of waves of monsters in this difficult, puzzle rpg.   | Read more »
Night Sky Pro™ (Reference)
Night Sky Pro™ 3.0.1 Device: iOS Universal Category: Reference Price: $2.99, Version: 3.0.1 (iTunes) Description: Night Sky Pro™Wonder No More™ Night Sky Pro™ is the ultimate stargazing experience. From the creators of the original... | Read more »
Audio Defence : Zombie Arena (Games)
Audio Defence : Zombie Arena 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: A zombie shooter audio game. Made from gut-wrenching 3D binaural sound, for a new kind of weird immersion. You... | Read more »
RPG Asdivine Hearts (Games)
RPG Asdivine Hearts 1.1.0 Device: iOS Universal Category: Games Price: $3.99, Version: 1.1.0 (iTunes) Description: SPECIAL PRICE50% OFF (USD 7.99 -> USD 3.99)!!! Travel alongside four companions and a cat in the adventure of a... | Read more »

Price Scanner via MacPrices.net

Apple now offering refurbished 2014 13-inch R...
The Apple Store is now offering Apple Certified Refurbished 2014 13″ Retina MacBook Pros for up to $270 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple Regains Momentum As Windows Stutters An...
The latest smartphone sales data from Kantar Worldpanel ComTech, for the three months to March 2014, shows Apple performing strongly in the first quarter of the year, with sales bouncing back in... Read more
Worldwide Smartphone Shipments Increase 25.2%...
New smartphone releases and an increased emphasis on emerging markets drove global smartphone shipments above 300 million units for the second consecutive quarter, according to preliminary data from... Read more
Apple now offering refurbished 2014 15-inch M...
The Apple Store is now offering Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
Apple drops prices on refurbished 2013 Retina...
The Apple Store has dropped prices on 2013 Apple Certified Refurbished 13″ and 15″ Retina MacBook Pros, with Retina models now available starting at $999. Apple’s one-year warranty is standard, and... Read more
New 2.8GHz Mac mini on sale for $949, save $5...
Abt Electronics has the new 2.8GHz Mac mini in stock and on sale for $949.05 including free shipping. Their price is $50 off MSRP, and it’s the lowest price available for this model from any reseller... Read more
Sale! 3.7GHz Quad Core Mac Pro available for...
 B&H Photo has the 3.7GHz Quad Core Mac Pro on sale for $2649 including free shipping plus NY sales tax only. Their price is $350 off MSRP, and it’s the lowest price for this model from any... Read more
Mujjo Steps Up The Game With Refined Touchscr...
Netherlands based Mujjo have just launched their Refined Touchscreen Gloves, stepping up their game. The gloves feature a updated elegant design that takes these knitted gloves to the next level. A... Read more
Sale! Preorder the new 27-inch 5K iMac for $2...
 Abt Electronics has the new 27″ 3.5GHz 5K iMac on sale and available for preorder for $2374.05 including free shipping. Their price is $125 off MSRP, and it’s the lowest price available for this... Read more
Simplex Solutions Inc. Brings Secure Web Surf...
New York based Simplex Solutions Inc. has announced the release and immediate availability of Private Browser 1.0, its revolutionary new secure web browser developed for iPhone, iPad and iPod touch... Read more

Jobs Board

Position Opening at *Apple* - Apple (United...
**Job Summary** Every day, business customers come to the Apple Store to discover what powerful, easy-to-use Apple products can do for them. As a Business Leader, Read more
Sr. Manager, *Apple* Deployment Programs fo...
**Job Summary** Apple is seeking candidates for a new position on the Education Content and Technology team. iPad and Mac is in the hands of millions of teachers and Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
*Apple* Solutions Consultant (ASC) - Apple I...
…important role that the ASC serves is that of providing an excellent Apple Customer Experience. Responsibilities include: * Promoting Apple products and solutions Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.