From: absurd@applelink.apple.com (Tim Dierks, software saboteur)
Subject: INIT writing 101 (long) [was: Re: little INIT code resource problem]
Date: Thu, 15 Oct 1992 02:48:31 GMT
Organization: MacDTS Marauders
Followup-To: comp.sys.mac.programmer

In article <zocca-141092150341@maria.amc.uva.nl>, zocca@amc.uva.nl (Vincent
Zocca) wrote:
> 
> 
> Hello!
> 
> I've got a probably very little problem with vriting an INIT.
> What I want is to check for disk insert events and perform some action as a
> reaction to them.
> I wrote something that looks like this.
> 
> {-----------------------}
> 
>    {headers and other stuff precede}
> 
> 	procedure Main;
> 
> 		var
> 			INITHndl: handle;
> 			OsError: OSErr;
> 			myEvent: EventRecord;
> 			GotEvent: Boolean;
> 
> 	begin
> 
> 		INITHndl := GetResource('INIT', 0);
> 		DetachResource(INITHndl);
> 
> 		repeat
> 			GotEvent := WaitNextEvent(DiskMask, myEvent, 60, nil);
> 			if GotEvent then
> 				begin
> 					if (HiWord(myevent.message) <> noErr) then
> 						begin
> 							myEvent.where.h := 40;
> 							myEvent.where.v := 20;
> 							InitCursor;
> 							OsError := DIBadMount(myEvent.where, myevent.message);
> 
>    {At this point I want to perform some action}
> 
> 						end;
> 				end;
> 		until False;
> 
> 	end;
> 
> {-----------------------}
> 
> 
> The code resource generated messes up the machine it's installed on. It
> does get disk events at startup though, I've tested that with a
> SysBeep(10).
> 
> Ok,ok,... What is it? What am I forgetting. Give it to me. I can handle it.

Well, OK, if you can handle it....

You need to reconsider how INITs generally work on the Mac; this looks
a lot like an application, but nothing like an INIT.  When an INIT is
called, it needs to install itself in some way so it can examine and 
modify the goings on of the Mac, and then it needs to exit.  This INIT
never exits; it just calls WaitNextEvent over and over again.  At that
rate, the Finder will never get launched; the system will spend the
rest of time sitting in your little REPEAT ... UNTIL false; loop looking
for nothing but disk-inserted events.

Here's the big secret: you _cannot_ retain control; you've got to find
some way to get control later, when you want to do something. Then, the
only thing your INIT does at INIT time is install the mechanism to get
control later.  Usually, this mechanism is a trap patch; if you patch
a trap, you will get control every time that trap is called; at that
time, you can do what you need to do.  Careful selection of which trap
to patch will make it easy to write clever INITs.

For example, let's take your problem.  You want to do something when
a "bad" disk is inserted, after the dialog comes up.  Just for
argument, let's say you want to rename every new disk to "This is a
really new disk!"  One way to do this would be to patch CloseWindow();
you could wait until the "bad mount" dialog got closed and then you
would have control at the right time; just after the bad disk dialog
got dismissed.  This would be a _bad_ way to do this patch, for the
following reasons:
    1) It's going to be a royal pain to be able to tell when the
       bad disk dialog is closing, as opposed to any other window.
    2) CloseWindow only gets passed the WindowPtr to the window to
       close. It might be tough to figure out what disk is the one
       you want to rename (or whatever).
    3) There is no reason 3.
    4) This is real dependant on the way this is all implemented by
       Apple; if they change the way the bad-mount sequence works,
       you'll be in trouble.
    5) You want a trap that will be called at a time when you can
       do what you need to do; for example, if you need to draw
       on the screen, patch a trap that is allowed to move or
       purge memory, because your patch will do so.

So the criteria for selecting a trap to patch are: 1) Ideally, it
should be called only at the time when you want to do what you want
to do, or it should be obvious whether you're interested in this call
from the arguments which get passed.  2) The trap should get passed
all the information you're going to need, or you should have a good
way to get everything you need.  3) The trap should be a natural
place to put this code; if you have to stretch to make it work, it's
more and more likely that it won't work properly on future system
software.

OK, so what's the best trap to select for your purposes?  Let's list
the criteria again:
   1) We want a trap that is only called when a bad disk is being
      dealt with.
   2) We want a trap which is passed a drive number or volume
      reference number for the disk being mounted, so we can figure
      out which disk to rename.
   3) We want a trap which is essential to the process of mounting
      a bad disk, so we can be sure it's less likely to change
      very much.
   4) Since you can rename a disk at any time, criterion 5), above,
      isn't as important, but it would be best if we did it at
      non-interrupt time, because otherwise we don't want to
      make a synchronous file system call.

Given these criteria, I think the obvious choice is DIBadMount();
it's only called when a bad disk is mounted, it gets passed the
drive number in the low word of the event message, it's a crucial
part of the bad-disk process, and it's always called at a "safe"
time, since it can move or purge memory.

OK, so we'll now get control when DIBadMount gets called.  We can't,
however, rename the disk yet, because it hasn't yet been mounted;
it's still an unformatted floppy.  We do know, however, know that
it will be formatted and ready to rename when DIBadMount has
finished.  Therefore, we will tail patch it; we'll call the real
DIBadMount near the start of our code and rename the disk when it
returns.

The only final stumbling block in our plan is that DIBadMount isn't
really a trap; it is only one routine that shares a single trap,
_Pack2.  This means we need to patch _Pack2, but only do anything
when DIBadMount is called.  The way that the _Pack2 call tells
which one of the Disk Initialization Package calls is being called
is from a selector which is pushed on the stack; specifically,
when an application calls DIBadMount, it first pushes the two
public parameters, the Point where, and the long eventMessage,
and then it pushes a 16-bit word onto the stack with the selector
for which call it is making; in the case of DIBadMount, it
pushes a 2.  Thus, we only want to do anything if the call to
_Pack2 is passing a selector of 2; if it's anything else, we
just want to jump through to the original _Pack2 code and 
forget about it; we're only interested in DIBadMount calls.

OK, so here's a description of the entire INIT: (n) indicates
footnote n.

INITEntry:
    Detach our code.  (1)
    Get the original address of _Pack2 with GetTrapAddress.
    Remember the original address of _Pack2.  (2)
    Set the address of _Pack2 to point to our routine Pack2Patch
        using SetTrapAddress.
    Return; we're done installing.

Pack2Patch:
    Check the selector.
    If the selector does not equal 2:
        Get the original address of _Pack2.
        Jump to it.
    If the selector is equal to 2:
        Remember the drive number which was passed.
        Get the original address of _Pack2.
        Call it.
        If DIBadMount returned an error:
            return the error
        If DIBadMount returned noErr:
            rename the volume, using the drive number you remembered.
            return noErr

Footnotes:
(1) Our code needs to be locked and in the system heap; the easiest
    way to do this is to mark the resPreload, resLocked, and resSysHeap
    bits on the INIT resource.  resPreload is important to make sure
    the code gets placed in the best place in the heap.
(2) To remember values like this when you don't have a constant context,
    the best thing to do is use PC-relative storage locations; you
    need to use assembly for this, though (unless you're clever, and
    sicker than I am).  I just posted an example of PC-relative storage
    in an article entitled "Re: Best way to get A5 into VBLTaskProc?".
    (My second post on the subject).  Or, if you're using Think, you
    can use their A4 global stuff.

That's about all I have to say (and I think it's plenty).  I haven't
given you remotely enough information to do this completely; most
lacking is a discussion of stack frame management and assembly versus
higher-level languages; you'll have to figure that out for yourself,
or ask for more.

Tim Dierks
MacDTS, but I speak too damn much.
Xref: sparky comp.sys.mac.programmer:17108 comp.sources.wanted:4701 comp.periphs.printers:2996