From the pages by Lee Davison.
6502 ROM file system
This is a simple ROMable file system for 6502 based computers. It was designed to hold all the web content for my Vic 20 web server and so only has the features needed to support that.
Requirements.
As it’s intended for use in embedded type applications the filesystem needs to be small and require few system resources. The system presented here uses only four bytes, apart from the filename to describe each file and directory. The routine to find a file needs only eleven bytes of RAM, apart from the filename to be searched for, only six of which need to be in page zero and none of which need to be maintained between calls.
File system description.
Files and directories are basically the same with just one bit changed in the header to tell them apart. There are no blocks or block mapping, no file dates or times, there aren’t even the pointers to reverse traverse the file tree, though they could fairly easily be added.
Files consist of a header and the payload. The header is from four to 259 bytes long and is made up from a flag byte, a length word and a null terminated filename. File names are case sensitive, can be up to 255 characters long and can include most characters, only “/” and $00 are expressly forbidden.
The payload is just binary data and can be any size, including zero bytes.
A directory is the same in form as a file except b6 in the flag byte is set. The payload is a list of word size pointers to the headers for each of the files and directories within the directory.
There are no end markers or guard bytes or any special way of marking the start and end of each file. The only point that must be known is the start of the root directory from which the pointers to all other files and directories can be found. Files do not need to be in contiguous memory and can be placed as and where is convenient.
The header.
The header can be as short as four and as long as 259 bytes, this is because the filename is variable in length and can be from zero to 255 bytes long (excluding the null treminator byte)
f_start .ds 1 ; flags f_length .ds 2 ; file payload length f_name .ds n+1 ; filename and null terminator
The first byte in the header, the flag byte, only has two bits defined and of those only one is used at present. b7 is the deleted bit, if this bit is set then this file or directory has been deleted. b6 is used to distinguish a directory file from a non directory file, if this bit is set then this file is a directory entry and as such is treated slightly differently by the filesystem.
bit function --- -------- 7 deleted 6 directory 5 unused 4 unused 3 unused 2 unused 1 unused 0 unused
The header flag byte has only b7 and b6 defined, all other bits are available for any use such as hidden, system, executable, read-only or archive bits.
The deleted flag is not used as such and, with a small code change, could also be assigned to other uses.
The second and third bytes in the header make up the file payload length word, this is length of the file’s contents. The file payload length is limited only by the space available but in practice will usually be small enough to fit in a single ethernet packet.
The fourth byte onwards of the header is the null terminated filename. There are few restrictions on the name aside from the maximum length which, excluding terminator, is 255 bytes and the character “/” and the byte $00 are not allowed. No other characters are expressly forbidden, but the use of non-printing characters is not recommended.
Directories.
Directories are distinguished from files only by the state of the directory bit in the flag byte. This means no special code is needed to search for directories instead of files as, during searching, both are treated the same. The down side to this is that you can’t have a file and a directory with the same name, only the first listed in the directory would ever be found.
; the root file should always be a directory root .byte directory ; flags .word root_end-root_b ; file payload length .byte $00 ; filename, null here root_b .word errors ; errors directory .word images ; images directory ; directories done, do files .word b_html ; the button page .word index_html ; default web page .word o_html ; other web page root_end
The payload in a directory is a list of absolute word pointers pointing to the start of each of the files and directories to which the current directory is the parent. No end marker is needed as the end can be dreived from the length so there is no restriction on the location of files. Though the entries in the example above have been arranged in directory then file alphabetic order this is not a requirement and any order is as valid as any other. No significance is placed on file ordering except where duplicate filenames are concerned. In this case only the first occurance of the duplicate filename will ever be found in a search.
The root directory.
The root directory is exactly the same in form as any other directory, what makes it special is that it is not pointed to by any pointer in any other directory and so it’s location can only be refered to explicitly. For this reason a name is not needed, it can never be searched for, but could be used as a volume name or distinguishing information in a system where more than one volume may exist or the volume is held in removable media – a flashcard for example.
If the root is always held in the same relative place it makes handling multiple or removable volumes much easier.
Files.
The header of a file is the same as that of a directory except the directory bit in the flag byte is clear. The payload is just binary data and there is no end-of-file marker needed as the end of file can be derived from the length.
Because of this there is no restriction on what the payload may contain and there is no significance in the filename or it’s extension (if any). All file payloads are of equal significance, i.e. none, as far as the filesystem is concerned.
; small image, served for "GET /email.gif" e_gif .byte none ; flags .word e_gif_end-e_gif_b ; file payload length .byte "e.gif",$00 ; filename, null terminator ; rest is file payload e_gif_b .byte "HTTP/1.0 200 OK",$0D,$0A,"Content-Type: image/gif",$0D,$0A .byte $0D,$0A,$47,$49,$46,$38,$39,$61,$10,$00,$10,$00,$C4,$00,$00 .byte $F7,$00,$5F,$33,$03,$9A,$33,$03,$CA,$03,$03,$83,$5F,$5F,$F7 .byte $33,$63,$FA,$63,$9B,$FB,$63,$CB,$FB,$03,$9B,$CB,$63,$FA,$FA .byte $FA,$FA,$CB,$97,$00,$00,$CB,$03,$03,$FA,$FA,$FA,$63,$63,$63 .byte $03,$03,$03,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$21,$F9,$04,$01,$00,$00,$10,$00,$2C .byte $00,$00,$00,$00,$10,$00,$10,$00,$00,$05,$69,$20,$24,$8E,$C6 .byte $91,$24,$63,$9A,$96,$49,$11,$18,$AA,$28,$40,$85,$59,$40,$46 .byte $A0,$3A,$8E,$20,$20,$07,$01,$6F,$38,$7C,$38,$1A,$3F,$84,$40 .byte $C1,$6C,$36,$8D,$C8,$02,$62,$E0,$64,$02,$18,$0A,$E8,$60,$BB .byte $AD,$32,$16,$D9,$63,$A3,$51,$2D,$43,$C9,$E5,$EA,$B9,$2C,$28 .byte $B8,$A9,$D0,$B4,$42,$70,$82,$3B,$E4,$F3,$7A,$F8,$C1,$EF,$F3 .byte $05,$26,$5B,$7C,$2A,$2F,$10,$02,$25,$37,$03,$31,$10,$04,$01 .byte $05,$06,$06,$37,$8B,$23,$01,$03,$6D,$31,$21,$00,$3B e_gif_end
Software.
The only software provided is a routine to search the file system tree for a particular file. The filename to search for should be the complete path from the root, start with a “/” character and be null terminated. Directories are separated with the “/” character. As this code is intended for a small web server there is the feature that any search terminating in a directory will automatically append “index.html” to the string and search that filename.
Source.
The code presented only allows searching of the file tree and there is no code to add or delete files or directories.
You can either download the source as zipped text or view it as html.
Expansion.
If 64 kilobytes, the limit for this system as it stands, is not enough the filesystem can quite easily be expanded by extending the directory pointers and payload length to values big enough to handle the required size. With 32 bit words up to 4GB of paged or externally stored data could be handled with a minimal increase in code complexity, more than enough for most 8 bit microprocessor systems.
; constants for the file system none = $00 ; no flags ; flags: deleted = $80 ; file is deleted but not removed directory = $40 ; file is really a directory ; variables for the file system ; zero page filesys_l .ds 1 ; file system pointer low byte filesys_h .ds 1 ; file system pointer high byte lensys_l .ds 1 ; file system length low byte lensys_h .ds 1 ; file system length high byte file_l .ds 1 ; current file pointer low byte file_h .ds 1 ; current file pointer high byte length_l .ds 1 ; current file length low byte length_h .ds 1 ; current file length high byte fileflags .ds 1 ; current file's flag byte findname_l .ds 1 ; filename to find pointer low byte findname_h .ds 1 ; filename to find pointer high byte
filesys is the pointer returned by the find routine, it is only valid if a file was found, i.e. Cb is = 1.
lensys is the length of the returned file and like filesys is only valid if a file was found.
file and length are used internally by the find routine.
fileflags is used internally by the find routine.
findname points to the start of the null yerminated path/name of the file to find. Like the HTTP “GET” it must start with “/” and “/” is used to separate directories so “/”,$00 is valid as is “/index.html”,$00 but “o.html”,$00 isn’t.
; file format is .. f_start .ds 1 ; flag byte ; bit function ; --- -------- ; 7 deleted ; 6 directory ; 5-0 unassigned f_length .ds 2 ; file payload length word f_name .ds 1 ; filename and null terminator byte ; .ds length ; here to here+length-1 is the payload
This is used to define the header, f_name is used realtive to f_start and will remain correct even if other parameters, such as a parent pointer for reverse tree searches, are added to the header.
; find file, searches the file system for the filename pointed to by the find ; name pointer. returns with Cb = 1 and the pointer to the file payload in ; filesys, if found. ; ; file name to search for should be "/[path[path]][name]" where path is the ; directory name followed by a "/". the path may be many deep and both the ; path and the name may be null. if the name is null then the default_file ; name will be searched for the path given. LAB_find LDA root+1 ; get root payload length low byte STA lensys_l ; save payload length low byte LDA root+2 ; get root payload length high byte STA lensys_h ; save payload length high byte LDA #<root_b ; get root body pointer low byte STA filesys_l ; set file system pointer low byte LDA #>root_b ; get body pointer high byte STA filesys_h ; set file system pointer high byte LDY #$00 ; clear index LDA (findname_l),Y ; get the first byte of the find name CMP #'/' ; compare with separator BNE LAB_exit_n_found ; exit if not "/" at start JSR LAB_directory ; search the root for the file BIT fileflags ; get the flags for this file BVC LAB_exit_find ; exit if it's a file JSR LAB_find_default ; a directory so find the default file LAB_exit_find RTS
The routine above returns either a found file pointer and it’s length or flags not found. If the match turns out to be a directory, e.g. “/images”, then the search routine is re-entered at the find default file entry so the file searched becomes effectively “/images/index.html”
; flag file found and exit LAB_exit_found BIT fileflags ; test the flags, set Vb for a directory SEC ; flag found RTS ; flag file not found and exit LAB_exit_n_foun CLV ; flag not a directory CLC ; flag not found RTS ; increment filesys to the next file pointer in the directory LAB_nextfile CLC ; clear carry for add LDA filesys_l ; get filesys low byte ADC #$02 ; increment to next pointer STA filesys_l ; save filesys low byte BCC ni_inc_h ; branch if no rollover INC filesys_h ; else increment filesys high byte ni_inc_h SEC ; set carry for subtract LDA lensys_l ; get remaining length low byte SBC #$02 ; increment to next pointer STA lensys_l ; save remaining length low byte BCS LAB_comparefile ; branch if no rollunder DEC lensys_h ; decerment remaining length high byte LAB_comparefile LDA lensys_l ; get remaining length low byte ORA lensys_h ; OR remaining length high byte BEQ LAB_exit_n_found ; exit if no more directory entries LDY #$00 ; clear index LDA (filesys_l),Y ; get file pointer low byte STA file_l ; save this file pointer low byte INY ; increment index LDA (filesys_l),Y ; get file pointer high byte STA file_h ; save this file pointer high byte DEY ; clear index LDA (file_l),Y ; get the file's flags STA fileflags ; save the file's flag byte INY ; point to payload length low byte LDA (file_l),Y ; get file's payload length low byte STA length_l ; save file's length low byte INY ; point to payload length high byte LDA (file_l),Y ; get file's payload length high byte STA length_h ; save file's length high byte CLC ; clear carry for add LDA file_l ; get this file pointer low byte ADC #f_name-f_start ; add offset to the file name STA file_l ; save this file pointer low byte BCC nf_inc_h ; branch if no rollover INC file_h ; increment this file pointer high byte nf_inc_h LDY #$FF ; set so first increment clears index ; compare this file's name, pointed to by file, with the find file ; name pointed to by findname. exits with Y indexed to the next byte ; in the name if the whole name matched. LAB_comparename INY ; increment index LDA (file_l),Y ; get next byte of name to test BEQ LAB_cnameexit ; exit if end of name (match) EOR (findname_l),Y ; compare with next byte of name to find BEQ LAB_comparename ; loop if character match BNE LAB_nextfile ; branch if not this file LAB_cnameexit LDA (findname_l),Y ; get next byte of name to find BEQ LAB_end_find ; branch if end of name to find CMP #'/' ; compare with separator BNE LAB_nextfile ; branch if not end of name to find LAB_end_find PHA ; save next byte of name to find ; name matched so update lensys LDA length_l ; get length low byte STA lensys_l ; save length low byte LDA length_h ; get length high byte STA lensys_h ; save length high byte ; name matched so update filesys SEC ; set carry for add +1 TYA ; copy offset ADC file_l ; add this file pointer low byte STA filesys_l ; save as file system pointer low byte LDA file_h ; get this file pointer high byte ADC #$00 ; add in the carry STA filesys_h ; save as file system pointer high byte PLA ; restore next byte of name to find BEQ LAB_exit_found ; branch if end of name to find BIT fileflags ; else test the flags byte BVC LAB_exit_n_found ; branch if not a directory ; searches the directory pointed to by the file system pointer for the ; filename pointed to by the find name pointer. the routine is entered ; with filesys pointing to the first directory byte after the file header ; and lensys holding the remaining directory size LAB_directory SEC ; set carry for add +1 TYA ; copy offset LDY #$00 ; clear index ADC findname_l ; add offset to findname low byte STA findname_l ; save findname low byte TYA ; set $00 for add carry ADC findname_h ; add findname high byte STA findname_h ; save findname high byte LDA (findname_l),Y ; get next byte of name to find BNE LAB_comparefile ; go compare file if not null filename LAB_find_default LDA #<default_file ; get default filename pointer low byte STA findname_l ; set pointer of name to find low byte LDA #>default_file ; get default filename pointer high byte STA findname_h ; set pointer of name to find high byte BNE LAB_comparefile ; go compare file (branch always)
The routine above is the main file tree search routine. The path/name to find is compared byte at a time with each filename in turn until a file is found that is a complete match to it’s end. Once there the find string is tested and if it’s at a separator, “/”, a directory change is made (assuming the found file was a directory), the search then continues.
If the find string ends in “/” then the pointer is changed to point to the default file name “index.html” and this file is searched for in the target directory. So “/” as a find string will search for, and return, the pointer and size for “/index.html”, where as “/images/” as a find string will search for, and fail to find, “/images/index.html” as there is no index.html file in the images directory.
default_file .byte "index.html",$00 ; default filename for "/xxx/" finds
Finally the test filesystem files. There is one additional, zero payload length, file “index” which is to test that matches with partial names don’t trigger false matches.
; this is the test data for the filesystem. it is made up of the HTML ; pages and other files for the Vic 20 mini web server plus a dummy ; file to test the file match. ; structure for this trial is .. ; root+--errors+--404 ; error 404, really an html file ; | ; +--images+--e.gif ; small .gif image ; | ; +--b.html ; button page ; +--index ; dummy file for test ; +--index.html ; default page ; +--o.html ; other page ; ********************************************************************* ; the root file is always a directory root .byte directory ; flags .word root_end-root_b ; file payload length .byte $00 ; filename, null in this case root_b .word errors ; errors directory .word images ; images directory ; done directories, now do files .word b_html ; the button page .word index_html ; default web page .word o_html ; other web page root_end ; ********************************************************************* ; dummy file for test index .byte none ; flags .word $0000 ; file payload length .byte "index",$00 ; filename, null terminator ; ********************************************************************* ; default web page, served for "GET / " index_html .byte none ; flags .word index_end-index_html_b ; file payload length .byte "index.html",$00 ; filename, null terminator ; rest is file payload index_html_b .byte "HTTP/1.0 200 OK",$0D,$0A,"Content-Type: text/html" .byte $0D,$0A,$0D,$0A .byte "<HTML><HEAD><TITLE>Vic 20 WEB Server</TITLE>" .byte "</HEAD><BODY BGCOLOR=#CC6502 TEXT=WHITE>" .byte "<TABLE ALIGN=CENTER WIDTH=80% HEIGHT=90%>" .byte "<TR><TD VALIGN=CENTER>" .byte "<FONT SIZE=+1>Vic 20 WEB Server </FONT>" .byte "- <A HREF=b.html>The button page</A>" .byte " - <A HREF=o.html>The other page</A>" .byte "<P><FONT SIZE=+4><B>Welcome</B></FONT>" .byte "<FONT FACE=Courier> - to the default page</FONT>" .byte "<P>" .byte "<A HREF=mailto:leeedavison@lycos.co.uk> e-mail " .byte "me <IMG SRC=images/e.gif BORDER=0 ALT=e-mail></A>" .byte "</TD></TR></TABLE></BODY></HTML>" index_end ; ********************************************************************* ; other web page, served for "GET /o.html" o_html .byte none ; flags .word o_html_end-o_html_b ; file payload length .byte "o.html",$00 ; filename, null terminator ; rest is file payload o_html_b .byte "HTTP/1.0 200 OK",$0D,$0A,"Content-Type: text/html" .byte $0D,$0A,$0D,$0A .byte "<HTML><HEAD><TITLE>Vic 20 WEB Server</TITLE>" .byte "</HEAD><BODY BGCOLOR=#65CC02 TEXT=WHITE>" .byte "<TABLE ALIGN=CENTER WIDTH=80% HEIGHT=90%>" .byte "<TR><TD VALIGN=CENTER>" .byte "<FONT SIZE=+1>Vic 20 WEB Server </FONT>" .byte "- <A HREF=>The home page</A>" .byte " - <A HREF=b.html>The button page</A>" .byte "<P>" .byte "<FONT SIZE=+4><B>Welcome</B></FONT>" .byte "<FONT FACE=Courier> - to the other page</FONT>" .byte "<P>" .byte "<A HREF=mailto:leeedavison@lycos.co.uk> e-mail " .byte "me <IMG SRC=images/e.gif BORDER=0 ALT=e-mail></A>" .byte "</TD></TR></TABLE></BODY></HTML>" o_html_end ; ********************************************************************* ; the button page, served for "GET /b.html" b_html .byte none ; flags .word b_html_end-b_html_b ; file payload length .byte "b.html",$00 ; filename, null terminator ; rest is file payload b_html_b .byte "HTTP/1.0 200 OK",$0D,$0A,"Content-Type: text/html" .byte $0D,$0A,$0D,$0A .byte "<HTML><HEAD><TITLE>Vic 20 WEB Server</TITLE>" .byte "</HEAD><BODY BGCOLOR=#065C02 TEXT=WHITE>" .byte "<TABLE ALIGN=CENTER WIDTH=90% HEIGHT=90%>" .byte "<TR><TD VALIGN=CENTER>" .byte "<FONT SIZE=+1>Vic 20 WEB Server </FONT>" .byte "- <A HREF=>The home page</A>" .byte " - <A HREF=o.html>The other page</A>" .byte "<P>" .byte "<FONT SIZE=+4><B>Welcome</B></FONT>" .byte "<FONT FACE=Courier> - to the button page</FONT>" .byte "<P>" .byte "<A HREF=mailto:leeedavison@lycos.co.uk> e-mail " .byte "me <IMG SRC=images/e.gif BORDER=0 ALT=e-mail></A></TD>" .byte "<TD WIDTH=20% ALIGN=CENTER>Are you easily confused?" .byte "<FORM METHOD=GET ACTION=b.html>" .byte "<PRE>" .byte "yes <INPUT TYPE=RADIO NAME=4 VALUE=0>",$0D,$0A .byte "no <INPUT TYPE=RADIO NAME=4 VALUE=1>",$0D,$0A .byte "fish<INPUT TYPE=RADIO NAME=4 VALUE=2 CHECKED>" .byte "</PRE>" .byte "<INPUT TYPE=SUBMIT VALUE=Submit>" .byte "</FORM></TD></TR></TABLE></BODY></HTML>" b_html_end ; ********************************************************************* ; errors directory errors .byte directory ; flags .word errors_end-errors_b ; file payload length .byte "errors",$00 ; filename, null terminator errors_b .word e_404 ; error 404 errors_end ; error 404, served for not found files e_404 .byte none ; flags .word e_404_end-e_404_b ; file payload length .byte "404",$00 ; filename, null terminator ; rest is file payload e_404_b .byte "HTTP/1.0 200 OK",$0D,$0A,"Content-Type: text/html" .byte $0D,$0A,$0D,$0A .byte "<HTML><HEAD><TITLE>Error 404</TITLE>" .byte "</HEAD><BODY BGCOLOR=#CC0265 TEXT=WHITE>" .byte "<TABLE ALIGN=CENTER WIDTH=80% HEIGHT=90%>" .byte "<TR><TD VALIGN=CENTER>" .byte "<FONT SIZE=+1>Vic 20 WEB Server </FONT>" .byte "- <A HREF=>The home page</A>" .byte "<P>" .byte "<FONT SIZE=+4><B>Error 404</B></FONT>" .byte "<FONT FACE=Courier> - File not found</FONT>" .byte "<P>" .byte "<A HREF=mailto:leeedavison@lycos.co.uk> e-mail " .byte "me <IMG SRC=images/e.gif BORDER=0 ALT=e-mail></A>" .byte "</TD></TR></TABLE></BODY></HTML>" e_404_end ; ********************************************************************* ; images directory images .byte directory ; flags .word images_end-images_b ; file payload length .byte "images",$00 ; filename, null terminator images_b .word e_gif ; small image images_end ; small image, served for "GET /email.gif" e_gif .byte none ; flags .word e_gif_end-e_gif_b ; file payload length .byte "e.gif",$00 ; filename, null terminator ; rest is file payload e_gif_b .byte "HTTP/1.0 200 OK",$0D,$0A,"Content-Type: image/gif",$0D,$0A .byte $0D,$0A,$47,$49,$46,$38,$39,$61,$10,$00,$10,$00,$C4,$00,$00 .byte $F7,$00,$5F,$33,$03,$9A,$33,$03,$CA,$03,$03,$83,$5F,$5F,$F7 .byte $33,$63,$FA,$63,$9B,$FB,$63,$CB,$FB,$03,$9B,$CB,$63,$FA,$FA .byte $FA,$FA,$CB,$97,$00,$00,$CB,$03,$03,$FA,$FA,$FA,$63,$63,$63 .byte $03,$03,$03,$FF,$FF,$FF,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$21,$F9,$04,$01,$00,$00,$10,$00,$2C .byte $00,$00,$00,$00,$10,$00,$10,$00,$00,$05,$69,$20,$24,$8E,$C6 .byte $91,$24,$63,$9A,$96,$49,$11,$18,$AA,$28,$40,$85,$59,$40,$46 .byte $A0,$3A,$8E,$20,$20,$07,$01,$6F,$38,$7C,$38,$1A,$3F,$84,$40 .byte $C1,$6C,$36,$8D,$C8,$02,$62,$E0,$64,$02,$18,$0A,$E8,$60,$BB .byte $AD,$32,$16,$D9,$63,$A3,$51,$2D,$43,$C9,$E5,$EA,$B9,$2C,$28 .byte $B8,$A9,$D0,$B4,$42,$70,$82,$3B,$E4,$F3,$7A,$F8,$C1,$EF,$F3 .byte $05,$26,$5B,$7C,$2A,$2F,$10,$02,$25,$37,$03,$31,$10,$04,$01 .byte $05,$06,$06,$37,$8B,$23,$01,$03,$6D,$31,$21,$00,$3B e_gif_end