[comp.sys.amiga] ID-handler - unique-number generator - source and executables

qix@ihlpa.ATT.COM (Puckett) (06/14/87)

-------
Below is the source and executable for an AmigaDOS handler "ID:".
I wrote it to help provide a facility similar to mktemp on UNIX (tm).
Since process IDs (being MsgPort addresses) are not necessarily
unique on the Amiga, another method was needed for generating
unique numbers.

See the README file for more information.


			-Ed Puckett.
-------
#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	Makefile
#	loader_Mount
#	loader.asm
#	ID-handler.c
#	ihl.uue
#	ih.uue
# This archive created: Sun Jun 14 10:11:11 1987
cat << \SHAR_EOF > README
README file for ID-handler (version 1.0 13-Jun-87)
==================================================

This program and source are freely distributable, provided the file headers
remain intact (i.e., my name is on them!!!).

Ed Puckett accepts no responsibility for others' use of this program.

...but you shouldn't have any problems!


WHAT IS THIS?
-------------
ID-handler is an AmigaDOS device.  It supports OPEN, CLOSE and READ.
It is used to generate unique identifiers.

Every time someone opens ID:, it generates a new 16 digit decimal number.
This number can then be read (in ASCII) from the FileHandle obtained
by opening ID:.  EOF returns on all reads subsequent to reading the
last digit.

ID: starts with number 0000000000000000 and proceeds in sequence.
You are guaranteed to get a unique number from it since it is a handler,
and communication with it from two processes cannot collide.


USAGE
-----
You can use TYPE to access ID:, as in:

	TYPE ID:

However, typical usage is in a program:

	char  *gensym (sym, buf, bufsize)

	char  *sym, *buf;
	int   bufsize;

	{ int	symlen;
	  BPTR	fh;

	  symlen= strlen (sym);
	  if ( ((symlen + 1 + 16) > bufsize) ||
	       ((fh= Open ("ID:", MODE_OLDFILE)) == 0) )
	    return NULL;

	  strcpy (buf, sym);
	  Read (fh, (buf + symlen), 16);     /* append digits to symbol */
	  buf[symlen + 16]= '\0';
	  Close (fh);

	  return buf;
	}


INSTALLATION
------------
1. Perform the following:
	<uudecode ihl.uue to produce ID-handler-l>
	<uudecode ih.uue  to produce ID-handler>
	Copy ID-handler-l L:ID-handler-loader
	Copy ID-handler   L:ID-handler

2. Add to S:Startup-Sequence (do not include !'s - they denote start of line):
	!Mount ID:
	!Type >NIL: ID:

3. Add to DEVS:Mountlist (do not include !'s - they denote start of line):
	!ID:	 Handler = L:ID-handler-loader
	!	 Stacksize = 1000
	!	 Priority = 5
	!#

4. Reboot


NOTES ON INSTALLATION
---------------------
* You can skip the reboot, and just perform the "Mount" and "Dir" from the
  startup-sequence manually.

* After the "Type", the ID-handler is loaded into the system, and the files
  "L:ID-handler-loader" and "L:ID-handler" will not be accessed until the
  next reboot.	This means you may remove them from L: if you want (until
  next	reboot).  I do this because I copy L: into Ram:.

* TO CHANGE THE HANDLER NAME: change "ID:" to whatever you want (e.g., "NUM:")
  in the following 2 files:
	DEVS:Mountlist		(1 occurrence)
	S:Startup-Sequence	(2 occurrences)

* Feel free to shorten or otherwise change the names "ID-handler-loader"
  and "ID-handler".  Just be sure to reflect those changes in
  "S:Startup-Sequence" and "DEVS:Mountlist".


WHAT IS THIS SILLY "LOADER" FILE?
---------------------------------
According to _The_AmigaDOS_Manual_ (Bantam Books, Feb 1986), page 291:

	If you write your device handler in C, you cannot use the automatic
	load and process creation provided by the kernel.  In this case you
	must load the code yourself . . . .

Well, I know others have gotten around this, and I did, too.  However, in my
"prelude" version of the handler, I noticed that the handler would take about
3 seconds to "Mount" (after first access to it).  This made me very nervous -
visions of wild linking through memory, etc.  The loader version mounts
almost immediately.

Anyway, due to my (possibly unfounded) paranoia, I instead use the BCPL-like
assembly module "ID-handler-loader" which LoadSeg()'s ID-handler.  There
are undoubtedly better ways of handling this, but this works and, for me,
it is not too annoying to put up with the extra file.


COMPILATION
-----------
The supplied C source files were compiled with Lattice v3.03.
The assembly programs were assembled using the Commodore Assembler.

I use an EXECUTE file "cc" to drive the compiler.  It is supplied.


INQUIRIES / COMMENTS / SUGGESTIONS
----------------------------------
Ed Puckett

US Mail:  MIT Branch PO - PO Box 61
	  Cambridge, MA  02139

E Mail:  ...!ihnp4!mit-eddie!mit-oz!qix
SHAR_EOF
cat << \SHAR_EOF > Makefile
mount : loader_mount

handler : loader_ID-handler

obj : loader.o ID-handler.o


loader_mount : handler-loader loader_ID-handler
    Copy  ID-handler-loader  L:
    Copy  loader_ID-handler  L:ID-handler
    Copy  loader_Mount	     DEVS:Mountlist
    Mount ID:


loader_ID-handler : ID-handler.o
    BLink FROM	   ID-handler.o \
	  TO	   loader_ID-handler \
	  LIBRARY  CLIB:lc.lib+CLIB:amiga.lib

ID-handler.o : ID-handler.c
    EXECUTE cc ID-handler


loader : loader.o
    BLink FROM	   ID-handler-loader.o \
	  TO	   ID-handler-loader \
	  LIBRARY  CLIB:amiga.lib

loader.o : loader.asm
    Assem loader.asm  -i :include  -o loader.o	-c w100000
SHAR_EOF
cat << \SHAR_EOF > loader_Mount
/* An example MOUNTLIST file enabling a 5" disk to be mounted
   as DF1: and an interactive serial port mounted as AUX:
*/

DF1:       Device = trackdisk.device
           Unit   = 1
           Flags  = 1
           Surfaces  = 2
           BlocksPerTrack = 11
           Reserved = 2
           Interleave = 0
           LowCyl = 0  ;  HighCyl = 39
           Buffers = 5
           BufMemType = 3 
#
/*  This is provided as an example of an alternative type of 
    non-filing device mount.  Please note that L:aux-handler
    is not provided, and thus this mount does not work.
*/

AUX:       Handler = L:aux-handler
           Stacksize = 700
           Priority = 5
#

P:         Handler = L:pipe-handler-loader
           Stacksize = 2000
           Priority = 5
#

ID:        Handler = L:ID-handler-loader
           Stacksize = 1000
           Priority = 5
#
SHAR_EOF
cat << \SHAR_EOF > loader.asm
; loader.asm


		INCLUDE "exec/types.i"
		INCLUDE "exec/exec.i"
		INCLUDE "libraries/dosextens.i"


		STRUCTURE	STACKDATA,0
		APTR	Packet
		LONG	ReturnVal
		APTR	DOSBase
		BPTR	Segment
		BYTE	STACKDATA_SIZE


XLIB:		MACRO
		XREF	_LVO\1
		ENDM

LINKEXE:	MACRO
		MOVEA.L _AbsExecBase,A6
		JSR	_LVO\1(A6)
		ENDM

LINKDOS:	MACRO
		MOVEA.L DOSBase(SP),A6
		JSR	_LVO\1(A6)
		ENDM


_AbsExecBase	EQU	4


		XLIB	OpenLibrary
		XLIB	CloseLibrary
		XLIB	LoadSeg
		XLIB	UnLoadSeg


;----------------------------------------------------------------------------

StartModule:	DC.L	(EndModule-StartModule)/4	; for BCPL linking


EntryPoint:	SUBA.L	#STACKDATA_SIZE,SP

		LSL.L	#2,D1			; convert to byte pointer
		MOVE.L	D1,Packet(SP)

		CLR.L	ReturnVal(SP)		; no error - for now


OpenDOS:	LEA	DOSName(PC),A1
		CLR.L	D0
		LINKEXE OpenLibrary
		MOVE.L	D0,DOSBase(SP)
		BNE	LoadCode

		MOVE.L	#ERROR_INVALID_RESIDENT_LIBRARY,ReturnVal(SP)
		BRA	Return


LoadCode:	LEA	HandlerName(PC),A1
		MOVE.L	A1,D1
		LINKDOS LoadSeg
		MOVE.L	D0,Segment(SP)
		BNE	CallHandler

		MOVE.L	#ERROR_OBJECT_NOT_FOUND,ReturnVal(SP)
		BRA	CloseDOS


CallHandler:	LEA	SPsave(PC),A1
		MOVE.L	SP,(A1) 		; save current SP

		MOVE.L	Segment(SP),D0		; BPTR to segment
		LSL.L	#2,D0
		MOVEA.L D0,A0			; byte pointer to segment
		MOVE.L	Packet(SP),D0		; packet address
		MOVE.L	 D0,-(SP)		; push (not sure if safe above)

; --- Now, call the loaded handler code.
; --- It is sent the byte address of the startup packet passed to this code.

		JSR	4(A0)			; call first code in segment

		MOVEA.L SPsave(PC),SP		; restore SP


UnloadCode:	MOVE.L	Segment(SP),D1
		LINKDOS UnLoadSeg

CloseDOS:	MOVE.L	DOSBase(SP),A1
		LINKEXE CloseLibrary

Return: 	MOVE.L	ReturnVal(SP),D0	; retrieve return value
		ADDA.L	#STACKDATA_SIZE,SP
		RTS


SPsave: 	DC.L	0

DOSName:	DOSNAME

HandlerName:	DC.B	'L:'
AProcessName:	DC.B	'ID-handler',0


;	trailing definitions for BCPL linking

		CNOP	0,4			; align to lonword boundary

		DC.L	0			; End Marker
		DC.L	1			; Global 1
		DC.L	EntryPoint-StartModule	; Offset
		DC.L	1			; Highest Global Used

EndModule:	END
SHAR_EOF
cat << \SHAR_EOF > ID-handler.c
/****************************************************************************
**  File:       ID-handler.c
**  Program:    ID-handler - an AmigaDOS handler for generating unique names
**  Version:    1.0
**  Author:     Ed Puckett      qix@mit-oz
**
**  Copyright 1987 EpAc Software.  All Rights Reserved.
**
**  History:    02-Feb-87       Original Version
*/

#include   <libraries/dos.h>
#include   <libraries/dosextens.h>
#include   <libraries/filehandler.h>
#include   <exec/exec.h>
#include   <ctype.h>



/*---------------------------------------------------------------------------
** References to system
*/

extern struct Library     *OpenLibrary ();
extern void               CloseLibrary ();
extern struct Task        *FindTask ();
extern ULONG              Wait ();
extern struct Message     *GetMsg ();
extern void               PutMsg ();
extern BYTE               *AllocMem ();
extern void               FreeMem ();

extern struct Library     *AbsExecBase;



/*---------------------------------------------------------------------------
** These are new to the 1.2 release
*/

#ifndef MODE_READWRITE
# define   MODE_READWRITE   1004
#endif  MODE_READWRITE

#ifndef MODE_READONLY
# define   MODE_READONLY    MODE_OLDFILE
#endif  MODE_READONLY

#ifndef ACTION_END
# define   ACTION_END       1007     /* not really new, just missing */
#endif  ACTION_END



/*---------------------------------------------------------------------------
*/

#define   ALLOCMEM_FLAGS    MEMF_PUBLIC


#define   ID_DIGITS   16

typedef struct opendata
  { char   id[ID_DIGITS];
    UBYTE  pos;
  }
OPENDATA;


struct Library  *SysBase  =  NULL;
struct Library  *DOSBase  =  NULL;

static char  ID[ID_DIGITS];



/*---------------------------------------------------------------------------
*/

#define   BPTRtoCptr(Bp)      ((char *) ((ULONG) (Bp) << 2))
#define   CptrtoBPTR(Cp)      ((BPTR)   ((ULONG) (Cp) >> 2))

#define   ReplyPkt(pkt)       PutMsg ((pkt)->dp_Port, (pkt)->dp_Link)



/*---------------------------------------------------------------------------
**      handler() performs initialization, replies to startup packet, and
** dispatches incoming request packets to the apropriate functions.
**      Our DeviceNode Task field is patched with our process ID so that this
** process is used for subsequent handler requests.  The function exits only
** if there is some initialization error.
*/

void  handler (StartPkt)

struct DosPacket  *StartPkt;

{ struct Task        *Task;
  struct MsgPort     *IDPort;
  ULONG              WakeupMask, SigMask;
  struct DeviceNode  *DevNode;
  struct DosPacket   *pkt, *GetPkt();
  unsigned           i;
  void               OpenID(), CloseID(), ReadID();


  SysBase= AbsExecBase;
  if ((DOSBase= OpenLibrary (DOSNAME, 0)) == NULL)
    goto QUIT;

  Task= FindTask (0);
  IDPort= (struct MsgPort *) ((ULONG) Task + sizeof (struct Task));
  ((struct Process *) Task)->pr_CurrentDir= 0;     /* initial file system root */

  WakeupMask= (1L << IDPort->mp_SigBit);

  DevNode= (struct DeviceNode *) BPTRtoCptr (StartPkt->dp_Arg3);
  DevNode->dn_Task= IDPort;

  ReplyPkt (StartPkt);


  for (i= 0; i < ID_DIGITS; ++i)
    ID[i]= '0';


LOOP:
  SigMask= Wait (WakeupMask);

  if (SigMask & WakeupMask)
    while ((pkt= GetPkt (IDPort)) != NULL)
      switch (pkt->dp_Type)
        { case MODE_READWRITE:
            OpenID (pkt);
            break;

          case MODE_NEWFILE:     /* syn: ACTION_FINDOUTPUT */
            pkt->dp_Res1= 0;
            pkt->dp_Res2= ERROR_WRITE_PROTECTED;
            ReplyPkt (pkt);
            break;

          case MODE_READONLY:     /* syn: MODE_OLDFILE, ACTION_FINDINPUT */
            OpenID (pkt);
            break;

          case ACTION_END:
            CloseID (pkt);
            break;

          case ACTION_READ:
            ReadID (pkt);
            break;

          case ACTION_WRITE:
            pkt->dp_Res1= -1;
            pkt->dp_Res2= ERROR_WRITE_PROTECTED;
            ReplyPkt (pkt);
            break;

          default:
            pkt->dp_Res1= 0;
            pkt->dp_Res2= ERROR_ACTION_NOT_KNOWN;
            ReplyPkt (pkt);
        }

  goto LOOP;


QUIT:
  DevNode->dn_Task= NULL;     /* bad if someone in process of accessing us . . . */

  if (DOSBase != NULL)
    CloseLibrary (DOSBase);
}



/*---------------------------------------------------------------------------
**      GetPkt() returns the DosPacket associated with the next message on
** "port", or NULL if the port is empty.  The message is removed from the
** port.  A related macro, ReplyPkt(), is provided above.
*/

static struct DosPacket  *GetPkt (port)

register struct MsgPort  *port;

{ register struct Message  *msg;

  return  ((msg= GetMsg (port)) == NULL)
            ? NULL
            : (struct DosPacket *) msg->mn_Node.ln_Name;
}



/*---------------------------------------------------------------------------
*/

static void  OpenID (pkt)

struct DosPacket  *pkt;

{ struct FileHandle  *handle;
  OPENDATA           *OpenData  =  NULL;
  unsigned           i;
  void               NextID();


  if ((OpenData= (OPENDATA *) AllocMem (sizeof (OPENDATA), ALLOCMEM_FLAGS)) == NULL)
    { pkt->dp_Res1= 0;
      pkt->dp_Res2= ERROR_NO_FREE_STORE;
      ReplyPkt (pkt);
    }

  for (i= 0; i < ID_DIGITS; ++i)
    OpenData->id[i]= ID[i];

  OpenData->pos= 0;

  NextID ();

  handle= (struct FileHandle *) BPTRtoCptr (pkt->dp_Arg1);
  handle->fh_Arg1= (LONG) OpenData;     /* for identification on Read, Close */

  pkt->dp_Res1= 1;
  pkt->dp_Res2= 0;     /* for successful open */
  ReplyPkt (pkt);
}



/*---------------------------------------------------------------------------
*/

static void  CloseID (pkt)

struct DosPacket  *pkt;

{ OPENDATA  *OpenData;


  OpenData= (OPENDATA *) pkt->dp_Arg1;

  FreeMem (OpenData, sizeof (OPENDATA));

  pkt->dp_Res1= 1;
  pkt->dp_Res2= 0;
  ReplyPkt (pkt);
}



/*---------------------------------------------------------------------------
*/

static void  ReadID (pkt)

struct DosPacket  *pkt;

{ OPENDATA  *OpenData;
  unsigned  n;


  OpenData= (OPENDATA *) pkt->dp_Arg1;

  pkt->dp_Res1= 0;

  if (OpenData->pos < ID_DIGITS)
    { if ((n= pkt->dp_Arg3) > (ID_DIGITS - OpenData->pos))
        n= (ID_DIGITS - OpenData->pos);

      for ( ; pkt->dp_Res1 < n; ++(pkt->dp_Res1), ++(OpenData->pos))
        ((char *) pkt->dp_Arg2)[pkt->dp_Res1]= OpenData->id[OpenData->pos];
    }

  pkt->dp_Res2= 0;
  ReplyPkt (pkt);
}



/*---------------------------------------------------------------------------
*/

static void  NextID ()

{ int  i;

  for (i= ID_DIGITS - 1; (i >= 0) && (++(ID[i]) > '9'); --i)
    ID[i]= '0';
}
SHAR_EOF
cat << \SHAR_EOF > ihl.uue
begin 777 ID-handler-l
M   #\P         !               R   #Z0   #(    RG_P    0Y8DO
M00  0J\ !$/Z (9"@"QX  1.KOW8+T  "&8   XO?    'H !&   %A#^@!P
M(@DL;P (3J[_:B]   QF   .+WP   #-  1@   J0_H /B*/("\ #.6(($ @
M+P  +P!.J  $+GH )B(O  PL;P (3J[_9")O  @L>  $3J[^8B O  3?_   
M !!.=0    !D;W,N;&EB<F%R>0!,.DE$+6AA;F1L97(               $ 
+   $     0   _(N
 
end
SHAR_EOF
cat << \SHAR_EOF > ih.uue
begin 777 ID-handler
M   #\P         $          ,   #S    !0    0    L   #Z0   /-.
M5O_D2.<@("/Y    !     !"ITAY    "$ZY    F%"/(\     $2H!G  &.
M0J=.N0   #!8CRU __P&@    %P@;O_\0J@ F'( ($ 2*  /= 'CHB)N  @B
M*0 <Y8$B02-   @D;@ (+Q(O*@ $+4#_^"U!_^PM0O_T3KD   !84(]"KO_D
M("[_Y R     $&02($#1_      0O  P4J[_Y&#B+R[_]$ZY    1%B/+4#_
M\,"N__1*@&?H+R[_^&$  1Y8CRU _^A*@&?6($ @*  (<C $@0    AK  "X
ML+L8"&;P3OL8!@   %=@  ""    4F   &P   /O8   5@   ^U@  !    #
M[F   !8   /L8    B\N_^AA  #R6(]@EB!N_^A"J  ,(7P   #? ! O$"\H
M  1.N0   %A0CV  _W8O+O_H80  Q%B/8 #_:"\N_^AA  %:6(]@ /]:+R[_
MZ&$  8I8CV  _TQP_R!N_^@A0  ,(7P   #? ! O$"\H  1.N0   %A0CV  
M_R@@;O_H0J@ #"%\    T0 0+Q O*  $3KD   !84(]@ /\&D<@B;O_L(T@ 
M"$JY    !&<.+SD    $3KD   "$6(],WP0$3EY.=4Y6__Q(YP ,*FX ""\-
M3KD   !P6(\H0+G\     &8$< !@!B!L  H@"$S?, !.7DYU3E;_]$*N__AP
M 2\ <!$O $ZY     %"/+4#_^$J 9AP@;@ (0J@ #'!G(4  $"\0+R@ !$ZY
M    6%"/0J[_]" N__0,@    !!D%B!N__C1P") T_P     $)%2KO_T8-X@
M;O_X0B@ $&$  0X@;@ (("@ %.6 ($ A;O_X "1R 2)N  @C00 ,0JD $"\1
M+RD !"U __Q.N0   %A0CTY>3G5.5O_\(FX ""!I !1P$2\ +P@M2/_\3KD 
M   84(]P 2!N  @A0  ,0J@ $"\0+R@ !$ZY    6%"/3EY.=4Y6__A(YR  
M(&X ""UH !3__$*H  QP "!N__P0*  0#(     09%(@;@ ((B@ ''00E( M
M0?_XLH)C!"U"__@@;@ (("@ #+"N__AD+"!H !C1P'  (F[__! I !#3P!"1
M(&X "%*H  P@;O_\$"@ $%( $4  $&#&(&X "$*H ! O$"\H  1.N0   %A0
MCTS?  1.7DYU3E;__' /+4#__$JN__QK*B!N__S1_      0$%( $( ,   Y
M;Q0@;O_\T?P     $+P ,%.N__Q@T$Y>3G4   /L    !0    $   '.   !
MQ@   "0    6    #@    0    "   #N@   Z0   )J    F     \    #
M   "T    B8   'R   !U    *P   .    "[    JX   )*   !L    8X 
M  %     ?@   #(    <         _(   /J    !0          9&]S+FQI
M8G)A<GD    #\@   ^L    $   #\@   ^D    L+PXL>0    !,[P #  A.
MKO\Z+%].=0  +PXL>0     B;P (("\ #$ZN_RXL7TYU+PXL>0     B;P (
M3J[^VBQ?3G4O#BQY     " O  A.KO["+%].=2\.+'D     3.\#   (3J[^
MDBQ?3G4  "\.+'D     (&\ "$ZN_HPL7TYU+PXL>0     B;P (3J[^8BQ?
M3G4O#BQY     ")O  @@+P ,3J[]V"Q?3G4   /L    "     $   "<    
MB    '0   !<    2    #0    <    !         /P     U]/<&5N3&EB
M<F%R>0   )@    $7T-L;W-E3&EB<F%R>0       (0    "7T=E=$US9P  
M  !P     E]0=71-<V<     6     )?5V%I=        $0    #7T9I;F14
M87-K        ,     )?1G)E94UE;0   !@    #7T%L;&]C365M        
)          /R
 
end
SHAR_EOF
#	End of shell archive
exit 0

page@ulowell.UUCP (06/15/87)

qix@ihlpa.ATT.COM (Puckett) wrote:
>process IDs (being MsgPort addresses) are not necessarily unique on the Amiga

I bet the scheduler would be surprised to hear that!  Hmmm... is that why
we sometimes see Guru Meditation messages, the scheduler tries to schedule
two processes at the same time?  :-)

Of course process IDs are unique.  Actually you probably want Task IDs,
which the scheduler uses.

..Bob
-- 
Bob Page, U of Lowell CS Dept.   page@ulowell.{uucp,edu,csnet} 

qix@ihlpa.ATT.COM (Puckett) (06/17/87)

In article <1384@ulowell.cs.ulowell.edu>, page@ulowell.UUCP writes:
> qix@ihlpa.ATT.COM (Puckett) wrote:
> >process IDs (being MsgPort addresses) are not necessarily unique on the Amiga
> 
> Of course process IDs are unique.  Actually you probably want Task IDs,
> which the scheduler uses.
> 
I was being sloppy.  What I should have said was that it is possible to
obtain the same process ID twice (a process could go away and another
might *happen* to be allocated with the same MsgPort address).
This would make it slightly unsafe to use Amiga process IDs as mktemp() does,
especially for interprocess communication identifiers (like pipe names)
where a process might create something and then go away.

I do not know about Task IDs.  Does a Task ID ever get reused from
reboot to reboot?  If not, then ID-handler is unnecessary, and Task IDs
will work fine.

			-Ed Puckett.

page@ulowell.cs.ulowell.edu (Bob Page) (06/18/87)

qix@ihlpa.ATT.COM (Ed Puckett) wrote:
>Does a Task ID ever get reused from reboot to reboot?  If not,
>then ID-handler is unnecessary, and Task IDs will work fine.

They get reused.  How about task ID + timestamp?  That should be
pretty unique.

..Bob
-- 
Bob Page, U of Lowell CS Dept.   page@ulowell.{uucp,edu,csnet}