Amiga Machine Code Letter X - Files
This is the second part, of a multi-part series about the system libraries. The previous post was about memory allocation.
We are still looking at Letter X of the Amiga Machine Code course, and as always, make sure to read the letter, since I won’t go through all the details.
Reading files
Reading files from an Amiga disk is pretty easy, when you use the DOS library. For the newbie there’s just one problem, how do we open that library?
The systems master library is Exec and it’s always open, and can be found at at memory address $\$4$. The Exec library is responsible for keeping track of all libraries and opening and closing them. To do that, the Exec library contains a function called OpenLibrary, that allows us to fetch the base pointer of the library we want to open.
The following illustration is from Amiga ROM Kernel Reference Manual: Libraries, 3rd edition.
The reasen that the other libraries doesn’t have fixed addresses like the Exec library is because the Amiga OS is allowed to load them into random memory locations. The one exception being the base pointer to the Exec library. The story have to start somewhere, as they say.
Here’s some documentation for OpenLibrary, from the book Mapping The Amiga.
OpenLibrary
Description: gains access to a library
Library: exec.library
Offset: -$198 (-408)
Syntax: library = OpenLibrary(libName, version)
ML: d0 = OpenLibrary(a1, d0)
Arguments: libName = the name of the library to open
Result: library = the library base address; zero if unsuccessful
A point of frustration, is that I can’t find documentation (autodocs and includes) for Kickstart 1.3. However, I have found the documentation for Kickstart 2.0 at amigadev.elowar.com. Others are having the same problem 😉.
You might want to take a look at OpenLibrary in the autodocs, since there’s more documentation. Just remember that kickstart 1.3 has System Library Version Number 33.
In the autodoc, you can read this:
All calls to OpenLibrary should have matching calls to CloseLibrary!
The matching call to CloseLibarary is needed, so that the system can reclaim the memory, when nobody is using the library. When OpenLibrary is called, it loads the library and setup it’s jump table, so that the offsets will point to the right memory locations. It then calls the Open method of the library, that will increment an open count. This sounds like simple reference counting that’s also used in e.g. COM.
As we shall see later, in the mc1002 program example, found on disk1, the call to CloseLibrary seems to be omitted. It’s also not mentioned in Letter X, which is a bit puzzling… 😐
The mc1002 program demonstrates how to read from a file into a buffer. Before we start, we should take a look at the following functions from the DOS library that we are going to need, when reading a file from disk.
To open a file, we need to use the Open method and provide a file name and access mode. See the autodoc here.
Open
Description: opens a file for input or output
Library: dos.library
Offset: -$1E (-30)
Syntax: fh = Open(name, accessMode)
ML: d0 = Open(d1, d2)
Arguments: name = filename of file to open
accessMode = type of file access desired
MODE_READWRITE $000003EC
MODE_OLDFILE $000003ED
MODE_NEWFILE $000003EE
Result: fh = filehandle of open file; zero if unsuccessful
To read the contents of the file, we need to call the Read method and provide a file handle, an input buffer, and a length. See the autodoc here).
Open
Description: reads bytes of data from a file
Library: dos.library
Offset: -$2A (-42)
Syntax: actualLength = Read(fh, buffer, length)
ML: d0 = Read(d1, d2, d3)
Arguments: fh = filehandle of file from which to read
buffer = input buffer to receive data
length = number of bytes to read; may not exceed buffer size
Result: actualLength = actual number of bytes read
If you “own” the file handle, then you can call Close on it. A file handle must only be closed once! See the autodoc here.
Close
Description: closes an open file
Library: dos.library
Offset: -$24 (-36)
Syntax: success = Close(fh)
ML: d0 = Close(d1)
Arguments: fh = filehandle of file to close
Result: success = zero if unsuccessful (return value valid only in Revision 2.0)
Heres the code listing for the mc1002 program that can be found on disk1 of the machine code course. I’ve added my comments to the listing.
The program reads a file called “Testfil” and then exits. Not much eh? When the program has run, take a look at the buffer, by using the Seka command line
SEKA>qbuffer
Voila, it will reveal the contents of “Testfil” - and I’m not telling what it is 😜
move.l #24,d0 ; move 24 into d0 (length)
lea.l filename,a0 ; move address of filename into a0
lea.l buffer,a1 ; move address of buffer into a1
bsr readfile ; branch to subroutine readfile
cmp.l #0,d0 ; check if value of d0 is zero
beq error ; if d0 is zero then goto error
rts ; return from subroutine
error:
rts ; return from subroutine
filename:
dc.b "Testfil",0 ; the filename terminated by a zero
buffer:
blk.b 50,0 ; allocate 50 bytes of buffer
readfile:
movem.l d1-d7/a0-a6,-(a7) ; push register values onto the stack
move.l a0,a4 ; move a0 into a4
move.l a1,a5 ; move a1 into a5
move.l d0,d5 ; move d0 into d5
move.l $4,a6 ; move base pointer of exec.library into a6
lea.l r_dosname,a1 ; move pointer to library name into a1
jsr -408(a6) ; call OpenLibrary in the exec.library. d0 = OpenLibrary(a1,d0)
move.l d0,a6 ; move base pointer to dos.library into a6
move.l #1005,d2 ; move 1005 into d2 (accessMode = MODE_OLDFILE)
move.l a4,d1 ; move a4 into d1 (name of filename to open)
jsr -30(a6) ; call Open in dos.library. d0 = Open(d1,d2)
cmp.l #0,d0 ; compare value of d0 with zero
beq r_error ; if d0 is zero goto r_error
move.l d0,d1 ; move d0 into d1 (filehandle)
move.l d0,d7 ; move d0 into d7
move.l a5,d2 ; move a5 into d2 (buffer)
move.l d5,d3 ; move d5 into d3 (length)
jsr -42(a6) ; call Read in dos.library. d0 = Read(d1,d2,d3)
move.l d7,d1 ; move d7 into d1 (filehandle)
move.l d0,d7 ; move d0 into d7 (number of bytes read)
jsr -36(a6) ; call Close in dos.library. d0 = Close(d1)
move.l d7,d0 ; move d7 into d0
movem.l (a7)+,d1-d7/a0-a6 ; pop values from the stack into the registers
rts ; return from subroutine
r_error: ; handle read error
clr.l d0 ; clear d0
movem.l (a7)+,d1-d7/a0-a6 ; pop values from the stack into the registers
rts ; return from subroutine
r_dosname:
dc.b "dos.library",0 ; library name terminated by zero
Here’s the documentation for the above readfile subroutine.
readfile
Description: reads a file
Syntax: actualLength = readfile(name, buffer, length)
ML: d0 = readfile(a0, a1, d0)
Arguments: name = filename of the file to read
buffer = input buffer to receive data
length = number of bytes to read
Result: actualLength = actual number of bytes read
Comments on the Readfile Subroutine
I think there is a potential problem with the readfile subroutine, with regard to how the length in d0 is handled.
When the mc1002 program sets the length to 24 bytes and stores the value in d0, then that d0 still contains 24 when we call OpenLibrary, where it’s used as a requested library version number. I don’t think this is intentional. From the documentation:
If the requested library is exists, and if the library version is greater than or equal to the requested version, then the open will succeed.
In mc1002 the call to OpenLibrary goes well, because we only requested 24 bytes from readfile, which uses this number to open the DOS libarary with a requested library version of 24. This goes well, because there are versions of the DOS library with higher version numbers.
However, what if we called the readfile subroutine and requested 64 bytes? There is no DOS library currently with a version number higher than, or equal to 64, and the result would be a failure to load the library.
I altered the mc1002 program, so that we requests a version of 64, and ran it against kickstart 1.3. No problems whatsoever - OpenLibrary happily did return a pointer to the DOS Library(!?). It’s almost as if the version number is ignored… Well, sadly I don’t have time to investigate this.
Another potential problem with the readfile subroutine, is that we do not call OpenLibrary with a matching CloseLibrary. This is a problem, since it will ruin the reference counting inside the library, and cause memory leaks.
Ok, now we can read files, so let’s move on 😃.
Writting Files
It turns out that writting files is pretty easy too - provided that you use the DOS library. The library contains a function called Write, which seems like an appropriate name for writting files. Here’s the autodoc documentation for it.
Write
Description: writes bytes of data to a file
Library: dos.library
Offset: -$30 (-48)
Syntax: actualLength = Write(fh, buffer, length)
ML: d0 = Write(d1, d2, d3)
Arguments: fh = filehandle of file to write to
buffer = buffer containing data to write
length = number of bytes to write
Result: actualLength = number of bytes successfully written; -1 if unsuccessful
The mc1003 program demonstrates how to write a file. It can be found on disk1 and I’ve added some comments. The program is very simple indeed. It only writes a buffer to a file called “Testfil”, and that’s it.
move.l #24,d0 ; move 24 into d0 (length)
lea.l filename,a0 ; move filename address into a0
lea.l buffer,a1 ; move buffer address into a1
bsr writefile ; branch to subroutine writefile
cmp.l #0,d0 ; compare d0 with zero
bne error ; if d0 is zero goto error
rts ; return from subroutine
error: ; writefile error handling
rts ; return from subroutine
filename:
dc.b "Testfil",0 ; filename terminated by zero
buffer:
dc.b "Hallo, dette er en test!" ; contents of the buffer
writefile: ; writefile subroutine
movem.l d1-d7/a0-a6,-(a7) ; push register values onto the stack
move.l a0,a4 ; move a0 into a4 (filename)
move.l a1,a5 ; move a1 into a2 (buffer)
move.l d0,d5 ; move d0 into d5 (length)
move.l $4,a6 ; move base pointer of exec.library into a6
lea.l w_dosname,a1 ; move pointer to library name into a1
jsr -408(a6) ; call OpenLibrary in the exec.library. d0 = OpenLibrary(a1,d0)
move.l d0,a6 ; move base pointer to dos.library into a6
move.l #1006,d2 ; move 1006 into d2 (accessMode = MODE_NEWFILE)
move.l a4,d1 ; move a4 into d1 (name of filename to open)
jsr -30(a6) ; call Open in dos.library. d0 = Open(d1,d2)
cmp.l #0,d0 ; compare value of d0 with zero
beq w_error ; if d0 is zero goto w_error
move.l d0,d1 ; move d0 into d1 (filehandle)
move.l d0,d7 ; move d0 into d7
move.l a5,d2 ; move a5 into d2 (buffer)
move.l d5,d3 ; move d5 into d3 (length)
jsr -48(a6) ; call Write in dos.library. d0 = Write(d1,d2,d3)
move.l d7,d1 ; move d7 into d1 (filehandle)
move.l d0,d7 ; move d0 into d7 (actualLength from Write)
jsr -36(a6) ; call Close in dos.library. d0 = Close(d1)
move.l d7,d0 ; move d7 into d0 (actualLength)
movem.l (a7)+,d1-d7/a0-a6 ; pop values from the stack into the registers
rts ; return from subroutine
w_error: ; write error handling
clr.l d0 ; clear d0
movem.l (a7)+,d1-d7/a0-a6 ; pop values from the stack into the registers
rts ; return from subroutine
w_dosname:
dc.b "dos.library",0 ; library name terminated by zero
The writefile subroutine suffers from the same problems as the readfile subroutine described earlier. Especially the call to CloseLibrary is missing, which will cause a memory leak.
In the next post, we are going to look at how to handle I/O between a program and the command line interface CLI. Stay tuned! 😃
Previous post: Amiga Machine Code Letter X - Memory.
Next post: Amiga Machine Code Letter X - CLI.