News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

CreateDirectoryPath

Started by fearless, October 01, 2011, 11:01:28 PM

Previous topic - Next topic

fearless

I was looking for a way to create a long pathname and found out CreateDirectory only creates single folder, so i wrote this function, is this the best way to do this? or is there a better way

;**************************************************************************
; Create each directory in turn from a folderpath
;**************************************************************************
CreateDirectoryPath PROC szFolderPath:DWORD
    LOCAL FolderPathToCreate[MAX_PATH]:BYTE
    push esi
    push edi

    mov esi, szFolderPath
    lea edi, FolderPathToCreate
@@:
    movzx eax, byte ptr [esi]       ; read byte from address in esi
    inc esi
    cmp al, 0                       ; test for zero
    je ExitLoop                     ; if null byte then we exit our loop
    cmp al, "\"                     ; test for "\" - means we hit an end of a folder whilst looping through each byte
    jne LoopAgain                   ; jump over if its not, otherwise create folder path up to this point

    mov byte ptr [edi], al          ; write '\' byte to address in edi as edi contains our path up to this point
    mov byte ptr [edi+1], 0         ; terminate path for the moment, whilst we create folders up to this point
    Invoke CreateDirectory, Addr FolderPathToCreate, NULL
    inc edi                         ; inc edi ready for next byte we will get going round loop again
    jmp @B
   
LoopAgain:   
    mov byte ptr [edi], al          ; write byte to address in edi
    inc edi                         ; inc edi ready for next byte we will get going round loop again
    jmp @B

ExitLoop:
    mov byte ptr [edi], 0           ; terminate path and create last folder possibly
    Invoke CreateDirectory, Addr FolderPathToCreate, NULL
    mov eax, TRUE    

    pop edi
    pop esi   
    ret
CreateDirectoryPath endp
ƒearless

Biterider

Hi
I have 2 procs in the OA32 library, one for ANSI and one for UNICODE strings. The difference with your proc is that they first search if the directory exists and if not, up to this point it starts to create the folders.
Regards, Biterider

jj2007

#2
    invoke CreateDirectoryA, addr bBuffer, NULL
    or esi, esi
    jz @@Exit
    inc esi
    jmp @@1


Don't you think a little check would be necessary here? Especially since you don't check after FindFirstFileA if the found item is really a folder (it could be a file - rare case but there are people who create files with no extension :bg)

ToutEnMasm

Very similar is this one,who made a test before create the directory:
only directory are tested,DesPath is the full path of a file.
Quote
   create_directory:
      push edi
      push esi
      invoke lstrcpy,addr repertoire_destination,addr DesPath
      lea edi,repertoire_destination
      @@:
      .if byte ptr [edi] != "\" &&  byte ptr [edi] != 0 ;pass the first e:\ minimum
         inc edi
         jmp @B
      .endif
      
      loop_create:   
      .if byte ptr [edi] == 0
         jmp findecreate
      .endif
      .if byte ptr [edi] != "\" ;chercher \
         inc edi
         jmp loop_create
      .endif      
      mov byte ptr [edi],0   ;efface le \ pour test
      invoke FindFirstFile,addr repertoire_destination,addr FindFileData
      .if eax != INVALID_HANDLE_VALUE
         invoke FindClose,eax
      .else
         invoke CreateDirectory,addr repertoire_destination,NULL
      .endif
      mov byte ptr [edi],"\"   ;efface le \ pour test   
      inc edi
      jmp loop_create
      
      findecreate:
      pop esi
      pop edi
   findecreate_directory:   


clive

My mechanism is very similar, although using GetFileAttributes() is way more effective/efficient than FindFirstFile().

http://msdn.microsoft.com/en-us/library/windows/desktop/aa364944%28v=vs.85%29.aspx

INVALID_FILE_ATTRIBUTES = -1 = INVALID_HANDLE_VALUE, it's missing from a lot of the include files. As far as I'm aware this function has existed since the dawn of Win32, not just XP (per MSDN), it's in WINBASE.H circa 1993.

You should also probably return the error code for the last CreateDirectory(), so you can catch the error rather than wait to fail with a subsequent CreateFile() call.

Also, you could use the path in-place by saving the directory marker replaced with a NUL at each iteration. This would also handle the condition where the passed in string exceeds MAX_PATH, but clearly not where the string is 'const' or in the code section. Depends on your code, and how it is called.

Consider also if you want to pass in a fully qualified path, or a network path, ie \\SERVER\Foo\Bar or C:\Foo\Bar. I mention this because it is probably cheaper to let CreateFile() fail once, then try to build out the tree, and try CreateFile() again if you are writing out a lot of subsequent files to the same directory.
It could be a random act of randomness. Those happen a lot as well.

dedndave

QuoteINVALID_FILE_ATTRIBUTES = -1 = INVALID_HANDLE_VALUE, it's missing from a lot of the include files.

also missing is the WIN32_FILE_ATTRIBUTE_DATA structure definition
WIN32_FILE_ATTRIBUTE_DATA STRUCT
  dwFileAttributes          dd ?
  ftCreationTime            FILETIME <>
  ftLastAccessTime          FILETIME <>
  ftLastWriteTime           FILETIME <>
  nFileSizeHigh             dd ?
  nFileSizeLow              dd ?
WIN32_FILE_ATTRIBUTE_DATA ENDS


expanded, it looks like this
;WIN32_FILE_ATTRIBUTE_DATA STRUCT
; dwFileAttributes dd ?
; ftCreationTime   FILETIME <>
;   dwLowDateTime    dd ?
;   dwHighDateTime   dd ?
; ftLastAccessTime FILETIME <>
;   dwLowDateTime    dd ?
;   dwHighDateTime   dd ?
; ftLastWriteTime  FILETIME <>
;   dwLowDateTime    dd ?
;   dwHighDateTime   dd ?
; nFileSizeHigh    dd ?
; nFileSizeLow     dd ?

lingo

This example will create VERY FAST a full path dirs "c:\Lingo1\Lingo2\Lingo3\" on your disk C:

.686
.xmm
.model     flat, stdcall
option     casemap:none
include    \masm32\include\windows.inc
include    \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

;****************************************
OBJECT_ATTRIBUTES          struc
  Len                      dd ?
  RootDirectory            dd ?
  ObjectName               dd ?
  Attributes               dd ?
  SecurityDescriptor       dd ?
  SecurityQualityOfService dd ?
OBJECT_ATTRIBUTES          ends
IO_STATUS_BLOCK            struc
Status                     dd ?
Pointer                    dd ?
Information                dd ?
IO_STATUS_BLOCK            ends

;****************************************
.const
OBJ_CASE_INSENSITIVE         equ 40h
FILE_DIRECTORY_FILE          equ 1 
FILE_CREATE                  equ 2 
FILE_SUPERSEDED              equ 0
FILE_EXISTS                  equ 4   
FILE_SYNCHRONOUS_IO_NONALERT equ 20h
FILE_RESERVE_OPFILTER        equ 100000h
FILE_OPEN_FOR_BACKUP_INTENT  equ 4000h

.data?
Iostatblock                  IO_STATUS_BLOCK <?>
RootDirectoryHandlez         dd ?
aNtCreateFile                dd ?
aNtClose                     dd ?   

.data
align 16
db 8 Dup(0)
RootDirectoryNamez      db "\",0,"?",0,"?",0,"\",0
Drive                   db "C",0,":",0,"\",0
DirNames                db "L",0,"i",0,"n",0,"g",0,"o",0,"1",0,"\",0   
                        db "L",0,"i",0,"n",0,"g",0,"o",0,"2",0,"\",0
                        db "L",0,"i",0,"n",0,"g",0,"o",0,"3",0,"\",0
DirNamesEx              db   (32767*2 ) Dup(0) 
align 16
Oaz dd sizeof OBJECT_ATTRIBUTES,0, RootDirectoryNameBufz,OBJ_CASE_INSENSITIVE,0,0
align 16
RootDirectoryNameBufz   db 0,0,0,0
RootNOffsetz            dd offset RootDirectoryNamez,0,0,0,0
szNTdll                 db "ntdll.dll",0
szNtCreateFile          db "ZwCreateFile",0
szNtClose               db "ZwClose",0

.code
option prologue:none
option epilogue:none
start:

  invoke GetModuleHandle, addr szNTdll
  mov    esi, eax   
  invoke GetProcAddress, esi, addr szNtCreateFile
  mov    aNtCreateFile, eax
  invoke GetProcAddress, esi, addr szNtClose
  mov    aNtClose, eax

  mov  esi, offset DirNames
  call CreatePath          ; return 0 if success or -1 if error
  push 0
  jmp  ExitProcess

align 8
CreatePath:
  cmp  word ptr [esi],0    ; Looking for "\" or zero
  je   Exit
  cmp  byte ptr [esi],"\"
  jne  @f
  mov  byte ptr [esi], 0   ; esi->current offset in Buffer
  mov  eax, esi
  sub  eax, offset RootDirectoryNamez
  mov  dword ptr RootDirectoryNameBufz, eax
  add  eax, 2
  mov  word ptr RootDirectoryNameBufz+2, ax
  call CreateDir
  mov  byte ptr [esi], "\"
  test eax, eax            ; STATUS_SUCCESS equ 0
  jne  ErrorExit
@@:
  add  esi,1
  jne  CreatePath
Exit:
  xor  eax, eax
  ret     
ErrorExit:
  or   eax, -1
  ret     
align 8
CreateDir:
   push 0
   push 0   
   push FILE_DIRECTORY_FILE or FILE_SYNCHRONOUS_IO_NONALERT or FILE_OPEN_FOR_BACKUP_INTENT
   push FILE_CREATE
   push FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE
   push FILE_ATTRIBUTE_DIRECTORY
   push 0
   push offset Iostatblock
   push offset Oaz
   push FILE_LIST_DIRECTORY or FILE_RESERVE_OPFILTER
   push offset RootDirectoryHandlez
   call aNtCreateFile
   test eax, eax     ; STATUS_SUCCESS equ 0
   jne  Not1
   push RootDirectoryHandlez
   call aNtClose
Yes1:
   xor  eax, eax     ; STATUS_SUCCESS equ 0
   ret
Not1:
   cmp  Iostatblock.Information, FILE_SUPERSEDED
   je   Yes1
   cmp  Iostatblock.Information, FILE_EXISTS
   je   Yes1
   ret
end start


jj2007

Quote from: clive on October 02, 2011, 10:30:21 AM
My mechanism is very similar, although using GetFileAttributes() is way more effective/efficient than FindFirstFile().

Good hint, Clive :U

Here is my implementation - 110 bytes, less than 7 paras, .const section-proof, and it does fail if there is a file around with a desired folder name.

include \masm32\include\masm32rt.inc
.data
ThePath   db "\masm32\examples\NoSuchFolder\One more", 0   ; if a file with this name is present, the algo will fail
.code
start:
   push offset ThePath
   
call MbMkDir
   .if eax!=16
      print str$(eax), " - failed!!", 13, 10
      mov eax, LastError$()
      print eax, 9, " - but check for files with the same name!!", 13, 10
   .else
      print str$(eax), " - success!!", 13, 10
   .endif
   inkey "Bye"
   exit

MbMkDir proc   ; arg is pPath; returns 16 if success, zero for failure
   push esi
   
push edi
   
push ebx
   
push ecx
   
mov ebx, esp   ; shortest solution
   
sub esp, MAX_PATH   ; create a buffer for the copy
   mov edi, esp   ; edi points to buffer
   invoke lstrcpy, edi, [ebx+4*4+4]   ; src=pPath
   xchg ecx, len(edi)   ; set counter for scasb
   push edi
   
pop esi
NxtFile:   mov al, "\"
   repne scasb   ; find the next backslash
   dec edi
   .if byte ptr [edi-1]!=":" && esi!=edi   ; drives and leading \ should be ignored
      .if !ecx && [edi]!=al   ; last element may or may not have backslash
         inc edi
      .endif
      mov byte ptr [edi], 0   ; zero-delimit current path
      push ecx
      invoke GetFileAttributes, esi
      pop ecx
      .if eax==INVALID_FILE_ATTRIBUTES   ; folder not found
         push ecx
         invoke CreateDirectory, esi, 0
         pop ecx
         
test eax, eax
         je @Err
         mov al, 16   ; folder successfully created
      .endif
      and eax, 16   ; really a folder?
      je @Err
      mov byte ptr [edi], "\"   ; restore full path
   .endif
   inc edi
   test ecx, ecx
   ja NxtFile
@Err:   mov esp, ebx
   pop ecx
   pop ebx
   
pop edi
   
pop esi
   retn 4
MbMkDir endp



@Lingo: If I ever need to create a Million folders in less than 10 milliseconds, can I use your algo, or is it copyrighted?

hutch--

I wonder why you need to have a "fail" if the directory already exists ? What is wrong with the idea of doing a "change directory" first and if that fails, create the new directory ?

When you are finished playing games, try this Shell API.



; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤
    include \masm32\include\masm32rt.inc
; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

comment * ------------------------------------------------------------------------------------------
                   Use the MASM32 Dialog Help from the HELP menu as reference
                   when building dialog applications using this type of code
        ------------------------------------------------------------------------------------------ *

      DlgProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
      enable_common_controls PROTO

    .data?
      hWnd      dd ?
      hInstance dd ?

    .code

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

start:
    invoke enable_common_controls
    mov hInstance, rv(GetModuleHandle,NULL)
    call main
    invoke ExitProcess,eax

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

main proc

    Dialog "SHCreateDirectoryEx Test", \                ; caption
           "MS Sans Serif",10, \            ; font,pointsize
            WS_OVERLAPPED or \              ; styles for
            WS_SYSMENU or DS_CENTER, \      ; dialog window
            3, \                            ; number of controls
            50,50,155,100, \                ; x y co-ordinates
            1024                            ; memory buffer size

    DlgButton "Build Tree",WS_TABSTOP,106,5,40,13,IDOK
    DlgButton "Cancel",WS_TABSTOP,106,20,40,13,IDCANCEL
    DlgStatic "Cick the Build Tree Button",SS_LEFT,5,5,90,9,100

    CallModalDialog hInstance,0,DlgProc,NULL

    ret

main endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

DlgProc proc hWin:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

    LOCAL pbuf  :DWORD
    LOCAL buffer[260]:BYTE

    Switch uMsg
      Case WM_INITDIALOG
        invoke SendMessage,hWin,WM_SETICON,1,rv(LoadIcon,NULL,IDI_ASTERISK)
        m2m hWnd, hWin
        return 1

      Case WM_COMMAND
        Switch wParam
          Case IDOK
            mov pbuf, ptr$(buffer)
            invoke GetCurrentDirectory,260,pbuf
            mov pbuf, cat$(pbuf,"\test1\test2\test3\test4")
            invoke SHCreateDirectoryEx,hWin,pbuf,NULL

            mov pbuf, ptr$(buffer)
            invoke GetCurrentDirectory,260,pbuf
            mov pbuf, cat$(pbuf,"\test1\test2\test3\test44")
            invoke SHCreateDirectoryEx,hWin,pbuf,NULL

            mov pbuf, ptr$(buffer)
            invoke GetCurrentDirectory,260,pbuf
            mov pbuf, cat$(pbuf,"\test1\test2\test3\test444")
            invoke SHCreateDirectoryEx,hWin,pbuf,NULL

            fn MessageBox,hWin,"The game is done, I've won I've won quote she and whistled thrice.","Coleridge",MB_OK

          Case IDCANCEL
            jmp quit_dialog
        EndSw
      Case WM_CLOSE
        quit_dialog:
         invoke EndDialog,hWin,0
    EndSw

    return 0

DlgProc endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

enable_common_controls proc

    LOCAL icce:INITCOMMONCONTROLSEX

  ; --------------------------------------
  ; comment out the styles you don't need.
  ; --------------------------------------
    mov icce.dwSize, SIZEOF INITCOMMONCONTROLSEX            ; set the structure size
    xor eax, eax                                            ; set EAX to zero
;     or eax, ICC_ANIMATE_CLASS                               ; OR as many styles as you need to it
;     or eax, ICC_BAR_CLASSES                                 ; comment out the rest
;     or eax, ICC_COOL_CLASSES
;     or eax, ICC_DATE_CLASSES
;     or eax, ICC_HOTKEY_CLASS
;     or eax, ICC_INTERNET_CLASSES
;     or eax, ICC_LISTVIEW_CLASSES
;     or eax, ICC_PAGESCROLLER_CLASS
;     or eax, ICC_PROGRESS_CLASS
;     or eax, ICC_TAB_CLASSES
;     or eax, ICC_TREEVIEW_CLASSES
;     or eax, ICC_UPDOWN_CLASS
;     or eax, ICC_USEREX_CLASSES
;     or eax, ICC_WIN95_CLASSES
    mov icce.dwICC, eax
    invoke InitCommonControlsEx,ADDR icce                   ; initialise the common control library
  ; --------------------------------------

    ret

enable_common_controls endp

; ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤

end start


:bg
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

jj2007

Quote from: hutch-- on October 03, 2011, 12:03:53 AM
I wonder why you need to have a "fail" if the directory already exists ?

Creating C:\WINDOWS\system32\drivers\etc\hosts\One more folder: 0 - failed!!
Creating C:\WINDOWS\system32\drivers\etc\hosts2\One more folder: 16 - success!!

Try the same with the other algos posted.

dedndave

QuoteC:\WINDOWS\system32\drivers\etc

i might be more inclined to test it on a more benign folder, however
a bug in your code might do some damage   :P

clive

Quote from: dedndave
QuoteC:\WINDOWS\system32\drivers\etc
i might be more inclined to test it on a more benign folder, however a bug in your code might do some damage   :P

I should post my aggressive tree pruning counter-play to CreateDirectoryPath(), deletes everything in the tree, then the directory itself, much hilarity will ensue...

Back in the 80's we had a "DEL *.*" and "FORMAT C:" award for the employee of the week who managed to cause the most self-inflicted damage to their computer system. The computer equivalent to the Darwin Award.
It could be a random act of randomness. Those happen a lot as well.