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
$524.94
Apple Inc.
+5.93
MSFT
$40.01
Microsoft Corpora
-0.39
GOOG
$536.10
Google Inc.
-20.44

MacTech Search:
Community Search:

Software Updates via MacUpdate

VMware Fusion 6.0.3 - 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
Tweetbot 1.5.1 - Popular iOS twitter cli...
Tweetbot is a full-featured OS X Twitter client with a lot of personality. Whether it's the meticulously-crafted interface, sounds and animation, or features like multiple timelines and column views... Read more
Mac DVDRipper Pro 4.1.7 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more

Latest Forum Discussions

See All

Have a Special Dead Trigger 2 Easter Bas...
Have a Special Dead Trigger 2 Easter Basket Full of Goodies, Courtesy of Madfinger Games Posted by Rob Rich on April 18th, 2014 [ permalink ] Dead Trigger 2 | Read more »
Almost All of Playdek’s Library is on Sa...
Almost All of Playdek’s Library is on Sale Right Now, and You Should Check it Out Posted by Rob Rich on April 18th, 2014 [ permalink ] Playdek has released quite a few great iOS ports of board and card games over the years, and now most of them... | Read more »
Zynga Launches Brand New Farmville Exper...
Zynga Launches Brand New Farmville Experience with Farmville 2: Country Escape Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
David. Review
David. Review By Cata Modorcea on April 18th, 2014 Our Rating: :: MINIMALISTIC IN A DIFFERENT WAYUniversal App - Designed for iPhone and iPad David is a minimalistic game wrapped inside of a soothing atmosphere in which the hero... | Read more »
Eyefi Unveils New Eyefi Cloud Service Th...
Eyefi Unveils New Eyefi Cloud Service That Allows Users to Share Media Across Personal Devices Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Tales from the Dragon Mountain: The Lair...
Tales from the Dragon Mountain: The Lair Review By Jennifer Allen on April 18th, 2014 Our Rating: :: STEADY ADVENTURINGiPad Only App - Designed for the iPad Treading a safe path, Tales from the Dragon Mountain: The Lair is a... | Read more »
Yahoo Updates Flickr App with Advanced E...
Yahoo Updates Flickr App with Advanced Editing Features and More Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
My Incredible Body - A Kid's App to...
My Incredible Body - A Kid's App to Learn about the Human Body 1.1.00 Device: iOS Universal Category: Education Price: $2.99, Version: 1.1.00 (iTunes) Description: Wouldn’t it be cool to look inside yourself and see what was going on... | Read more »
Trials Frontier Review
Trials Frontier Review By Carter Dotson on April 18th, 2014 Our Rating: :: A ROUGH LANDINGUniversal App - Designed for iPhone and iPad Trials Frontier finally brings the famed stunt racing franchise to mobile, but how much does its... | Read more »
Evernote Business Notebook by Moleskin I...
Evernote Business Notebook by Moleskin Introduced – Support Available in Evernote for iOS Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Free HopTo 2.2 Helps Enhance Your Productivit...
The HopTo app helps you do more on your iPad by providing more and easier adaccess to files and documents. Version 2.2 adds Egnyte and HopTo’s Mac OSX File Connector. If you already have the hopTo... Read more
National Distracted Driving Awareness Month:...
As the country recognizes National Distracted Driving Awareness Month, Sprint is reminding wireless consumers to focus on driving while behind the wheel, to not text or email while driving, and to... Read more
13-inch 2.4GHz Retina MacBook Pro available f...
Abt has the 13″ 2.4GHz 128GB Retina MacBook Pro available for $1229 including free shipping. Their price is $70 off MSRP. Read more
iMacs on sale for up to $160 off MSRP this we...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
iPad Airs on sale this weekend for up to $100...
Best Buy has WiFi iPad Airs on sale for $50 off MSRP and WiFi + Cellular iPad Airs on sale for $100 off MSRP on their online store for a limited time, with prices now starting at $449. Choose free... Read more
Apple restocks refurbished Mac minis starting...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Hyundai Brings Apple CarPlay To The 2015 Sona...
Hyundai Motor America has announced it will bring Apple CarPlay functionality to the 2015 Sonata. CarPlay is pitched as a smarter, safer and easier way to use iPhone in the car and gives iPhone users... Read more
Updated iPads Coming Sooner Than We Had Thoug...
MacRumors, cites KGI securities analyst Ming Chi Kuo, well-respected as an Apple product prognisticator, saying that Apple will introduce an upgraded iPad Air and iPad mini in 2014/Q3, meaning the... Read more
Toshiba Unveils New High And Low End Laptop M...
Toshiba has announced new laptop models covering both the high-end and low-end of the notebook computer spectrum. Toshiba 4K Ultra HD Laptop Toshiba’s new Satellite P55t features one of the world’s... Read more
Save up to $270 with Apple refurbished 13-inc...
The Apple Store has Apple Certified Refurbished October 2013 13″ Retina MacBook Pros available starting at $1099, with models up to $270 off MSRP. Apple’s one-year warranty is standard, and shipping... Read more

Jobs Board

*Apple* Automotive Parts Department position...
Apple Automotive is one of the fastest growing dealer…and it shows. Consider making the switch to the Apple Automotive Group today! At Apple Automotive, we Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.