I've been searching the board for a recursive file search algo. I have found several, but all of them fail when I change the file mask from *.* to something else. When changing it to *.txt for example, it won't find any directories, only files, something that precludes the algo from being recursive. Does anyone have a solution to this problem?

edit:
Here is an example of a recursive search that don't work with another file mask:
http://www.asmcommunity.net/board/showthread.php?threadid=8879

/Delight
Posted on 2003-07-03 16:22:23 by Delight
Hi Delight,

This isn't very elegant but it's what I wrote to do that. GoAsm syntax:
;########################

; FindFile by Donkey v g0.6
; Syntax:
; invoke FindFile,pszRootDir,pszFileName,Recurse,CallBack
;
; pszRootDir = The directory to start the search in example "C:\WinNT",0
; pszFileName = The filename to search for (wildcards allowed) example "*.exe",0
; Recurse = Recurse directories (TRUE or FALSE)
; CallBack = The address of a callback routine to process the filenames:
;
; FindFileCallBack FRAME pszFileName
; Return TRUE to continue to next file
; Return FALSE to exit routine
; FindFile will exit when there are no more files meeting the file spec
;##############################

FindFile FRAME pszRootDir, pszFileName, Recurse, CallBack
LOCAL hRead :D
LOCAL hWrite :D
LOCAL bytesRead :D
LOCAL hHeap :D
LOCAL pOutput :D
LOCAL pCommand :D
LOCAL pSysDir :D
LOCAL sui :STARTUPINFO
LOCAL pi :PROCESS_INFORMATION
LOCAL sa :SECURITY_ATTRIBUTES

invoke GetProcessHeap
mov [hHeap],eax
invoke HeapAlloc, [hHeap], HEAP_ZERO_MEMORY, 1024
mov [pOutput],eax
invoke HeapAlloc, [hHeap], HEAP_ZERO_MEMORY, 1024
mov [pCommand],eax
invoke HeapAlloc, [hHeap], HEAP_ZERO_MEMORY, 1024
mov [pSysDir],eax

invoke GetVersion

cmp al,5
jge >XP
invoke GetWindowsDirectoryA,[pSysDir],MAX_PATH
cmp D[Recurse],TRUE
jne >.Recurse1
push <"/s/b",0>
push [pszFileName]
push [pszRootDir]
push [pSysDir]
push <"%s\Command.com /C DIR ",22H,"%s\%s",22H," %s",0>
push [pCommand]
call wsprintfA
jmp >done
.Recurse1
push <"/b",0>
push [pszFileName]
push [pszRootDir]
push [pSysDir]
push <"%s\Command.com /C DIR ",22H,"%s\%s",22H," %s",0>
push [pCommand]
call wsprintfA
jmp >done
XP:
invoke GetSystemDirectoryA,[pSysDir],MAX_PATH
cmp D[Recurse],TRUE
jne >.Recurse2
push <"/s/b",0>
push [pszFileName]
push [pszRootDir]
push [pSysDir]
push <"%s\CMD.EXE /C DIR ",22H,"%s\%s",22H," %s",0>
push [pCommand]
call wsprintfA
jmp >done
.Recurse2
push <"/b",0>
push [pszFileName]
push [pszRootDir]
push [pSysDir]
push <"%s\CMD.EXE /C DIR ",22H,"%s\%s",22H," %s",0>
push [pCommand]
call wsprintfA
done:
add esp,24 ; wsprintfA is a C call so take care of the stack

mov D[sa.nLength],sizeof SECURITY_ATTRIBUTES
mov D[sa.lpSecurityDescriptor],NULL
mov D[sa.bInheritHandle],TRUE

push NULL
lea eax,sa
push eax
lea eax,hWrite
push eax
lea eax,hRead
push eax
call CreatePipe
cmp eax,NULL
jne >.PipeGood
mov edi,-1
jmp >EXITNOPIPE
.PipeGood

mov D[sui.cb],sizeof STARTUPINFO
lea eax,sui
push eax
call GetStartupInfoA
mov eax,[hWrite]
mov D[sui.hStdOutput],eax
mov D[sui.hStdError],eax
mov D[sui.dwFlags],STARTF_USESHOWWINDOW+STARTF_USESTDHANDLES
mov W[sui.wShowWindow],SW_HIDE

lea eax,pi
push eax
lea eax,sui
push eax
push NULL
push NULL
push NULL
push TRUE
push NULL
push NULL
push [pCommand]
push NULL
call CreateProcessA
cmp eax,NULL
jne >.ProcessGood
invoke CloseHandle, [hWrite]
mov edi,-1
jmp >EXITNOPROC
.ProcessGood
invoke CloseHandle,[hWrite]

mov eax,1
.while
mov ecx,256
mov eax,0
mov edi,[pOutput]
rep stosd
push NULL
lea eax,bytesRead
push eax
push 1023
push [pOutput]
push [hRead]
call ReadFile
cmp eax,NULL
jne >.else
jmp >EXITALL
.else
mov edi,[pOutput]
mov al,[edi]
cmp al,"0"
jb >.NoFile
push [pOutput]
call [CallBack]
.NoFile
cmp eax,FALSE
jne .while

EXITALL:
mov edi,0
invoke CloseHandle,[pi.hProcess]
invoke CloseHandle,[pi.hThread]
EXITNOPROC:
invoke CloseHandle,[hRead]
invoke CloseHandle, [hHeap]
EXITNOPIPE:
invoke HeapFree, [hHeap], NULL, [pOutput]
invoke HeapFree, [hHeap], NULL, [pCommand]
invoke HeapFree, [hHeap], NULL, [pSysDir]

mov eax,edi
ret
FindFile endf
Posted on 2003-07-03 16:43:31 by donkey
Hi Delight

Here is one that filters asm & inc files.

KetilO
Posted on 2003-07-03 17:59:06 by KetilO
This is the MASM translation of the FindFile routine, it works the same as the GoAsm version:
FindFile Proc pszRootDir:DWORD, pszFileName:DWORD, Recurse:DWORD, CallBack:DWORD

LOCAL hRead :DWORD
LOCAL hWrite :DWORD
LOCAL bytesRead :DWORD
LOCAL hHeap :DWORD
LOCAL pOutput :DWORD
LOCAL pCommand :DWORD
LOCAL pSysDir :DWORD
LOCAL Recursion :DWORD
LOCAL CommandLine :DWORD
LOCAL startupinfo :STARTUPINFO
LOCAL pi :PROCESS_INFORMATION
LOCAL sa :SECURITY_ATTRIBUTES

.data
szCmdNT BYTE "%s\CMD.EXE /C DIR ",22H,"%s\%s",22H," %s",0
szCmd9x BYTE "%s\Command.com /C DIR ",22H,"%s\%s",22H," %s",0
szRecurse BYTE "/s/b",0
szNoRecurse BYTE "/b",0
.code

invoke GetProcessHeap
mov hHeap,eax
invoke HeapAlloc, hHeap, HEAP_ZERO_MEMORY, 2048
mov pOutput,eax
add eax,1024
mov pCommand,eax
add eax,512
mov pSysDir,eax

invoke GetVersion
and eax,080000000h
.IF eax
invoke GetWindowsDirectory,pSysDir,MAX_PATH
mov CommandLine,OFFSET szCmd9x
.ELSE
invoke GetSystemDirectory,pSysDir,MAX_PATH
mov CommandLine,OFFSET szCmdNT
.ENDIF

.IF Recurse==TRUE
mov Recursion,OFFSET szRecurse
.ELSE
mov Recursion,OFFSET szNoRecurse
.ENDIF

invoke wsprintf, pCommand, CommandLine, pSysDir, pszRootDir, pszFileName, Recursion

mov sa.nLength,sizeof SECURITY_ATTRIBUTES
mov sa.lpSecurityDescriptor,NULL
mov sa.bInheritHandle,TRUE

invoke CreatePipe, addr hRead, addr hWrite, addr sa, NULL
.IF eax==NULL
jmp EXITNOPIPE
.ENDIF

mov startupinfo.cb,sizeof STARTUPINFO
invoke GetStartupInfo,addr startupinfo
mov eax,hWrite
mov startupinfo.hStdOutput,eax
mov startupinfo.hStdError,eax
mov startupinfo.dwFlags,STARTF_USESHOWWINDOW+STARTF_USESTDHANDLES
mov startupinfo.wShowWindow,SW_HIDE

invoke CreateProcess, NULL, pCommand, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pi
.IF eax==NULL
invoke CloseHandle,hWrite
jmp EXITNOPROC
.ENDIF
invoke CloseHandle,hWrite

mov eax,1
.WHILE eax
mov ecx,256
mov eax,0
mov edi,pOutput
rep stosd
invoke ReadFile, hRead, pOutput, 1023, addr bytesRead, NULL
.IF eax==NULL
.break
.ELSE
mov edi,pOutput
mov al,[edi]

.IF al >= "0"
push pOutput
call CallBack
.ENDIF
.ENDIF
.ENDW

EXITALL:
invoke CloseHandle,pi.hProcess
invoke CloseHandle,pi.hThread
EXITNOPROC:
invoke CloseHandle,hRead
EXITNOPIPE:
invoke HeapFree, hHeap, NULL, pOutput
invoke HeapFree, hHeap, NULL, pCommand
invoke HeapFree, hHeap, NULL, pSysDir
invoke CloseHandle, hHeap
ret
FindFile endp
Posted on 2003-07-03 23:09:28 by donkey
Donkey: Thanks, very interesting code but it feels a little too "dirty" to use in my program.
KetilO: What i'm looking for is an algorithm that can handle more advanced file masks like "ma*.htm*". Thanks anyway.

I have almost solved it, but I have found a really strange bug:



SearchFilesWithMask proc Path:DWORD,Msk:DWORD,Recur:DWORD

;For the directory search:
LOCAL wc:WIN32_FIND_DATA
LOCAL hFind:DWORD
LOCAL tBuff[MAX_PATH]:BYTE
LOCAL tBuff2[MAX_PATH]:BYTE

;For the file search:
LOCAL Fwc:WIN32_FIND_DATA
LOCAL FhFind:DWORD
LOCAL FtBuff2[MAX_PATH]:BYTE


invoke lstrcpy,addr tBuff,Path
invoke lstrcat,addr tBuff, offset MskAll
invoke FindFirstFile,addr tBuff, addr wc

.if eax!=INVALID_HANDLE_VALUE
mov hFind,eax

.while eax !=0
.if byte ptr wc.cFileName != '.'
.if wc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
invoke lstrcpy,addr tBuff2,Path
invoke lstrcat,addr tBuff2,addr wc.cFileName
invoke lstrcat,addr tBuff2,addr sls
Call SearchFiles

.if Recur
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;REMOVE THIS LINE AND IT WILL HANG (???);;;;
PrintText "Recursive start" ;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


invoke SearchFilesWithMask,addr tBuff2,Msk,Recur


.endif
.endif
.endif

invoke FindNextFile,hFind,addr wc
.endw

.endif
invoke FindClose,hFind
ret

SearchFiles:

invoke lstrcpy,addr FtBuff2,addr tBuff2
invoke lstrcat,addr FtBuff2, Msk ;sk?t detta snyggare
invoke FindFirstFile,addr FtBuff2, addr Fwc

.if eax!=INVALID_HANDLE_VALUE
mov FhFind,eax
.while eax !=0
.if byte ptr Fwc.cFileName != '.'
.if !(Fwc.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
;----------+
;FILE HERE |
;----------+------------------ - - - - -
invoke SendDlgItemMessage,hWnd,1002,LB_ADDSTRING,0,addr [Fwc].cFileName
;----------------------------- - - - - -

.endif
.endif
invoke FindNextFile,FhFind,addr Fwc
.endw
.endif
invoke FindClose, FhFind
RETN
SearchFilesWithMask endp





The thing is that it works quite OK as long as I have the start directory set to the root of a drive. When changing it to a subfolder, the program freeze. The strange thing is that if I add a PrintText "anything" just before the recursion, it works perfectly. I can't figure out why, so I have attached a small RadASM projects if someone have the time to play with it and perhaps find a solution.
Posted on 2003-07-04 05:24:53 by Delight
Delight,
I coded similar example and here is the algo:


Find_First:
push offset W32FD
push offset szFullPathwithMask ; lp to curr full path string with \ and mask * at the end
call FindFirstFile ; call API
mov esi, eax ; esi->hFindFirstFile
inc eax ;
jz NtRet ;
FileOrDir:
test W32FD.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY
jz ProcessFile ; it is file ->process it and jump to Find_Next
cmp W32FD.cFileName, "." ;
jz Find_Next ; skip it
push esi ; save esi-> current hFindFirstFile
push offset RestoreH ; return address
jmp ProcessDirectory ; it is dir ->process it and jump to Find_First
RestoreH:
pop esi ; restore previous esi>hFindFirstFile
Find_Next:
push offset W32FD
push esi ; esi-> hFindFirstFile
call FindNextFile
test eax, eax
jnz FileOrDir
push esi ; esi-> current hFindFirstFile
call FindClose
NtRet:
ret ; return to RestoreH: or to caller

ProcessDirectory:
; Update szFullPathwithMask ; lp to curr full path string with \ and mask * at the end
; Example:
; before c:\windows\help\* ; always " * " !!!
; after c:\windows\help\tour\*
; or
; before c:\windows\help\tour\*
; after c:\windows\help\*
[B]Note:[/B] We don't use SetCurrentDirectory() API because of that!!!

; call CheckMatchDirFilter
; test eax, eax
; jz SkipDir
; Print it or what you want
SkipDir:
jmp Find_First

ProcessFile:
; call CheckMatchFileFilter
; test eax, eax
; jz SkipFile
; Print it or what you want
SkipFile:
jmp Find_Next

We don't use second and/or third FindFirstFile API and
it is pro because it is much faster and we have additional
functionality

We use one general filter " * " (don't touch it) and two
user defined filters, for file name and/or dir name

CheckMatchDirFilter and CheckMatchFileFilter
are cons (the price of speed) and something like that:

FilterDirectory db "pro*de?re",0 ; stupid user defined dir filter
Directory db "Program Files", 0 ; current dir name

FilterFile db "Le*a?re",0 ; stupid user defined file filter
FileName db "Levantare" ,0 ; current file name

mov eax, offset FilterFile
mov edx, offset FileName
L_1:
mov cl, [eax]
inc eax
test cl, cl
jz Match
cmp cl, "*"
je L_3
cmp cl, "?"
je L_2
cmp cl, [edx]
jne NotMatch
L_2:
inc edx
cmp byte ptr [edx],0
jne L_1
Match:
mov eax, 1
ret
;StarFound
L_3:
mov cl, [eax]
inc eax
test cl, cl
jz Match
cmp cl, "?"
je L_3
cmp cl, "*"
je L_3
L_4:
cmp cl, [edx]
je L_2 ; Continue
inc edx
cmp byte ptr [edx],0
jne L_4
NotMatch:
xor eax, eax
ret

As you see not a big deal ...

Regards,
Lingo
Posted on 2003-07-05 16:27:19 by lingo12
Thank you lingo12 :) Nice code!
Posted on 2003-07-06 10:58:38 by Delight
Thanks Delight,

Here is an example (without filters)

Regards,
Lingo
Posted on 2003-07-06 11:41:29 by lingo12
Great example :alright: . Thanks again :)
Posted on 2003-07-06 13:31:18 by Delight
lingo12
Your code contains the wrong suggestion
the name of the directory can begin with points
necessary to compare to {'.',0} and {'..',0}
Posted on 2003-07-06 22:17:30 by P2M
Thanks P2M,
imho, it is not wrong
{'.'} is the first byte of {..} and
I compare 1st byte '.' only
and it is enough
We have no other stuff in the directory
begining with ".", just "." and ".."
Can you create file or dir with name ".P2M"?
or "...P2M"?

As you saw I don't use strcmp()
and don't need zeroes because of that

Regards,
Lingo
Posted on 2003-07-06 23:09:25 by lingo12
lingo12
Can you create file or dir with name ".P2M"? or "...P2M"?
I have done this on w2k and w98se.
Posted on 2003-07-07 00:14:50 by P2M
Ok, I'll correct it

Thanks

Lingo
Posted on 2003-07-07 07:29:12 by lingo12
P2M,
Here is updated version

Thanks
Lingo
Posted on 2003-07-07 18:12:53 by lingo12
I have tried to make recursive file search with anymask you want.I have tested on Win98 SE and it worked.I dont know if it works for you anyway hope you like it.



.data
gMask db "*",0
sls db "\",0
;usage invoke SearchFiles,CTEXT("C:\NCDTREE\"),CTEXT("*.zip"),0,1

SearchFiles PROC szPath:DWORD,szMask:DWORD,pFunc:DWORD,bRec:DWORD
LOCAL wc:WIN32_FIND_DATA
LOCAL hFind:DWORD
LOCAL tBuff[2*MAX_PATH]:BYTE
LOCAL tBuff2[2*MAX_PATH]:BYTE


invoke lstrcpy,addr tBuff,szPath
invoke lstrcat,addr tBuff,addr gMsk ;X:\dir\*.*
invoke FindFirstFile,addr tBuff, addr wc
cmp eax,INVALID_HANDLE_VALUE
jz @nofile
mov hFind,eax
test eax,eax
jz @endsearch

@search:
cmp byte ptr wc.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
jnz @next ;@passed
cmp byte ptr wc.cFileName,'.'
jz @next

invoke lstrcpy,addr tBuff2, szPath
invoke lstrcat,addr tBuff2,addr wc.cFileName
invoke lstrcat,addr tBuff2,addr sls
;-- --------------------------<<<<>>>>>>>>>>>>>>>>>>>>
;callback for directory if you want
; invoke MessageBox,0,addr tBuff2,0,MB_OK

;----------------------------<<<<>>>>>>>>>>>>>>>>>>>>
cmp bRec,0
jz @next
invoke SearchFiles,addr tBuff2,szMask,pFunc,bRec

@next:
invoke FindNextFile,hFind,addr wc
or eax,eax
jne @search
invoke FindClose,hFind ;finished searching subdirs

;---------------------------------------------------------
invoke lstrcpy,addr tBuff,szPath
invoke lstrcat,addr tBuff, szMask
invoke FindFirstFile,addr tBuff, addr wc
cmp eax,INVALID_HANDLE_VALUE
jz @nomaskedfile
mov hFind,eax
test eax,eax
jz @maskend

@searchmask:
invoke lstrcpy,addr tBuff2, szPath
invoke lstrcat,addr tBuff2,addr wc.cFileName
cmp byte ptr wc.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
jz @nope ;we can have some dirs with mask
;---------------------------------------
;callback function for file
invoke MessageBox,0,addr tBuff2,0,MB_OK
;---------------------------------------

@nope:
invoke FindNextFile,hFind,addr wc
or eax,eax
jne @searchmask
invoke FindClose,hFind ;finished searching subdirs
jmp @endsearch

@nomaskedfile:
@nofile:
xor eax,eax
dec eax
@maskend:
@endsearch:
ret
SearchFiles ENDP
Posted on 2003-07-10 18:27:22 by LaptoniC
I have also coded little fast and small version of this proc but it needs shlwapi.dll



SearchFiles PROC szPath:DWORD,szMask:DWORD,pFunc:DWORD,bRec:DWORD
LOCAL wc:WIN32_FIND_DATA
LOCAL hFind:DWORD
LOCAL tBuff[2*MAX_PATH]:BYTE
LOCAL tBuff2[2*MAX_PATH]:BYTE

invoke lstrcpy,addr tBuff,szPath
invoke lstrcat,addr tBuff, addr gMsk ;-------------->
invoke FindFirstFile,addr tBuff, addr wc
cmp eax,INVALID_HANDLE_VALUE
jz @nofile
mov hFind,eax
test eax,eax
jz @endsearch

@search:
invoke lstrcpy,addr tBuff2, szPath
invoke lstrcat,addr tBuff2,addr wc.cFileName
cmp byte ptr wc.dwFileAttributes,FILE_ATTRIBUTE_DIRECTORY
jnz @itisfile ;@passed
cmp byte ptr wc.cFileName,'.'
jz @next
invoke lstrcat,addr tBuff2,addr sls

;------------------------------callback for directory-----------------
invoke MessageBox,0,addr tBuff2,0,MB_OK
;------------------------------callback for directory-----------------

cmp bRec,0
jz @next
invoke SearchFiles,addr tBuff2,szMask,0,bRec
jmp @next

@itisfile:
invoke PathMatchSpec,addr wc.cFileName,szMask ;requires shlwapi.dll
test eax,eax
jz @next

;------------------------------callback for file-----------------
invoke MessageBox,0,addr tBuff2,0,MB_OK
;------------------------------callback for file----------------- -

@next:
invoke FindNextFile,hFind,addr wc
test eax,eax
jnz @search
invoke FindClose,hFind

@endsearch:
; xor eax,eax
@nofile:
ret
SearchFiles ENDP
Posted on 2003-07-11 08:34:47 by LaptoniC

lingo12
Can you create file or dir with name ".P2M"? or "...P2M"?
I have done this on w2k and w98se.

Posted on 2003-07-11 19:07:20 by QvasiModo
LaptoniC,

Thank you for sharing your code
It will be better to have an example with it
just to test the speed.

As I stated above it is faster to use FindFirstFile
and FindNextFile ONLY.
Here is my updated example with filters



QvasiModo
from dos

Regards,
Lingo
Posted on 2003-07-11 22:02:27 by lingo12
explorer doesn't seem to want to rename files to something starting with . ("enter a filename").
Then again, there's always a shell:
07/12/2003 03:26p 0 .test

Need to look at 3 chars (4 compares) instead of one. Oh well :)
Posted on 2003-07-12 08:31:55 by Jan Wassenberg
lingo12 I didnt said it is faster than yours just faster than my previous version :) I am not good on speed issues :tongue: Sorry for my bad english.I posted this two because your first proc covers just all files and directories.For example if I change mask to *.exe in your first proc it will find only exe files in the C:\ and it stops.Frankly,I prefer codes that I understand at first glance, and which I can pass arguments to it.I am connected to net with GPRS phone and these days it is very bad.So I cant upload anything it just resets after some time.It is my second try for this post.However I really want to see how I can optimize it and what score it gets.Best regards

EDIT:I have looked your new proc and saw that you are using your own filter function for file (CheckFilter:).I guess it doesnt handle multiple filters ie *.asm;*.inc

Please if you update your source,could you make more understanble for newbies like me.I dont like to play with stack :)
Posted on 2003-07-12 08:34:54 by LaptoniC