SDCC and recursion and MSX-DOS2

Page 1/2
| 2

Par rolandve

Champion (372)

Portrait de rolandve

30-03-2023, 23:32

Building on MSXgl, I've written a piece of code that recursively tries to walk the directory tree. Going in to a directory works fine. So from root->A->B works. However, at the end of B it should go up 1 "..". It doesn't. It goes back to "\" and forgets everything in between. Does SDCC have special parameters for recursion? I've looked at the manual but the one I have doesn't mention recursion. It seems like fib stays zero because findnextfile can't find a file after its done searching a sudirectory and going up 1 level., In this case it only reached the first directory of a list of subdirectories. because there is also root->A->C, etc.

#include 
#include 
#include 
// Change Directory

u8 __at(0xF55E) outBuf[256];

u8 DOS_ChangeDir(const c8* path)
{
	path; // HL
__asm
	ex de,hl
	ld c, #DOS_FUNC_CHDIR      // DOS routine
	call BDOS
__endasm;
}

void walk(const u8* param)
{
    Mem_Set('$',outBuf,15);
    DOS_FIB* fib = DOS_FindFirstEntry(param,0x37);
    while (fib) {
        if (fib->Filename[0] != '.') {
            String_Format(outBuf,"%s\n\r$",fib->Filename);
            DOS_StringOutput(outBuf);
            if ((fib->Attribute & 0x10) == 0x10 ) {
                DOS_ChangeDir(fib->Filename);
                walk("*.*");
               DOS_ChangeDir("..");
            }
        }
        fib = DOS_FindNextEntry();
    }
}

void main()
{
    walk("*.*"); // this is the current directory
}

!login ou Inscrivez-vous pour poster

Par DamnedAngel

Champion (286)

Portrait de DamnedAngel

31-03-2023, 06:01

I have no experience with MSXgl, but I suggest two points to check:

1. DOS_FIB* fib is a pointer to a DOS_FIB structure. Where is such structure allocated? If DOS_FindFirstEntry has a fixed DOS_FIB structure for all instances of the recursion, the recursion will likely fail.
2. Are both MSXgl and you application compiled with the same calling standard (sdcccall0 (stack) and sdcccall1 (registers))? If they aren't, function calling will be broken.

Let us know the answers/results!

Par aoineko

Paragon (1140)

Portrait de aoineko

31-03-2023, 08:25

DOS_FindFirstEntry() use a single FIB buffer so when you start searching to a subdirectory you overwrite the parent FIB data.
So, to iterate through a whole directory tree using recursion, you will need to keep a stack of FIB buffer and restore parent FIB when you are done parsing a subdirectory.
BTW I don't think MSX-DOS2 function CHDIR support the ".." filename.

Par rolandve

Champion (372)

Portrait de rolandve

31-03-2023, 10:21

It looks like your first observation. In each step of the recursion DOS_FIB gets a new location, that is the pointers address goes up.The address pointed to: the FIB structure it self seems fixed. That can explain, why it terminates. FIB is zero at the end of the subdirectory and this applies to all previous instances.

Par aoineko

Paragon (1140)

Portrait de aoineko

31-03-2023, 09:27

A brute-force approach would be to:
- Allocate a FIB buffer at the start of your walk() function
- Backup the FIB pointed by DOS_GetLastFileInfo() into your buffer
- ... your walk() content...
- Restore DOS_GetLastFileInfo() content
- Free your temporary buffer

If you have no other memory allocation that can occur during your directories parsing (like in your ISR) you can use "flat" allocation/free using Mem_HeapAlloc and Mem_HeapFree. Otherwise you have to use dynamic allocator ( see Mem_DynamicInitialize).

I'm not sure a call to CHDIR is needed to continue a directory parsing started with FFIRST. So change to ".." it's perhaps just useless.

Par msd

Paragon (1532)

Portrait de msd

31-03-2023, 09:58

I made the sames in asm long time ago. Indeed you need to keep a FIB per recursion. '..' is a valid string for bdos

		org	#100

		output "SEARCH.COM"

		include "../../../lib/bdos/bdos.inc"
		include "../../../lib/macros.inc"

		ld   de,search_txt
		call String.PrintAsciiz

		CALL	Bdos.IsDos2OrHigher
		JP		NZ,NoDos2


Get_path_wild
		ld	a,(CMD_LENGTH)
		or	a,a
		jp	z,Get_path_wild.2

		ld	a,(CMD_LENGTH)
		or	a,a
		jr	z,nope
		ld	b,a
		ld	hl,#81
SkipSpaces
		ld	a,(hl)
		inc	hl
		cp	a," "+1
		jp	c,SkipSpaces

		ld	a,(hl)
		cp	a,":"
		jp	nz,nope

		dec	hl
		ld	a,(HL)
		and	a,UPPER_CASE_MASK
		sub	a,"A"
		ld	e,a
		
		PUSH	BC
		PUSH	HL
		call	Bdos.SelectDrive
		POP	HL	; Current command line pointer
		POP	BC	; Current length
		jp	nz,ScanError
nope
		ld	a,b
		dec	a
		ld	l,a
		ld	h,0
		ld	de,#82
		add	hl,de	; Pointer to last command line char
		ld	de,0
		ld	a,"\\"
Get_path_wild.0
		cp	a,(hl)
		jp	z,Get_path_wild.1
		inc	de
		dec	hl
		djnz	Get_path_wild.0	; Search back until \ is found or begin of command line

Get_path_wild.1

		ld	b,d
		ld	c,e
		inc	hl
		push	hl
		ld	de,file_mask
		ldir		; Copy file name to file_mask
		ex	de,hl
		ld	(hl),0	; Set term char file name
		pop	hl
		ld	(hl),0	; Set term char path name
		ld	de,#82
		CALL Bdos.ChangeDir
		jp nz,ScanError

Get_path_wild.2

		ld		ix,fileInfoBlock
		call	Scan_dir
		JR		NZ,ScanError

		ld		de,file_txt
		call	String.PrintAsciiz
		ld		hl,(file_count)
		ld		de,SYSBUF
		call	String.UnsignedInt16ToStr
		ld		de,SYSBUF
		call	String.PrintAsciiz

		ld		de,dir2_txt
		call	String.PrintAsciiz

		ld		hl,(dir_count)
		ld		de,SYSBUF
		call	String.UnsignedInt16ToStr
		ld		de,SYSBUF
		call	String.PrintAsciiz
		ret

ScanError
		ld		DE,Bdos.ERROR_STRING
		JP	    String.PrintAsciiz
		

Scan_dir
; IX = pointer to space for a new FIB
; First list all files in a directory
; Then search for a directory and open it and call Scan_dir 

	call	scan_files

	ld	    de,dir_mask
	ld		b,FILE_HIDDEN | FILE_SYSTEM | FILE_DIRECTORY
	CALL    Bdos.FindFirst

	ld	    a,"."
	cp	    a,(ix+1)
	jp	    nz,Scan_dir.3	; entry is "."/".." then skip 2
	call	Bdos.FindNext

Scan_dir.0
	call	Bdos.FindNext
	cp	    a,ERROR.NOFIL
	;jp	    z,end_of_dir
	ret		z

Scan_dir.3
	bit	4,(ix+FIB.fileAttrib)
	jp	Z,Scan_dir.0	; Entry is a file
		
	LD A,(ix+FIB.fileName)
	CP A,128
	JP NC,Scan_dir.0 ; First char is crap
	CP A,32
	JP C,Scan_dir.0 ; First char is crap

Scan_dir.1

	ld	e,ixl
	ld	d,ixh
	inc	de	; Set pointer on dir Name
	CALL Bdos.ChangeDir		; Dir level up
	ret		nz			    ; exit loop on some error

	call	Get_path

	push	ix			; Remeber old FIB pointer
	ld		de,FIB	 	; FIB space
	add		ix,de
	call	Scan_dir	; Rec. call scan_dir
	pop		ix			; Restore old FIB pointer

	ret		nz			; Return on some error

	ld		de,dbl_point
	CALL Bdos.ChangeDir	; Dir level down
	ret		nz
	jp	Scan_dir.0


scan_files
		ld	de,file_mask
		ld	b,FILE_HIDDEN | FILE_SYSTEM
		call Bdos.FindFirst
		cp	a,ERROR.NOFIL
		ret	z

scan_file_loop
		; Increase file count
		ld	hl,(file_count)
		inc	hl
		ld	(file_count),hl

		call print_name
		call Bdos.FindNext
		cp	a,ERROR.NOFIL
		ret	z
		jp	scan_file_loop

print_name
   		ld		e,ixl
		ld		d,ixh
		inc		de  ; File name
		call	String.PrintAsciiz
		ld	    de,next_line
		jp	     String.PrintAsciiz

Get_path
		push	ix
		; Increase count number of directories
		ld	hl,(dir_count)
		inc	hl
		ld	(dir_count),hl

		ld		de,dir_txt
		call	String.PrintAsciiz

		ld		c,_GETCD
		ld		de,path_buf
		ld		b,0
		call	BDOS

		ld		de,path_buf
		CALL	String.PrintAsciiz

		ld	de,next_line
		CALL	String.PrintAsciiz
		pop	ix
		ret

NoDos2:
		LD	DE,.txt
		JP String.PrintAsciiz

.txt: db "File searcher requires DOS2.xx run",CR,LF,0

	include "../../../lib/bdos/bdos.asm"
	include "../../../lib/string/string.asm"

SYSBUF		DS	10	
search_txt	db  "File searcher V1.1 (c) Teambomba 2004,2021",CR,LF,CR,LF,0
file_txt	db	CR,LF,"Number of matched file(s)     : ",0
dir2_txt	db	CR,LF,"Number of searched directories: ",0

path_buf	ds	MAX_PATH,0
dir_txt		db	"\\",0
dbl_point	db	"..",0
next_line	db	CR,LF,0
dir_mask	db	"????????.???",0
file_mask	db	"??????????.???",0
file_count	dw	0
dir_count	dw	0	; Current dir is always scanned

; String from this point a maximum of 31x64 bytes of space is used as MAX_PATH = 64 
fileInfoBlock	FIB	

Par rolandve

Champion (372)

Portrait de rolandve

31-03-2023, 11:08

Looking at something like that. I need to to get the address of the FIB in IX.
SDCC puts 16 bits values in HL and then in DE. FIB is the first address

u8 FindFirst(DOS_FIB* fib, c8* param)
// fib HL
// param DE
{
__asm
    ld b,0x37 // attributes
    ld c,0x40 // Find First File
    ld ix,hl // <- this instruction does not exist....
    call BDOS
__endasm;
}

So my big question is: how do I put the value of HL in IX. So each time the compiler creates a new instance of the FIB entry, I don't need to allocate memory and simple pass the address of the current FIB.

Doesn't DOS use the CWD? At the end of the routine, the programs directory is the one where the FIB read all entries. I can imagine that not going up, leave the program in the directory where your finished.

Par aoineko

Paragon (1140)

Portrait de aoineko

31-03-2023, 14:21

You can do:

push hl
pop ix

Par rolandve

Champion (372)

Portrait de rolandve

31-03-2023, 14:51

Wow, that's possible? In my head I had something like: you can only pop/push to the same registers.

Par rolandve

Champion (372)

Portrait de rolandve

31-03-2023, 23:44

Lol, I changed the code, wrote a function that transfers a newly created FIB block, recursively call the walk function and it still ends when the last entry in a subdirectory is reached. The fib pointer gets a new address each time, but it looks like the administration of MSX-DOS2 is not held in the FIB block, MSX-DOS seems to copy the data to the FIB. So the end is the end for all FIB find operations.

#include 
#include 
#include 
// Change Directory

u8 __at(0xF55E) outBuf[256];

u8 DOS_ChangeDir(const c8* path)
{
	path; // HL
__asm
	ex de,hl
	ld c, #DOS_FUNC_CHDIR      // DOS routine
	call BDOS
__endasm;
}

u8 FFirst(DOS_FIB* fib, c8* param)
// fib HL
// param DE
{
        fib; //HL
        param; //DE

__asm
    ld b,#0x37 // attributes
    ld c,#0x40 // Find First File
    push hl
    pop ix
    call BDOS
__endasm;
}

u8 FNext(DOS_FIB* fib)
// fib HL
{
    fib; //HL
__asm
    push ix
    push hl
    pop ix
    xor b // b == 0
    ld c,#0x41 // fnext
    call BDOS
    pop ix
__endasm;
}

void walk(const u8* param)
{
    u8 ret;
    Mem_Set('$',outBuf,15);
    DOS_FIB* fibStore =(DOS_FIB*)Mem_HeapAlloc(sizeof(DOS_FIB));
    ret = FFirst(fibStore,param);
    while (!ret) {
        if (fibStore->Filename[0] != '.') {
            String_Format(outBuf,"%s\n\r$",fibStore->Filename);
            DOS_StringOutput(outBuf);
            if ((fibStore->Attribute & 0x10) == 0x10 ) {
                DOS_ChangeDir(fibStore->Filename);
                walk("*.*");
            }
        }
        ret = FNext(fibStore);
    }
    Mem_HeapFree((u16)fibStore);
    DOS_ChangeDir("..");
}

void main()
{
    walk("*.*"); // this is the current directory
}

Par msd

Paragon (1532)

Portrait de msd

01-04-2023, 09:06

@rolandve: the fibs contain all the information required for the items in an directory. My code doesn’t stop in the condition yours stops. Did you check in a debugger (openmsx) what happens ?

Page 1/2
| 2