

			DOS Lists
			~~~~~~~~~
				by M.Meany


 A curse on the programmers responsible for DOS!

 Now a quick note for Dave Edwards. You were wondering how Commodore had
overcome the BCPL approach to DOS, they haven't. At least not from the
programmers point of view. The v2.0 programmers interface to DOS is still
bogged down with the peculiarities of this language! What has been done to
the inner workings of DOS I cannot even guess at, there is a whole new
world to be discovered. But BPTR's and BSTR's are still abundant. Ah well,
you gets what yer given and makes the most of it!

 If you have read the file on exec lists, which are consistent throught most
of the Amigas libraries ( ie, the same list structure is used by Intuition,
Graphics etc ) it's time for a change! DOS uses it's own form of list.

 Before explaining DOS lists, I will first introduce two other peculiarities
of the DOS library. 'B' pointers ( BPTR ) and 'B' strings. It appears that,
for some reason beyond the comprehension of mortal man, AmigaDos was written
in BCPL. This language, though similar to C, uses a different type of pointer
and a different string format ( no longer beyond our understanding!, see
DE's docs on this disk ).

 To recap, a 'C' pointer is a long word address. The data required can be
found at this address. A 'C' string is a set of consecutive characters
terminated by a NULL ( 0 ) byte.

 A 'B' pointer is a long word pointer, that is to say BCPL considers memory
to be made up of long word and not bytes. This is best visualised by looking
at a list of BCPL memory addresses and 'real' memory addresses:

	BCPL				Real ( 'C' or Assembly )

	0000					00000000
	0001					00000004
	0002					00000008
	0003					0000000C
	0004					00000010

 So, to convert a 'B' pointer into a useable address multiply it by 4. In
assembly language, this can be achieved by shifting the long word 'B'
pointer left two bits:

; d0 holds a BPTR, we want a real address in register a0.

		asl.l		#2,d0		convert BPTR
		move.l		d0,a0		into address register

 ( Dave, I don't have instruction timings to hand! Is this slower than
   using 'adda.l An,An' twice? )

 Many DOS structures contain BPTR's and it will be necessary to do this
conversion before using the pointers in an assembly language program.

 A 'B' string is a set of characters, up to a maximum of 255 in length. The
first byte in a BSTR is the length of the following string. The strings are
not necessarily NULL terminated and you should never make this assumption.

 Again an example should help. The string 'Mark Meany' is shown below as a
'C' string and as a 'B' string:

	BSTR
 	dc.b		10,'Mark Meany'

	CSTR
	dc.b		'Mark Meany',0

 You may have to copy a BSTR into a buffer and NULL terminate it if you need
to use it in conjunction with Intuition or the Console devive. Notice that
the DOS library function Write() is idealy suited for printing BSTR's, this
fact is made use of in the start.i file. There are two routines available:

	BPrint		Prints the BSTR pointed to by a0.
	BPrintNL	Calls BPrint then prints a line feed.

 If using start.i, include it at the start (???) of your program. Your
program should start at the label 'Main'. Other subroutines available from
start.i are:

	Print		Prints null terminated string to CLI
	PrintNL		calls Print then prints a line feed

	< Arrrg!!! system failure, second time through again! > 
	< I want to kill the programmer responsible for the   >
	< yellow 'Software Error' type Guru present on v2.0   >
	< machines. It states the error is not fatal and is   >
	< followed by it's big brother, the red Guru, that    >
	< says it is and resets the machine. V1.3, all is     >
	< forgiven, at least I wasn't lured into a false      >
	< sense of security.				      >

 That's BSTR's and BPTR's out of the way, time to move on to a DOS list.

 The Device List
 ~~~~~~~~~~~~~~~
 The device list consists of structures linked together in one direction
only. The first long word of each structure in the list contains a BPTR to
the next structure in the list. The last structure in the list has a NULL
in this field to signal the end of the list.

 An entry in the device list can represent one of three objects:

	1/ A device ( RAW:, PAR:, CON: etc ).
	2/ An assigned device ( C:, libs:, t: etc ).
	3/ A Disk.

 The structure for each is slightly different, but I wont concern you with
that here. Take a look in the 'libraries/dosextens.i' include file for more
information. For the purpose of this tutorial, the generic structure will
be used. This can be applied to any entry in the list, here is a listing of
the structure:

 STRUCTURE DosList,0
    BPTR	dol_Next		; bptr to next device on lis
    LONG	dol_Type		; see DLT below
    APTR	dol_Task		; ptr to handler task
    BPTR	dol_Lock

    STRUCT	dol_VolumeDate,0	; creation date (UNION)
    STRUCT	dol_AssignName,0	; name for assign path (UNION)
    BSTR	dol_Handler		; file name to load if seglist is null
    STRUCT	dol_List,0		; List of directories assigned (UNION)
    LONG	dol_StackSize		; stacksize to use when starting process
    LONG	dol_Priority		; task priority when starting process

    STRUCT	dol_LockList,0		; outstanding locks (UNION)
    ULONG	dol_Startup		; startup msg: FileSysStartupMsg
					; for disks

    STRUCT	dol_DiskType,0		; 'DOS', etc (UNION)
    BPTR	dol_SegList		; already loaded code for new task

    BPTR	dol_GlobVec		; BCPL global vector

    BSTR	dol_Name		; bptr to bcpl name
    LABEL	DosList_SIZEOF

 The fields to take note of are:

	1/ dol_Next	used to step through the list.
	2/ dol_Type	used to identify entry ( see below ).
	3/ dol_Name	name of entry.

 From this list of structures, details of all recognised devices, disks and
assigned paths can be obtained. Ideal for filerequester or DiskMaster type
utilities.

 The type of an entry is specified by one of the following values:

* definitions for dl_Type
DLT_DEVICE	EQU	0
DLT_DIRECTORY	EQU	1	; assign
DLT_VOLUME	EQU	2
DLT_LATE	EQU	3	; late-binding assign
DLT_NONBINDING	EQU	4	; non-binding assign (AssignPath)
DLT_PRIVATE	EQU	-1	; for internal use only

 To step through the device list, the first structure in the list must be
located. This is not as easy as it sounds. The process requires obtaining
apointer from DOSBase to a RootNode structure. From the RootNode structure
a BPTR is obtained to a DosInfo structure. The DosInfo structure contains a
BPTR to the first entry in the device list.

 I will present each of the above mentioned structures and show the path
taken to arrive at the first entry in the device list:

*DosLibrary
dl_lib			Struct	Library
dl_Root			LONG --------->---------+
dl_GV			LONG			|
dl_A2			LONG			|
dl_A5			LONG			|
dl_A6			LONG			|
						|
	+-------<---------------------<---------+
	|
	V

*RootNode
rn_TaskArray		BPTR
rn_ConsoleSegment	BPTR
rn_Time			Struct	DateStamp
rn_RestartSeg		LONG
rn_Info			BPTR ---------->--------+
rn_FileHandlerSegment	BPTR			|
						|
	+-------<----------------------<--------+
	|
	V

*DosInfo
di_McName		BPTR
di_DevInfo		BPTR ---------->--------+
di_Devices		BPTR			|
di_Handlers		BPTR			|
di_NetHand		LONG			|
						|
	+--------<---------------------<--------+
	|
	V
   FIRST ENTRY IN
    DEVICE LIST

 This can be achieved in assembly language as follows:

		*************************
		move.l		_DOSBase,a6		a6->lib base struct
		move.l		dl_Root(a6),a0		a0->Root Node
		move.l		rn_Info(a0),d0		d0=BPTR
		asl.l		#2,d0			convert
		move.l		d0,a0			a0->DosInfo
		move.l		di_DevInfo(a0),d0	d0=BPTR
		asl.l		#2,d0			convert
		move.l		d0,a4			a4->device list
		*************************

 The following example steps through the list and displays each entries
type and name as it goes. Notice how the dol_Next pointer has to be converted
into a correct pointer in order to step through the list.

		*************************

		include		start.i

; Locate start of device list

Main		move.l		_DOSBase,a6		a6->lib base struct
		move.l		dl_Root(a6),a0		a0->Root Node
		move.l		rn_Info(a0),d0		d0=BPTR
		asl.l		#2,d0			convert
		move.l		d0,a0			a0->DosInfo
		move.l		di_DevInfo(a0),d0	d0=BPTR
		asl.l		#2,d0			convert
		move.l		d0,a4			a4->device list

; Print this entries type

Loop		move.l		dol_Type(a4),d0		d0=Type
		bmi.s		Next			skip if private
		asl.l		#2,d0			get vector offset
		lea		TypeTable,a0		a0->vector table
		move.l		0(a0,d0),a0		a0->type
		bsr		Print			display it

; Print this entries name

		move.l		dol_Name(a4),d0		BPTR
		asl.l		#2,d0			convert
		move.l		d0,a0			a0->BSTR
		bsr		BPrintNL		print it

; Step on to next entry

Next		move.l		dol_Next(a4),d0		step on
		beq		Done			exit if so
		asl.l		#2,d0			convert BPTR
		move.l		d0,a4
		bra		Loop

; All entries processed, so exit.

Done		rts					exit



TypeTable	dc.l		isDevice
		dc.l		isDirectory
		dc.l		isVolume
		dc.l		isLate
		dc.l		isNBinding

isDevice	dc.b		'<DEVICE> ',0
		even
isDirectory	dc.b		'<ASSIGN> ',0
		even
isVolume	dc.b		'<DISK>   ',0
		even
isLate		dc.b		'<LATE>   ',0
		even
isNBinding	dc.b		'<NON B>  ',0
		even

		*************************

 There is a second example on the disk that filters as it steps so that
only disks and assigned devices get displayed. This is such a small
variation that I will not include it in this text.

 File Requester Routines
 ~~~~~~~~~~~~~~~~~~~~~~~
 Just about all file requesters have a means of allowing you to see all the
available volumes. Here I will present two routines that can be used to
this end.

 The first routine generates a double linked 'exec' type list. Each entry
in this list contains a pointer to the name of a recognised disk or assign
and the type of the entry. The list is built up as the device list is
scanned. Whenever an entry of the desired type is found in the device list,
memory is allocated for a node structure and a copy of the name. This node
is then added to the tail of the double linked list. The address of the
list header is returned in register d0.

 I have opted for storing the device entries type in the LN_PRI field of
the linked list. The reason for this is to save having to use an extended
node structure. The LN_PRI field serves no other purpose in this list!

 The second routine frees all the memory occupied by the linked list.

 I will present a written outline of each routine, broken down into stages,
followed by the routine itself.

 GetVolList
 ~~~~~~~~~~
	1/ Save registers.
	2/ Allocate memory for list header and initialise it.
	3/ Locate start of the device list.
	4/ Check entry is of required type, branch to step   if not.
	5/ Allocate memory for a node structure.
	6/ Allocate memory for a copy of the name + NULL terminator.
	7/ Copy name into allocated memory.
	8/ Set nodes LN_NAME to point to the copy of the name.
	9/ Set nodes LN_PRI equal to the entries type.
	A/ Add the node to end of list.
	B/ Step on to next device list entry, branch to step D if no more.
	C/ Branch back to step   to process this entry.
	D/ Get address of list header into register d0.
	E/ Restore registers.
	F/ Exit.

		*************************
GetVolList	movem.l		d1-d7/a0-a6,-(sp)	save registers

; Allocate memory for list header and initialise it.

		moveq.l		#LH_SIZE,d0		size
		move.l		#MEMF_CLEAR,d1		type
		CALLEXEC	AllocMem		get memory
		tst.l		d0			ok?
		beq		.QuitFast		exit now if not!

		move.l		d0,a5			save in safe reg
		move.l		d0,a0			initialise header
		NEWLIST		a0

; Locate the start of the Device list.

		move.l		_DOSBase,a6
		move.l		dl_Root(a6),a0		a0->Root Node
		move.l		rn_Info(a0),d0		d0=BPTR
		asl.l		#2,d0			convert
		move.l		d0,a0			a0->DosInfo
		move.l		di_DevInfo(a0),d0	d0=BPTR
		asl.l		#2,d0			convert
		move.l		d0,a4			a4->Device list

; Check entry is of required type, skip it if not.

.Loop		move.l		dol_Type(a4),d4		d4=Type
		cmp.l		#1,d4			min value
		blt		.Next			skip if lower
		cmp.l		#2,d4			max value
		bgt.s		.Next			skip if higher

; Allocate memory for node.

		moveq.l		#LN_SIZE,d0		size
		move.l		#MEMF_CLEAR,d1		type
		CALLEXEC	AllocMem		get memory
		tst.l		d0			ok?
		beq.s		.error			exit if not
		move.l		d0,a3			keep safe

; Allocate memory for copy of name.

		move.l		dol_Name(a4),d0		BPTR
		asl.l		#2,d0			convert
		move.l		d0,a2			a2->name (BSTR)
		moveq.l		#0,d0			clear
		move.b		(a2)+,d0		d0=name length
		addq.l		#1,d0			+1 for  NULL
		move.l		#MEMF_CLEAR,d1		type
		CALLEXEC	AllocMem		get memory
		move.l		d0,d7			keep safe
		bne.s		.GotAllMem		branch if allocated
		
		;error handler. If no mem for name, release node and quit!
		
		move.l		a3,a1			node address
		moveq.l		#LN_SIZE,d0		node size
		CALLEXEC	FreeMem			release it
		bra.s		.error			and exit

; Copy name into allocated memory.

.GotAllMem	move.l		a2,a0			source
		move.l		d7,a1			destination
		moveq.l		#0,d0			clear
		move.b		-1(a2),d0		size
		CALLEXEC	CopyMem			copy name

; Link name to node.

		move.l		d7,LN_NAME(a3)		store name pointer

; Copy entry type into priority field.

		move.l		dol_Type(a4),d0		Type
		move.b		d0,LN_PRI(a3)		into node struct

; Add node to end of list.

		move.l		a5,a0			header
		move.l		a3,a1			node
		ADDTAIL					add it!

; Step on to next entry

.Next		move.l		(a4),d0			step on
		beq.s		.error			exit if so
		asl.l		#2,d0			convert BPTR
		move.l		d0,a4
		bra		.Loop

; Address of header into d0.

.error		move.l		a5,d0			header

; All entries processed, so exit.

.QuitFast	movem.l		(sp)+,d1-d7/a0-a6	restore
		rts					exit

		*************************

 FreeVolList
 ~~~~~~~~~~~
	1/ Save registers.
	2/ Get address of next nodes name, branch to step 5 if last node.
	3/ Free memory used to hold name.
	4/ Branch back to step 2.
	5/ Remove node from head of list, branch to step 8 if no more nodes.
	6/ Free memory used for removed node.
	7/ branch back to step 5.
	8/ Free memory used for list header.
	9/ Restore registers.
	A/ Exit.

		*************************

FreeVolList	movem.l		d0-d7/a0-a6,-(sp)	save

		move.l		a0,a4			a4->header
		move.l		a0,a3

; Get address of next node in list.

.NameLoop	TSTNODE		a3,a3			a3->next node
		beq.s		.NamesDone		branch if at tail

; Get address of nodes name.

		move.l		LN_NAME(a3),a0		a0->Name
		move.l		a0,a1			copy

; Determine length of name.

		moveq.l		#0,d0			length
.LenLoop	addq.l		#1,d0			bump counter
		tst.b		(a0)+			EOS?
		bne.s		.LenLoop		branch if not

; Release memory used for name and loop back.

		CALLEXEC	FreeMem			release it
		bra.s		.NameLoop		branch
		
; Remove next node from start of list.

.NamesDone	move.l		a4,a0			a0->list header
		CALLEXEC	RemHead			remove 1st node
		tst.l		d0			at tail?
		beq.s		.DoneNodes		branch if so.

; Release memory used for removed node.

		move.l		d0,a1			a1->mem
		moveq.l		#LN_SIZE,d0		size
		CALLEXEC	FreeMem			free it
		bra.s		.NamesDone		and loop

; Finally, release the header's memory.

.DoneNodes	move.l		a4,a1			a1->mem
		moveq.l		#LH_SIZE,d0		size
		CALLEXEC	FreeMem			free it

; All memory released, so exit.

.error		movem.l		(sp)+,d0-d7/a0-a6	restore
		rts

		*************************

 To see these routines in operation I have supplied a test routine on this
disk called 'RequesterSubs_Test.s'. Assemble and run it.

 Well that's about it on DOS lists, unless you are thinking of writing your
own device driver ........

				MM.
