gno/bin/gsh/hash.asm
tribby e586eeefeb Changes for gsh version 2.0d9:
Added additional optional debug code. A version of gsh with the memory
checking debug code turned on is available as an exe file.

Lock and unlock the hash mutual exclusion key in when disposing of the
hash table.

Allow five digits rather than four when printing the process number in
the ps command.

Piping the output of a command into a non-executable file no longer
causes gsh to hang.
1999-01-14 17:44:25 +00:00

1205 lines
22 KiB
NASM

**************************************************************************
*
* The GNO Shell Project
*
* Developed by:
* Jawaid Bazyar
* Tim Meekins
*
* $Id: hash.asm,v 1.9 1999/01/14 17:44:24 tribby Exp $
*
**************************************************************************
*
* HASH.ASM
* By Tim Meekins & Greg Thompson
* Modified by Dave Tribby for GNO 2.0.6
*
* Command hashing routines
*
* Note: text set up for tabs at col 16, 22, 41, 49, 57, 65
* | | | | | |
* ^ ^ ^ ^ ^ ^
**************************************************************************
*
* Interfaces defined in this file:
*
* hash jsr with params: (2:num, 4:name)
*
* dohash subroutine (4:files)
* return 4:table
*
* search subroutine (4:file,4:table,4:paths)
* return 4:full_path
*
* dispose_table subroutine (4:table)
* return
*
* free_files subroutine (4:files)
* return
*
* dir_search subroutine (4:dir,2:dirNum,4:files)
* return
*
* hashpath jsl with no parameters
* no returned value
*
* dispose_hash jsr with no parameters
* no returned value
*
**************************************************************************
mcopy /obj/gno/bin/gsh/hash.mac
dummyhash start ; ends up in .root
end
setcom 60
C1 gequ 11
C2 gequ 13
TAB_MULT gequ 4
;
; Structure for filenames
;
; struct filenode {
; short dirnum;
; char name[32];
; filenode *next;
; };
fn_dirNum gequ 0
fn_name gequ fn_dirNum+2
fn_next gequ fn_name+32
fn_size gequ fn_next+4
;
; Structure for hash table
;
; struct tablenode {
; short dirnum;
; char *name[32];
; };
tn_dirNum gequ 0
tn_name gequ tn_dirNum+2
tn_size gequ tn_name+32
**************************************************************************
*
* Calculate hash value for a filename
*
**************************************************************************
hash START
using hashdata
space equ 1
num equ space+2
name equ num+2
end equ name+4
* NOTE: hash should only be called after hashmutex is locked
;
; No local variables; just need to save old Dir Page pointer and set
; up new one to point to parameters.
;
tsc
phd
tcd
lda num If this isn't the first time
bne hasher through, reuse the value of "h".
;
; First time through for the name: calculate value for "h"
;
stz h
ldy #0
loop lda [name],y
and #$FF
beq hasher
sta addit+1 Modify "adc #$FFFF"
lda h ;left shift 7
xba
and #$FF00
lsr a
addit adc #$FFFF NOTE: immediate data was modified.
phy
UDivide (@a,t_size),(@a,@a)
sta h
ply
iny
bra loop
;
; "h" has been calculated; now do the rest of the hash function
;
hasher lda num ;num*num
sta tmp
lda #0
ldx #16
mulloop asl a
asl tmp
bcc nomul
clc
adc num
nomul dex
bne mulloop
pha ;Acc * C2
asl a
asl a
sec
sbc 1,s (Use top word on stack as temp var)
asl a
asl a
adc 1,s
sta 1,s
lda num ;num*C1 + (Acc*C2) + h
asl a
adc num
asl a
asl a
adc 1,s
adc h
sec
sbc num
plx (Remove temp var from stack)
UDivide (@a,t_size),(@a,@y)
;
; Return the hashed value to the user
;
lda space
sta end-2
pld
tsc
clc
adc #end-3
tcs
tya Final hash value is in accumulator.
rts
h ds 2 NOTE: h must be a "static" variable.
tmp ds 2
END
**************************************************************************
*
* dohash
*
**************************************************************************
dohash START
using hashdata
h equ 1
temp equ h+2
qh equ temp+4
table equ qh+2
space equ table+4
files equ space+3
end equ files+4
; subroutine (4:files),space
tsc
sec
sbc #space-1
tcs
phd
tcd
lda hash_numexe
bne mktsize
stz table
stz table+2
jmp done
;
; t_size = (TAB_MULT * numexe) - 1
; [Shift since TAB_MULT is 4, change later if needed]
;
mktsize asl a
asl a
dec a
sta t_size
;
; table = (tablenode **)malloc(sizeof(tablenode *) * t_size);
;
inc a ;safety precaution
asl a
asl a
pea 0
pha
~NEW
sta table
stx table+2
;
; for (i=0; i < t_size; ++i) table[i] = NULL;
;
ldy #0
ldx t_size
tya
clrtbl sta [table],y
iny
iny
sta [table],y
iny
iny
dex
bne clrtbl
;
; files = files->next
;
mainloop ldy #fn_next
lda [files],y
tax
ldy #fn_next+2
lda [files],y
sta files+2
stx files
;
; while (files != NULL) {
;
ora files
jeq done
stz qh
;
; while (table[h = hash(files->name, qh))]) { ++qh; ++colls; }
;
hashloop pei (files+2)
lda files
inc a
inc a
pha
pei (qh)
jsr hash
asl a
asl a
sta h
tay
lda [table],y
tax
iny2
ora [table],y
beq gotit
; If it's the same name, skip the duplicate entry
ldy h Calculate address
clc of hash entry's
lda [table],y name field.
adc #tn_name
tax
iny2
lda [table],y
adc #0
pha High-order word of address.
phx Low-order word of address.
pei (files+2)
lda files
inc a
inc a
pha
jsr cmpcstr
beq mainloop
inc qh
bra hashloop
;
; table[h] = (tablenode *)malloc(sizeof(tablenode))
;
gotit ph4 #tn_size
~NEW
sta temp
stx temp+2
ldy h
sta [table],y
iny
iny
txa
sta [table],y
;
; table[h]->dirnum = files->dirNum
;
lda [files]
sta [temp]
;
; strcpy(table[h]->name, files->name);
;
pei (files+2)
lda files
inc a
inc a
pha
pei (temp+2)
lda temp
inc a
inc a
pha
jsr copycstr
jmp mainloop
done anop
; return 4:table
ldx table+2
ldy table
lda space
sta end-3
lda space+1
sta end-2
pld
tsc
clc
adc #end-4
tcs
tya
rtl
END
**************************************************************************
*
* Search the hash table
*
**************************************************************************
search START
using hashdata
ptr equ 1
name_len equ ptr+4
full_path equ name_len+2
qh equ full_path+4
space equ qh+2
paths equ space+3
table equ paths+4
file equ table+4
end equ file+4
; subroutine (4:file,4:table,4:paths),space
tsc
sec
sbc #space-1
tcs
phd
tcd
lock hashmutex
stz qh
stz full_path Set result to NULL.
stz full_path+2
lda table If hash table hasn't
ora table+2 been allocated,
jeq done return null string.
pei (file+2)
pei (file)
jsr lowercstr
mainloop pei (file+2) Get hash(qh,file)
pei (file)
pei (qh)
jsr hash
asl a Multiply by 4
asl a
tay Use as index into table.
lda [table],y ptr = table[hash(qh,file)]
sta ptr
tax
iny
iny
ora [table],y If == 0,
jeq done all done.
lda [table],y
sta ptr+2
pei (file+2)
pei (file)
pha ;ptr+2
inx ;ptr + #2
inx
phx
jsr cmpcstr Compare filename against entry.
beq found
inc qh
bra mainloop
;
; Found an entry that matches the filename. Calculate full path.
;
found lda [ptr]
asl a
asl a
adc paths ;(cf=0)
sta ptr
ldx paths+2
stx ptr+2
pei (file+2)
pei (file)
jsr cstrlen Get length of prog name.
sta name_len
ldy #2
lda [ptr],y
pha
lda [ptr]
pha
jsr cstrlen Get length of path.
pha
sec
adc name_len Add name length + 1 (carry bit).
pea 0
pha
~NEW Allocate memory,
sta full_path storing address at
stx full_path+2 functional return value.
ldy #2
lda [ptr],y
pha
lda [ptr]
pha
pei (full_path+2)
pei (full_path)
jsr copycstr Copy pathname into buffer.
pla ;length of path
pei (file+2)
pei (file)
pei (full_path+2)
clc
adc full_path
pha
jsr copycstr Put filename at end of pathname.
done unlock hashmutex
ldx full_path+2 Load return value into Y- & X- regs
ldy full_path
; Adjust stack in preparation for return
lda space
sta end-3
lda space+1
sta end-2
pld
tsc
clc
adc #end-4
tcs
tya A- & X- regs contain ptr (or NULL)
rtl
END
**************************************************************************
*
* Dispose the hash table
*
**************************************************************************
dispose_table START
using hashdata
ptr equ 0
count equ ptr+4
space equ count+2
subroutine (4:table),space
mv4 table,ptr
mv2 t_size,count
loop ldy #2
lda [ptr],y
pha
lda [ptr]
pha
jsl nullfree
add2 ptr,#4,ptr
dec count
bne loop
pei (table+2)
pei (table)
jsl nullfree
return
END
**************************************************************************
*
* Dispose the file table
*
**************************************************************************
free_files START
space equ 0
subroutine (4:files),space
loop ora2 files,files+2,@a
beq done
ldy #fn_next
lda [files],y
tax
ldy #fn_next+2
lda [files],y
pei (files+2)
pei (files)
stx files
sta files+2
jsl nullfree
bra loop
done return
END
**************************************************************************
*
* Directory search
*
**************************************************************************
dir_search START
using hashdata
temp2 equ 0
temp equ temp2+4
entry equ temp+4
numEntries equ entry+2
ptr equ numEntries+2
space equ ptr+4
subroutine (4:dir,2:dirNum,4:files),space
* NOTE: dir_search is only called from hashpath (after hashmutex locked)
;
; Open directory name passed as 1st parameter
;
ld2 3,ORec
pei (dir+2) Turn "dir" c-string into
pei (dir) a GS/OS string, allocated
jsr c2gsstr via ~NEW.
sta ORecPath
stx ORecPath+2
phx Put GS/OS string addr on stack
pha so it can be deallocated.
Open ORec Open that file.
bcc goodopen If there was an error,
jsl nullfree Free the GS/OS string
jmp exit and exit.
goodopen jsl nullfree Free the GS/OS string.
;
; Set up parameter block for GetDirEntry
;
mv2 ORecRef,DRecRef Copy the file ref num from open.
stz DRecBase Zero the base and
stz DRecDisp displacement.
GetDirEntry DRec Make DirEntry call.
mv2 DRecEntry,numEntries Save number of entries.
ld2 1,(DRecBase,DRecDisp)
stz entry # processed entries = 0.
;
; Process each entry in the directory
;
loop lda entry If number of processed entries
cmp numEntries equals the total number,
jge done we are all done.
;
; Get directory entry's information
;
GetDirEntry DRec
; Check for filetype $B3: GS/OS Application (S16)
if2 DRecFileType,eq,#$B3,goodfile
; Check for filetype $B5: GS/OS Shell Application (EXE)
if2 @a,eq,#$B5,goodfile
; Check for filetype $B0, subtype $0006: Shell command file (EXEC)
cmp #$B0
jne nextfile
lda DRecAuxType
cmp #$06
bne nextfile
lda DRecAuxType+2
bne nextfile
;
; This directory entry points to an executable file.
; Included it in the file list.
;
goodfile inc hash_numexe Bump the (global) # files.
ldx TempRBlen Get length word from GS/OS string
short a
stz TempRBname,x Store terminating null byte.
long a
ph4 #TempRBname
jsr lowercstr Convert name to lower case.
ph4 #TempRBname Push src addr for copycstr.
ldy #fn_next temp = files->next.
lda [files],y
sta temp
ldy #fn_next+2
lda [files],y
sta temp+2
ph4 #fn_size temp2 = new entry.
~NEW
sta temp2
stx temp2+2
ldy #fn_next files->next = temp2
sta [files],y
ldy #fn_next+2
txa
pha
sta [files],y
lda temp2
clc
adc #fn_name
pha
jsr copycstr Copy string into entry.
lda dirNum temp2->dirnum = dirNum
sta [temp2]
ldy #fn_next temp2->next = temp
lda temp
sta [temp2],y
ldy #fn_next+2
lda temp+2
sta [temp2],y
nextfile inc entry Bump entry number
jmp loop and stay in the loop.
;
; Done adding entries to the hash table from this directory
;
done anop
ld2 1,ORec ORec.pCount = 1
Close ORec
exit return
; Parameter block for GS/OS Open and Close calls
ORec dc i'3' pCount (3 for Open, 1 for Close)
ORecRef ds 2 refNum
ORecPath ds 4 pathname (result buf)
ORecAccess dc i'1' requested access = read
; Parameter block for GS/OS GetDirEntry call
DRec dc i'13' pCount
DRecRef ds 2 refNum
DRecFlag ds 2 flags: extended/not
DRecBase dc i'0' base: displacement is absolute entry #
DRecDisp dc i'0' displacement: get tot # active entries
DRecName dc i4'TempResultBuf' name: result buf
DRecEntry ds 2 entryNum: entry # whose info is rtrned
DRecFileType ds 2 fileType
DRecEOF ds 4 eof: # bytes in data fork
DRecBlockCnt ds 4 blockCount: # blocks in data fork
DRecCreate ds 8 createDateTime
DRecMod ds 8 modDateTime
DRecAccess ds 2 access attribute
DRecAuxType ds 4 auxType
; GS/OS result buffer for getting a directory entry's name
TempResultBuf dc i2'68' Total length = 64 bytes + 4 for length
TempRBlen ds 2 Value's length returned here
TempRBname ds 64 Allow 64 bytes for returned name
ds 1 Extra byte for null string termination
END
**************************************************************************
*
* Hash the path variable
*
**************************************************************************
hashpath START
using hashdata
using vardata
using global
len equ 1
pathnum equ len+2
ptr equ pathnum+2
files equ ptr+4
pathptr equ files+4
qflag equ pathptr+4
qptr equ qflag+2
gsosbuf equ qptr+4
space equ gsosbuf+4
end equ space+3
;
; Allocate space on stack for direct page variables
;
tsc
sec
sbc #space-1
tcs
phd
tcd
lock hashmutex
;
; Allocate special file node
;
ph4 #fn_size
~NEW
sta hash_files
sta files
stx hash_files+2
stx files+2
;
; Initialize counters and pointers
;
lda #0
sta hash_numexe
sta pathnum
ldy #fn_next
sta [files],y
ldy #fn_next+2
sta [files],y
ldy #fn_name
sta [files],y
sta [files]
;
; Get value of $PATH environment variable string
;
ph4 #pathname
jsl getenv
sta gsosbuf Save address of allocated buffer.
stx gsosbuf+2
ora gsosbuf+2 If null,
bne setptr
ldx #^nopatherr print error message
lda #nopatherr
jsr errputs
jmp noprint and exit.
setptr clc Add 4 bytes to
lda gsosbuf direct page pointer
adc #4 to get the addr of
sta pathptr beginning of text.
lda gsosbuf+2
adc #0
sta pathptr+2
;
; Begin parsing $PATH
;
loop lda [pathptr]
and #$FF
jeq pathdone
;
; parse next pathname
;
stz qflag Clear quote flag for this path
mv4 pathptr,ptr
ldy #0
despace lda [pathptr],y
and #$FF
beq gotspace0
if2 @a,eq,#' ',gotspace1
if2 @a,eq,#009,gotspace1
if2 @a,eq,#013,gotspace1
if2 @a,eq,#'\',gotquote
iny
bra despace
; Found "\"
gotquote anop
iny2
ldx qflag If quote flag hasn't already been set,
bne despace
sty qflag set it to index of first "\" + 2.
bra despace
; Found null byte
gotspace0 tyx Why put Y-reg in X???
bra gotspace3
; Found " ", tab, or creturn
gotspace1 tyx Why put Y-reg in X???
short a
lda #0
sta [pathptr],y
long a
gotspace2 iny
lda [pathptr],y
and #$FF
if2 @a,eq,#' ',gotspace2
if2 @a,eq,#009,gotspace2
if2 @a,eq,#013,gotspace2
gotspace3 anop
clc Bump pathptr by
tya the number of bytes
adc pathptr indicated in Y-reg.
sta pathptr
lda pathptr+2
adc #0
sta pathptr+2
lda pathnum
cmp #32*4
bcc numok
ldx #^toomanyerr
lda #toomanyerr
jsr errputs
jmp pathdone
;
; Convert c string to GS/OS string (allocating space for it)
;
numok pei (ptr+2)
pei (ptr)
jsr c2gsstr
phx Push allocated address onto
pha stack for later deallocation.
sta EPinputPath Save address in ExpandPath
stx EPinputPath+2 parameter block.
;
; If any quoted characters were included, the "\" chars must be removed
;
ldy qflag Get quote flag (index to "\" char).
beq xpandit If no "\", go ahead with expansion.
sta qptr Save EPinputPath pointer in
stx qptr+2 direct page variable.
lda [qptr] Store length + 2 (since we're indexing
inc2 a from before length word) in qflag.
sta qflag
tyx X = index of 1st overwritten "\".
short a Use 1-byte accumulator
;
; Copy characters toward front of string, removing "\" chars
;
chkloop2 lda [qptr],y Get next character.
cmp #'\' If it's a quote,
bne storeit
lda [qptr] Decrement length.
dec a
sta [qptr]
iny Skip over "\".
lda [qptr],y Get character following.
storeit phy Push source index onto stack
txy so destination index can be
sta [qptr],y used to store the character.
ply Restore the source index.
inx Bump destination and
iny source index registers.
cpy qflag If source index < length,
bcc chkloop2 stay in copy loop.
long a Restore long accumulator.
;
; Convert the input pathname into the corresponding full pathname with
; colons as separators. Use temp result buf this time, just to get length.
;
xpandit anop
ld4 TempResultBuf,EPoutputPath
ExpandPath EPParm
;
; Allocate memory for ExpandPath GS/OS result string
;
lda TempRBlen Get length of value.
inc2 a Add 4 bytes for result buf len words.
inc2 a
sta len Save result buf len.
inc a Add 1 more for terminator.
pea 0
pha
~NEW Request the memory.
sta EPoutputPath Store address in ReadVariable
stx EPoutputPath+2 parameter block and
sta ptr direct page pointer.
stx ptr+2
ora ptr+2 If address == NULL,
jeq donext get next entry.
lda len Store result buffer length
sta [ptr] at beginning of buf.
ExpandPath EPParm Call again, and get the name.
bcc epok
ldx #^eperrstr Print error message:
lda #eperrstr "Invalid pathname syntax."
jsr errputs
donext jsl nullfree Free GS/OS string (pushed earlier).
jmp next Get the next one.
epok anop
jsl nullfree Free source string (addr on stack)
ldy #2
lda [ptr],y Get length of text
sta len
tay
iny4 and add four.
short a
lda #0 Store 0 at end of text so it
sta [ptr],y can act like a C string.
long a
;
; Move text in GS/OS result buffer to beginning of buffer
; (overwritting the two length words).
;
clc Source is result
lda ptr buffer plus
adc #4 four bytes.
tay
lda ptr+2
adc #0
pha
phy
pei (ptr+2) Destination is first
pei (ptr) byte of buffer.
jsr copycstr
lda ptr
ldy pathnum
sta hash_paths,y Store address of this
lda ptr+2 path's address in the
sta hash_paths+2,y hash path table.
ldy len
beq bumppnum
dey
lda [ptr],y If last character
and #$FF of path name
cmp #':' isn't ":",
beq bumppnum
iny
lda #':' store ":\0"
sta [ptr],y at end of string.
bumppnum anop
add2 pathnum,#4,pathnum Bump path pointer.
next jmp loop Stay in loop.
;
; The $PATH entries have been created. Now we need to search each of the
; directories for executable files and add them to the "files" list.
; The earliest versions of gsh added files to the list in the order that
; directories appeared in $PATH, which put the earliest directories' files
; at the end of the list. Check for existence of $OLDPATHMODE environment
; variable to see if the user wants this, or would rather have them hashed
; in the expected order.
;
pathdone anop
lda varoldpmode
beq neworder
;
; Search directories and add executables to file list starting at the
; beginning of $PATH and working to the end.
;
stz pathnum Start at beginning of path table.
nextpath1 ldy pathnum Get offset into hash table.
cpy #32*4
bcs filesdone
lda hash_paths,y If address of this path
ora hash_paths+2,y has not been set,
beq filesdone all done.
lda hash_paths,y Get address of this
ldx hash_paths+2,y path's address in the
phx hash path table.
pha
tya Directory number =
lsr2 a offset / 4
pha
pei (files+2) Pointer to file list.
pei (files)
jsl dir_search Add executables from this directory.
add2 pathnum,#4,pathnum Bump path offset.
bra nextpath1
;
; Search directories and add executables to file list starting at end
; of $PATH and working back to the beginning. (Note: Loop begins at
; "neworder", but structuring the code this ways saves an instruction.)
;
nextpath2 dey4 Decrement path offset.
sty pathnum
lda hash_paths,y Get address of this
ldx hash_paths+2,y path's address in the
phx hash path table.
pha
tya Directory number =
lsr2 a offset / 4
pha
pei (files+2) Pointer to file list.
pei (files)
jsl dir_search Add executables from this directory.
neworder ldy pathnum Get offset into hash table.
bne nextpath2 When == 0, no more to do.
;
; Executable files in $PATH have been added to the list. Print
; number of files, then build the hash table.
;
filesdone anop
ph4 gsosbuf Free memory allocated for
jsl nullfree $PATH string.
lda done_init If initialization isn't complete,
beq noprint don't print the # of files.
Int2Dec (hash_numexe,#hashnum,#3,#0)
ldx #^hashmsg
lda #hashmsg
jsr puts
noprint anop
;
; Create the hash table from the file list.
;
ph4 hash_files
jsl dohash
sta hash_table
stx hash_table+2
unlock hashmutex
pld
tsc
clc
adc #end-4
tcs
rtl
pathname gsstr 'path'
hashmsg dc c'hashed '
hashnum dc c'000 files',h'0d00'
; Parameter block for GS/OS call ExpandPath
EPParm dc i'2' pCount = 2
EPinputPath ds 4 ptr to inputPath (GS/OS string)
EPoutputPath ds 4 ptr to outputPath (Result buffer)
; GS/OS result buffer for getting the full length of expanded name
TempResultBuf dc i2'5' Only five bytes total.
TempRBlen ds 2 Value's length returned here.
ds 1 Only 1 byte for value.
eperrstr dc c'rehash: Invalid pathname syntax.',h'0d00'
toomanyerr dc c'rehash: Too many paths specified.',h'0d00'
nopatherr dc c'rehash: PATH string is not set.',h'0d00'
END
**************************************************************************
*
* Dispose of hashing tables
*
**************************************************************************
dispose_hash START
using hashdata
lock hashmutex
ora2 hash_table,hash_table+2,@a
beq done
ldx #32 32 different paths, maximum
ldy #0 Start looking at the first entry.
loop1 phx Save path counter
phy and index.
lda hash_paths+2,y Put address for this
pha path table entry on
lda hash_paths,y the stack.
pha
lda #0 Zero out the table entry.
sta hash_paths+2,y
sta hash_paths,y
jsl nullfree Free the entry's memory.
next1 ply Restore path index
plx and counter.
iny4 Bump pointer to next address.
dex If more paths to process,
bne loop1 stay in the loop.
ph4 hash_files
jsl free_files
stz hash_files
stz hash_files+2
ph4 hash_table
jsl dispose_table
stz hash_table
stz hash_table+2
done unlock hashmutex
rts
END
**************************************************************************
*
* Hash data
*
**************************************************************************
hashdata DATA
hashmutex key Mutual exclusion key
t_size ds 2 t_size = (TAB_MULT * numexe) - 1
hash_paths dc 32i4'0' 32 paths max for now.
hash_files dc i4'0'
hash_table dc i4'0' Pointer to table (t_size entries)
hash_numexe dc i2'0' Number of hashed executables
END