;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                              ;;
;; Contains ext2 initialization, plus syscall handling code.    ;;
;;                                                              ;;
;; Copyright (C) KolibriOS team 2013-2015. All rights reserved. ;;
;; Distributed under terms of the GNU General Public License    ;;
;;                                                              ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

$Revision: 5363 $


include 'ext2.inc'
include 'blocks.inc'
include 'inode.inc'
include 'resource.inc'

iglobal
align 4
ext2_user_functions:
        dd      ext2_free
        dd      (ext2_user_functions_end - ext2_user_functions - 4) / 4
        dd      ext2_Read
        dd      ext2_ReadFolder
        dd      ext2_Rewrite
        dd      ext2_Write
        dd      ext2_SetFileEnd
        dd      ext2_GetFileInfo
        dd      ext2_SetFileInfo
        dd      0
        dd      ext2_Delete
        dd      ext2_CreateFolder
ext2_user_functions_end:
endg

;--------------------------------------------------------------------- 
; Locks up an ext2 partition.
; Input:        ebp = pointer to EXTFS.
;---------------------------------------------------------------------
proc ext2_lock
        lea     ecx, [ebp + EXTFS.lock]
        jmp     mutex_lock
endp

;--------------------------------------------------------------------- 
; Unlocks up an ext2 partition.
; Input:        ebp = pointer to EXTFS.
;---------------------------------------------------------------------
proc ext2_unlock
        lea     ecx, [ebp + EXTFS.lock]
        jmp     mutex_unlock
endp

;---------------------------------------------------------------------
; Check if it's a valid ext* superblock.
; Input:        ebp:       first three fields of PARTITION structure.
;               ebx + 512: points to 512-bytes buffer that can be used for anything.
; Output:       eax:       clear if can't create partition; set to EXTFS otherwise.
;---------------------------------------------------------------------
proc ext2_create_partition
        push    ebx
        cmp     dword [esi+DISK.MediaInfo.SectorSize], 512
        jnz     .fail

        mov     eax, 2                          ; Superblock starts at 1024-bytes.
        add     ebx, 512                        ; Get pointer to fs-specific buffer.
        call    fs_read32_sys
        test    eax, eax
        jnz     .fail

        ; Allowed 1KiB, 2KiB, 4KiB, 8KiB.
        cmp     [ebx + EXT2_SB_STRUC.log_block_size], 3  
        ja      .fail

        cmp     [ebx + EXT2_SB_STRUC.magic], EXT2_SUPER_MAGIC
        jne     .fail

        cmp     [ebx + EXT2_SB_STRUC.state], EXT2_VALID_FS
        jne     .fail

        ; Can't have no inodes per group.
        cmp     [ebx + EXT2_SB_STRUC.inodes_per_group], 0
        je      .fail

        ; If incompatible features required, unusable superblock.
        mov     eax, [ebx + EXT2_SB_STRUC.feature_incompat] 
        test    eax, not EXT4_FEATURE_INCOMPAT_SUPP
        jz      .setup

    .fail:
        ; Not a (valid/usable) EXT2 superblock.
        pop     ebx
        xor     eax, eax
        ret

    .setup:
        movi    eax, sizeof.EXTFS
        call    malloc
        test    eax, eax
        jz      ext2_create_partition.fail

        ; Store the first sector field. 
        mov     ecx, dword[ebp + PARTITION.FirstSector]
        mov     dword[eax + EXTFS.FirstSector], ecx
        mov     ecx, dword [ebp + PARTITION.FirstSector+4]
        mov     dword [eax + EXTFS.FirstSector+4], ecx

        ; The length field.
        mov     ecx, dword[ebp + PARTITION.Length]
        mov     dword[eax + EXTFS.Length], ecx
        mov     ecx, dword[ebp + PARTITION.Length+4]
        mov     dword[eax + EXTFS.Length+4], ecx

        ; The disk field.
        mov     ecx, [ebp + PARTITION.Disk]
        mov     [eax + EXTFS.Disk], ecx

        mov     [eax + EXTFS.FSUserFunctions], ext2_user_functions

        push    ebp esi edi

        mov     ebp, eax
        lea     ecx, [eax + EXTFS.lock]
        call    mutex_init

        ; Copy superblock from buffer to reserved memory.
        mov     esi, ebx
        lea     edi, [ebp + EXTFS.superblock]
        mov     ecx, 512/4
        rep movsd

        ; Get total groups.
        mov     eax, [ebx + EXT2_SB_STRUC.blocks_count]
        sub     eax, [ebx + EXT2_SB_STRUC.first_data_block]
        dec     eax
        xor     edx, edx
        div     [ebx + EXT2_SB_STRUC.blocks_per_group]
        inc     eax
        mov     [ebp + EXTFS.groups_count], eax

        ; Get log(block_size), such that 1,2,3,4 equ 1KiB,2KiB,4KiB,8KiB.
        mov     ecx, [ebx + EXT2_SB_STRUC.log_block_size]
        inc     ecx
        mov     [ebp + EXTFS.log_block_size], ecx

        ; 512-byte blocks in ext2 blocks.
        mov     eax, 1
        shl     eax, cl
        mov     [ebp + EXTFS.count_block_in_block], eax

        ; Get block_size/4 (we'll find square later).
        shl     eax, 7
        mov     [ebp + EXTFS.count_pointer_in_block], eax
        mov     edx, eax

        ; Get block size.
        shl     eax, 2
        mov     [ebp + EXTFS.block_size], eax

        ; Save block size for 2 kernel_alloc calls.
        push    eax eax

        mov     eax, edx
        mul     edx
        mov     [ebp + EXTFS.count_pointer_in_block_square], eax

        ; Have temporary block storage for get_inode procedure, and one for global procedure.
        KERNEL_ALLOC [ebp + EXTFS.ext2_save_block], .error
        KERNEL_ALLOC [ebp + EXTFS.ext2_temp_block], .error
        
        mov     [ebp + EXTFS.partition_flags], 0x00000000
        mov     eax, [ebx + EXT2_SB_STRUC.feature_ro_compat]
        and     eax, not EXT2_FEATURE_RO_COMPAT_SUPP
        jnz     .read_only

        mov     eax, [ebx + EXT2_SB_STRUC.feature_incompat]
        and     eax, EXT4_FEATURE_INCOMPAT_W_NOT_SUPP
        jz      @F

    .read_only:
        ; Mark as read-only.
        or      [ebp + EXTFS.partition_flags], EXT2_RO
    @@:
        mov     ecx, [ebx + EXT2_SB_STRUC.blocks_per_group]
        mov     [ebp + EXTFS.blocks_per_group], ecx

        movzx   ecx, word[ebx + EXT2_SB_STRUC.inode_size]
        mov     [ebp + EXTFS.inode_size], ecx

        ; Allocate for three inodes (loop would be overkill).
        push    ecx ecx ecx

        KERNEL_ALLOC [ebp + EXTFS.ext2_save_inode], .error
        KERNEL_ALLOC [ebp + EXTFS.ext2_temp_inode], .error
        KERNEL_ALLOC [ebp + EXTFS.root_inode], .error

        ; Read root inode.
        mov     ebx, eax
        mov     eax, EXT2_ROOT_INO
        call    ext2_inode_read

        test    eax, eax
        jnz     .error

        ;call    ext2_sb_update
        ; Sync the disk.
        ;mov     esi, [ebp + PARTITION.Disk]
        ;call    disk_sync                       ; eax contains error code, if any.

        mov     eax, ebp                        ; Return pointer to EXTFS.
        pop     edi esi ebp ebx
        ret

    ; Error in setting up.
    .error:        
        ; Free save block.
        KERNEL_FREE [ebp + EXTFS.ext2_save_block], .fail

        ; Temporary block.
        KERNEL_FREE [ebp + EXTFS.ext2_temp_block], .fail

        ; All inodes.
        KERNEL_FREE [ebp + EXTFS.ext2_save_inode], .fail
        KERNEL_FREE [ebp + EXTFS.ext2_temp_inode], .fail
        KERNEL_FREE [ebp + EXTFS.root_inode], .fail

        mov     eax, ebp
        call    free

        jmp     .fail
endp

; FUNCTIONS PROVIDED BY SYSCALLS.

;---------------------------------------------------------------------
; Frees up all ext2 structures.
; Input:        eax = pointer to EXTFS.
;---------------------------------------------------------------------
proc ext2_free
        push    ebp

        xchg    ebp, eax
        stdcall kernel_free, [ebp+EXTFS.ext2_save_block]
        stdcall kernel_free, [ebp+EXTFS.ext2_temp_block]
        stdcall kernel_free, [ebp+EXTFS.ext2_save_inode]
        stdcall kernel_free, [ebp+EXTFS.ext2_temp_inode]
        stdcall kernel_free, [ebp+EXTFS.root_inode]

        xchg    ebp, eax
        call    free

        pop     ebp
        ret
endp

;---------------------------------------------------------------------
; Read disk folder.
; Input:        ebp = pointer to EXTFS structure.
;               esi + [esp + 4] = file name.
;               ebx = pointer to parameters from sysfunc 70.
; Output:       ebx = blocks read (or 0xFFFFFFFF, folder not found)
;               eax = error code (0 implies no error)
;---------------------------------------------------------------------
ext2_ReadFolder:
        ;DEBUGF  1, "Reading folder.\n"
        call    ext2_lock
        cmp     byte [esi], 0
        jz      .root_folder

        push    ebx
        stdcall ext2_inode_find, [esp + 4 + 4]            ; Get inode.
        pop     ebx

        mov     esi, [ebp + EXTFS.ext2_save_inode]
        test    eax, eax
        jnz     .error_ret

        ; If not a directory, then return with error.
        test    [esi + EXT2_INODE_STRUC.i_mode], EXT2_S_IFDIR
        jz      .error_not_found
        jmp     @F

    .root_folder:
        mov     esi, [ebp + EXTFS.root_inode]
        test    [esi + EXT2_INODE_STRUC.i_mode], EXT2_S_IFDIR
        jz      .error_root

        ; Copy the inode.
        mov     edi, [ebp + EXTFS.ext2_save_inode]
        mov     ecx, [ebp + EXTFS.inode_size]
        shr     ecx, 2
        
        push    edi
        rep movsd
        pop     esi

    @@:
        cmp     [esi + EXT2_INODE_STRUC.i_size], 0      ; Folder is empty.
        je      .error_empty_dir
        
        mov     edx, [ebx + 16]
        push    edx                                     ; Result address [edi + 28].
        push    0                                       ; End of the current block in folder [edi + 24]
        push    dword[ebx + 12]                         ; Blocks to read [edi + 20]
        push    dword[ebx + 4]                          ; The first wanted file [edi + 16]
        push    dword[ebx + 8]                          ; Flags [edi + 12]
        push    0                                       ; Read files [edi + 8]
        push    0                                       ; Files in folder [edi + 4]
        push    0                                       ; Number of blocks read in dir (and current block index) [edi]

        ; Fill header with zeroes.
        mov     edi, edx
        mov     ecx, 32/4
        rep stosd
        
        mov     edi, esp                                ; edi = pointer to local variables.
        add     edx, 32                                 ; edx = mem to return.

        xor     ecx, ecx                                ; Get number of first block.
        call    ext2_inode_get_block
        test    eax, eax
        jnz     .error_get_block

        mov     eax, ecx
        mov     ebx, [ebp + EXTFS.ext2_save_block]
        call    ext2_block_read                          ; Read the block.
        test    eax, eax
        jnz     .error_get_block

        mov     eax, ebx                                ; esi: current directory record
        add     eax, [ebp + EXTFS.block_size]
        
        mov     [edi + 24], eax

        mov     ecx, [edi + 16]                         ; ecx = first wanted (flags ommited)

    .find_wanted_start:
        jecxz   .find_wanted_end
        
    .find_wanted_cycle:
        cmp     [ebx + EXT2_DIR_STRUC.inode], 0         ; Don't count unused inode in total files.
        jz      @F

        inc     dword [edi + 4]                         ; EXT2 files in folder.
        dec     ecx
    @@:
        movzx   eax, [ebx + EXT2_DIR_STRUC.rec_len]
        
        cmp     eax, 12                                 ; Minimum record length.
        jb      .error_bad_len
        test    eax, 0x3                                ; Record length must be divisible by four.
        jnz     .error_bad_len

        sub     [esi + EXT2_INODE_STRUC.i_size], eax    ; Subtract "processed record" length directly from inode.
        add     ebx, eax                                ; Go to next record.
        cmp     ebx, [edi + 24]                         ; If not reached the next block, continue.
        jb      .find_wanted_start

        push    .find_wanted_start
   .end_block:                                          ; Get the next block.
        cmp     [esi + EXT2_INODE_STRUC.i_size], 0
        jle     .end_dir

        inc     dword [edi]                             ; Number of blocks read.
        
        ; Read the next block.
        push    ecx
        mov     ecx, [edi]
        call    ext2_inode_get_block
        test    eax, eax
        jnz     .error_get_block

        mov     eax, ecx
        mov     ebx, [ebp + EXTFS.ext2_save_block]
        call    ext2_block_read
        test    eax, eax
        jnz     .error_get_block
        pop     ecx

        mov     eax, ebx
        add     eax, [ebp + EXTFS.block_size]
        mov     [edi + 24], eax                         ; Update the end of the current block variable.  
        ret

    .wanted_end:
        loop    .find_wanted_cycle                      ; Skip files till we reach wanted one.
        
    ; First requisite file.
    .find_wanted_end:
        mov     ecx, [edi + 20]
    .wanted_start:                                      ; Look for first_wanted + count.
        jecxz   .wanted_end

        cmp     [ebx + EXT2_DIR_STRUC.inode], 0         ; if (inode == 0): not used;
        jz      .empty_rec
 
        ; Increment "files in dir" and "read files" count.
        inc     dword [edi + 8]
        inc     dword [edi + 4]

        push    edi ecx
        mov     edi, edx                                ; Zero out till the name field.
        xor     eax, eax
        mov     ecx, 40 / 4
        rep stosd
        pop     ecx edi

        push    ebx edi edx
        mov     eax, [ebx + EXT2_DIR_STRUC.inode]       ; Get the child inode.
        mov     ebx, [ebp + EXTFS.ext2_temp_inode]
        call    ext2_inode_read
        test    eax, eax
        jnz     .error_read_subinode

        lea     edi, [edx + 8]

        mov     eax, [ebx + EXT2_INODE_STRUC.i_ctime]   ; Convert time in NTFS format.
        xor     edx, edx
        add     eax, 3054539008                         ; (369 * 365 + 89) * 24 * 3600
        adc     edx, 2
        call    ntfs_datetime_to_bdfe.sec

        mov     eax, [ebx + EXT2_INODE_STRUC.i_atime]
        xor     edx, edx
        add     eax, 3054539008
        adc     edx, 2
        call    ntfs_datetime_to_bdfe.sec

        mov     eax, [ebx + EXT2_INODE_STRUC.i_mtime]
        xor     edx, edx
        add     eax, 3054539008
        adc     edx, 2
        call    ntfs_datetime_to_bdfe.sec

        pop     edx
        test    [ebx + EXT2_INODE_STRUC.i_mode], EXT2_S_IFDIR ; If folder, don't report size.
        jnz     @F

        mov     eax, [ebx + EXT2_INODE_STRUC.i_size]    ; Low size
        stosd
        mov     eax, [ebx + EXT2_INODE_STRUC.i_dir_acl] ; High size
        stosd

        xor     dword [edx], FS_FT_DIR                  ; Mark as file.
    @@:
        xor     dword [edx], FS_FT_DIR                  ; Mark as directory.

        ; Copy name after converting from UTF-8 to CP866.
        push    ecx esi
        mov     esi, [esp + 12]
        movzx   ecx, [esi + EXT2_DIR_STRUC.name_len]
        lea     edi, [edx + 40]
        lea     esi, [esi + EXT2_DIR_STRUC.name]
        call    utf8_to_cp866
        and     byte [edi], 0
        pop     esi ecx edi ebx

        cmp     byte [edx + 40], '.'                    ; If it begins with ".", mark it as hidden.
        jne     @F
        or      dword [edx], FS_FT_HIDDEN
    
    @@:
        add     edx, 40 + 264                           ; Go to next record.
        dec     ecx
    .empty_rec:
        movzx   eax, [ebx + EXT2_DIR_STRUC.rec_len]

        cmp     eax, 12                                 ; Illegal length.
        jb      .error_bad_len
        test    eax, 0x3                                ; Not a multiple of four.
        jnz     .error_bad_len

        sub     [esi + EXT2_INODE_STRUC.i_size], eax    ; Subtract directly from the inode.
        add     ebx, eax     
        cmp     ebx, [edi + 24]                         ; Are we at the end of the block?
        jb      .wanted_start

        push    .wanted_start 
        jmp     .end_block

    .end_dir:                                           ; End of the directory.
        call    ext2_unlock
        mov     edx, [edi + 28]                         ; Address of where to return data.
        mov     ebx, [edi + 8]                          ; EXT2_read_in_folder
        mov     ecx, [edi + 4]                          ; EXT2_files_in_folder
        mov     dword [edx], 1                          ; Version
        mov     [edx + 4], ebx
        mov     [edx + 8], ecx
        
        lea     esp, [edi + 32]
        
        xor     eax, eax                                ; Reserved in current implementation.
        lea     edi, [edx + 12]
        mov     ecx, 20 / 4
        rep stosd

        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret

    .error_bad_len:
        mov     eax, ERROR_FS_FAIL

    .error_read_subinode:
    .error_get_block:
        ; Fix the stack.
        lea     esp, [edi + 32]

    .error_ret:
        or      ebx, -1
        push    eax
        call    ext2_unlock
        pop     eax
        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret
        
    .error_empty_dir:                                   ; inode of folder without blocks.
    .error_root:                                        ; Root has to be a folder.
        mov     eax, ERROR_FS_FAIL
        jmp     .error_ret

    .error_not_found:                                   ; Directory not found.
        mov     eax, ERROR_FILE_NOT_FOUND
        jmp     .error_ret

;---------------------------------------------------------------------
; Read file from the hard disk.
; Input:        esi + [esp + 4] = points to file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       ebx = bytes read (0xFFFFFFFF -> file not found)
;               eax = error code (0 implies no error)
;---------------------------------------------------------------------
ext2_Read:
        ;DEBUGF  1, "Attempting read.\n"
        call    ext2_lock
        cmp     byte [esi], 0
        jnz     @F

    .this_is_nofile:
        call    ext2_unlock
        or      ebx, -1
        mov     eax, ERROR_ACCESS_DENIED
        ret

    @@:
        push    ebx
        stdcall ext2_inode_find, [esp + 4 + 4]
        pop     ebx

        mov     esi, [ebp + EXTFS.ext2_save_inode]
        test    eax, eax
        jz      @F

        call    ext2_unlock
        or      ebx, -1
        mov     eax, ERROR_FILE_NOT_FOUND
        ret

    @@:
        mov     ax, [esi + EXT2_INODE_STRUC.i_mode]
        and     ax, EXT2_S_IFMT                         ; Leave the file format in AX.

        ; Check if file.
        cmp     ax, EXT2_S_IFREG
        jne     .this_is_nofile

        mov     edi, [ebx + 16]
        mov     ecx, [ebx + 12]

        mov     eax, [ebx + 4]
        mov     edx, [ebx + 8]                          ; edx:eax = start byte number.

        ; Check if file is big enough for us.
        cmp     [esi + EXT2_INODE_STRUC.i_dir_acl], edx
        ja      .size_greater
        jb      .size_less

        cmp     [esi + EXT2_INODE_STRUC.i_size], eax
        ja      .size_greater

    .size_less:
        call    ext2_unlock
        xor     ebx, ebx
        mov     eax, ERROR_END_OF_FILE
        ret
        
    @@:
    .size_greater:
        add     eax, ecx                                ; Get last byte.
        adc     edx, 0

        ; Check if we've to read whole file, or till requested.
        cmp     [esi + EXT2_INODE_STRUC.i_dir_acl], edx
        ja      .read_till_requested
        jb      .read_whole_file
        cmp     [esi + EXT2_INODE_STRUC.i_size], eax
        jae     .read_till_requested

    .read_whole_file:
        push    1                                       ; Read till the end of file.
        mov     ecx, [esi + EXT2_INODE_STRUC.i_size]
        sub     ecx, [ebx + 4]                          ; To read = (size - starting byte)
        jmp     @F

    .read_till_requested:
        push    0                                       ; Read as much as requested.

    @@:
        ; ecx = bytes to read.
        ; edi = return memory
        ; [esi] = starting byte.

        push    ecx                                     ; Number of bytes to read.
        
        ; Get part of the first block.
        mov     edx, [ebx + 8]
        mov     eax, [ebx + 4]
        div     [ebp + EXTFS.block_size]

        push    eax                                     ; Save block counter to stack.
        
        push    ecx
        mov     ecx, eax
        call    ext2_inode_get_block
        test    eax, eax
        jnz     .error_at_first_block

        mov     ebx, [ebp + EXTFS.ext2_save_block]
        mov     eax, ecx
        call    ext2_block_read
        test    eax, eax
        jnz     .error_at_first_block

        pop     ecx
        ; Get index inside block.
        add     ebx, edx

        neg     edx
        add     edx, [ebp + EXTFS.block_size]          ; Get number of bytes in this block.

        ; If it's smaller than total bytes to read, then only one block.
        cmp     ecx, edx
        jbe     .only_one_block

        mov     eax, ecx
        sub     eax, edx
        mov     ecx, edx

        push    esi
        mov     esi, ebx
        rep movsb                                       ; Copy part of 1st block.
        pop     esi

        ; eax -> bytes to read.
    .calc_blocks_count:
        mov     ebx, edi                                ; Read the block in ebx.
        xor     edx, edx
        div     [ebp + EXTFS.block_size]                ; Get number of bytes in last block in edx.
        mov     edi, eax                                ; Get number of blocks in edi.

    @@:
        ; Test if all blocks are done.
        test    edi, edi
        jz      .finish_block
        
        inc     dword [esp]
        mov     ecx, [esp]
        call    ext2_inode_get_block

        test    eax, eax
        jnz     .error_at_read_cycle

        mov     eax, ecx                                ; ebx already contains desired values.
        call    ext2_block_read

        test    eax, eax
        jnz     .error_at_read_cycle

        add     ebx, [ebp + EXTFS.block_size]

        dec     edi
        jmp     @B

    ; In edx -- number of bytes in the last block.
    .finish_block:          
        test    edx, edx
        jz      .end_read

        pop     ecx                                     ; Pop block counter in ECX.
        inc     ecx
        call    ext2_inode_get_block
        
        test    eax, eax
        jnz     .error_at_finish_block

        mov     edi, ebx
        mov     eax, ecx
        mov     ebx, [ebp + EXTFS.ext2_save_block]
        call    ext2_block_read

        test    eax, eax
        jnz     .error_at_finish_block

        mov     ecx, edx
        mov     esi, ebx
        rep movsb                                       ; Copy last piece of block.
        jmp     @F

    .end_read:
        pop     ecx                                     ; Pop block counter in ECX.
    @@:
        pop     ebx                                     ; Number of bytes read.
        call    ext2_unlock
        pop     eax                                     ; If we were asked to read more, say EOF.
        test    eax, eax
        jz      @F

        mov     eax, ERROR_END_OF_FILE
        ret
    @@:
        xor     eax, eax
        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret
        
    .only_one_block:
        mov     esi, ebx
        rep movsb                                       ; Copy last piece of block.
        jmp     .end_read
        
    .error_at_first_block:
        pop     edx
    .error_at_read_cycle:
        pop     ebx
    .error_at_finish_block:
        pop     ecx edx
        or      ebx, -1
        push    eax
        call    ext2_unlock
        pop     eax

        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret

;---------------------------------------------------------------------
; Read file information from block device.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;---------------------------------------------------------------------
ext2_GetFileInfo:
        ;DEBUGF  1, "Calling for file info, for: %s.\n", esi
        call    ext2_lock
        mov     edx, [ebx + 16]
        cmp     byte [esi], 0
        jz      .is_root

        push    edx
        stdcall ext2_inode_find, [esp + 4 + 4]
        mov     ebx, edx
        pop     edx
        
        mov     esi, [ebp + EXTFS.ext2_save_inode]
        test    eax, eax
        jz      @F

        push    eax
        call    ext2_unlock
        pop     eax
        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret

    .is_root:      
        xor     ebx, ebx                                ; Clear out first char, since we don't want to set hidden flag on root.
        mov     esi, [ebp + EXTFS.root_inode]

    @@:
        xor     eax, eax
        mov     edi, edx
        mov     ecx, 40/4
        rep stosd                                       ; Zero fill buffer.

        cmp     bl, '.'
        jne     @F
        or      dword [edx], FS_FT_HIDDEN

    @@:
        test    [esi + EXT2_INODE_STRUC.i_mode], EXT2_S_IFDIR
        jnz     @F                                      ; If a directory, don't put in file size.

        mov     eax, [esi + EXT2_INODE_STRUC.i_size]    ; Low file size.
        mov     ebx, [esi + EXT2_INODE_STRUC.i_dir_acl] ; High file size.
        mov     dword [edx+32], eax
        mov     dword [edx+36], ebx

        xor     dword [edx], FS_FT_DIR                  ; Next XOR will clean this, to mark it as a file.
    @@:
        xor     dword [edx], FS_FT_DIR                  ; Mark as directory.

        lea     edi, [edx + 8]

        ; Store all time.
        mov     eax, [esi + EXT2_INODE_STRUC.i_ctime]
        xor     edx, edx
        add     eax, 3054539008
        adc     edx, 2
        call    ntfs_datetime_to_bdfe.sec

        mov     eax, [esi + EXT2_INODE_STRUC.i_atime]
        xor     edx, edx
        add     eax, 3054539008
        adc     edx, 2
        call    ntfs_datetime_to_bdfe.sec

        mov     eax, [esi + EXT2_INODE_STRUC.i_mtime]
        xor     edx, edx
        add     eax, 3054539008
        adc     edx, 2
        call    ntfs_datetime_to_bdfe.sec

        call    ext2_unlock
        xor     eax, eax
        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret

;---------------------------------------------------------------------
; Set file information for block device.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;---------------------------------------------------------------------
ext2_SetFileInfo:
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jz      @F

        mov     eax, ERROR_UNSUPPORTED_FS
        ret

    @@:    
        push    edx esi edi ebx
        call    ext2_lock
        mov     edx, [ebx + 16]

        ; Is this read-only?
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jnz     .fail

        ; Not supported for root.
        cmp     byte [esi], 0
        je      .fail

    .get_inode:
        push    edx
        stdcall ext2_inode_find, [esp + 4 + 20]
        pop     edx

        test    eax, eax
        jnz     @F

        ; Save inode number.
        push    esi
        mov     esi, [ebp + EXTFS.ext2_save_inode]

        ; From the BDFE, we ignore read-only file flags, hidden file flags;
        ; We ignore system file flags, file was archived or not.

        ; Also ignored is file creation time. ext2 stores "inode modification"
        ; time in the ctime field, which is updated by the respective inode_write
        ; procedure, and any writes on it would be overwritten anyway.

        ; Access time.
        lea     edi, [esi + EXT2_INODE_STRUC.i_atime]
        lea     esi, [edx + 16]
        call    bdfe_to_unix_time

        ; Modification time.
        add     esi, 8
        add     edi, 8
        call    bdfe_to_unix_time

        mov     ebx, [ebp + EXTFS.ext2_save_inode] ; Get address of inode into ebx.
        pop     eax                             ; Get inode number in eax.
        call    ext2_inode_write                ; eax contains error code, if any.
        test    eax, eax
        jnz     @F

        call    ext2_sb_update
        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

    @@:
        push    eax
        call    ext2_unlock
        pop     eax

        pop     ebx edi esi edx
        ret

    .fail:
        call    ext2_sb_update
        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

        mov     eax, ERROR_UNSUPPORTED_FS
        jmp     @B

;---------------------------------------------------------------------
; Set file information for block device.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;---------------------------------------------------------------------
ext2_Delete:
        ;DEBUGF  1, "Attempting Delete.\n"
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jz      @F

        mov     eax, ERROR_UNSUPPORTED_FS
        ret
 
    @@:    
        push    ebx ecx edx esi edi
        call    ext2_lock

        add     esi, [esp + 20 + 4]

        ; Can't delete root.
        cmp     byte [esi], 0
        jz      .error_access_denied

        push    esi 
        stdcall ext2_inode_find, 0
        mov     ebx, esi
        pop     esi

        test    eax, eax
        jnz     .error_access_denied

        mov     edx, [ebp + EXTFS.ext2_save_inode]
        movzx   edx, [edx + EXT2_INODE_STRUC.i_mode]
        and     edx, EXT2_S_IFMT                                ; Get the mask.
        cmp     edx, EXT2_S_IFDIR
        jne     @F                                              ; If not a directory, we don't need to check if it's empty.

        call    ext2_dir_empty                                  ; 0 means directory is empty.

        test    eax, eax
        jnz     .error_access_denied

    @@:
        ; Find parent.
        call    ext2_inode_find_parent
        test    eax, eax
        jnz     .error_access_denied
        mov     eax, esi

        ; Save file/dir & parent inode.
        push    ebx eax

        cmp     edx, EXT2_S_IFDIR
        jne     @F      

        ; Unlink '.'
        mov     eax, [esp + 4]
        call    ext2_inode_unlink
        cmp     eax, 0xFFFFFFFF
        je      .error_stack8

        ; Unlink '..'
        mov     eax, [esp + 4]
        mov     ebx, [esp]
        call    ext2_inode_unlink
        cmp     eax, 0xFFFFFFFF
        je      .error_stack8

    @@:
        pop     eax
        mov     ebx, [esp]
        ; Unlink the inode.
        call    ext2_inode_unlink
        cmp     eax, 0xFFFFFFFF
        je      .error_stack4
        
        ; If hardlinks aren't zero, shouldn't completely free.
        test    eax, eax
        jz      @F

        add     esp, 4
        jmp     .disk_sync

    @@:
        ; Read the inode.
        mov     eax, [esp]
        mov     ebx, [ebp + EXTFS.ext2_save_inode]
        call    ext2_inode_read
        test    eax, eax
        jnz     .error_stack4
        
        ; Free inode data.
        mov     esi, [ebp + EXTFS.ext2_save_inode]
        xor     ecx, ecx

    @@:
        push    ecx
        call    ext2_inode_get_block
        test    eax, eax
        jnz     .error_stack8
        mov     eax, ecx
        pop     ecx

        ; If 0, we're done.
        test    eax, eax
        jz      @F

        call    ext2_block_free
        test    eax, eax
        jnz     .error_stack4

        inc     ecx
        jmp     @B

    @@:
        ; Free indirect blocks.
        call    ext2_inode_free_indirect_blocks
        test    eax, eax
        jnz     .error_stack4

        ; Clear the inode, and add deletion time.
        mov     edi, [ebp + EXTFS.ext2_save_inode]
        xor     eax, eax
        mov     ecx, [ebp + EXTFS.inode_size]
        rep stosb

        mov     edi, [ebp + EXTFS.ext2_save_inode]
        add     edi, EXT2_INODE_STRUC.i_dtime
        call    current_unix_time

        ; Write the inode.
        mov     eax, [esp]
        mov     ebx, [ebp + EXTFS.ext2_save_inode]
        call    ext2_inode_write
        test    eax, eax
        jnz     .error_stack4

        ; Check if directory.
        cmp     edx, EXT2_S_IFDIR
        jne     @F

        ; If it is, decrement used_dirs_count.

        ; Get block group.
        mov     eax, [esp]
        dec     eax
        xor     edx, edx
        div     [ebp + EXTFS.superblock + EXT2_SB_STRUC.inodes_per_group]

        push    eax
        call    ext2_bg_read_desc
        test    eax, eax
        jz      .error_stack8

        dec     [eax + EXT2_BLOCK_GROUP_DESC.used_dirs_count]
        
        pop     eax
        call    ext2_bg_write_desc

    @@:
        pop     eax
        call    ext2_inode_free
        test    eax, eax
        jnz     .error_access_denied

    .disk_sync:
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

    .return:    
        push    eax
        call    ext2_unlock
        pop     eax

        pop     edi esi edx ecx ebx
        ;DEBUGF  1, "And returning with: %x.\n", eax
        ret

    .error_stack8:
        add     esp, 4
    .error_stack4:
        add     esp, 4
    .error_access_denied:
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

        mov     eax, ERROR_ACCESS_DENIED
        jmp     .return

;---------------------------------------------------------------------
; Set file information for block device.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;---------------------------------------------------------------------
ext2_CreateFolder:
        ;DEBUGF  1, "Attempting to create folder.\n"
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jz      @F

        mov     eax, ERROR_UNSUPPORTED_FS
        ret
 
    @@:    
        push    ebx ecx edx esi edi
        call    ext2_lock

        add     esi, [esp + 20 + 4]

        ; Can't create root, but for CreateFolder already existing directory is success.
        cmp     byte [esi], 0
        jz      .success

        push    esi 
        stdcall ext2_inode_find, 0
        pop     esi

        ; If the directory is there, we've succeeded.
        test    eax, eax
        jz      .success

        ; Find parent.
        call    ext2_inode_find_parent
        test    eax, eax
        jnz     .error

        ; Inode ID for preference.
        mov     eax, esi
        call    ext2_inode_alloc
        test    eax, eax
        jnz     .error_full

        ; Save allocated inode in EDX; filename is in EDI; parent ID in ESI.
        mov     edx, ebx

        push    edi

        xor     al, al
        mov     edi, [ebp + EXTFS.ext2_temp_inode]
        mov     ecx, [ebp + EXTFS.inode_size]
        rep stosb

        mov     edi, [ebp + EXTFS.ext2_temp_inode]
        add     edi, EXT2_INODE_STRUC.i_atime
        call    current_unix_time

        add     edi, 8
        call    current_unix_time

        pop     edi

        mov     ebx, [ebp + EXTFS.ext2_temp_inode]
        mov     [ebx + EXT2_INODE_STRUC.i_mode], EXT2_S_IFDIR or PERMISSIONS
        mov     eax, edx
        call    ext2_inode_write
        test    eax, eax
        jnz     .error

        ; Link to self.
        push    edx esi

        mov     eax, edx
        mov     ebx, eax
        mov     dl, EXT2_FT_DIR
        mov     esi, self_link
        call    ext2_inode_link

        pop     esi edx

        test    eax, eax
        jnz     .error

        ; Link to parent.
        push    edx esi

        mov     eax, ebx
        mov     ebx, esi
        mov     dl, EXT2_FT_DIR
        mov     esi, parent_link
        call    ext2_inode_link

        pop     esi edx

        test    eax, eax
        jnz     .error

        ; Link parent to child.
        mov     eax, esi
        mov     ebx, edx
        mov     esi, edi
        mov     dl, EXT2_FT_DIR
        call    ext2_inode_link
        test    eax, eax
        jnz     .error

        ; Get block group descriptor for allocated inode's block.
        mov     eax, ebx
        dec     eax
        xor     edx, edx
        
        ; EAX = block group.
        div     [ebp + EXTFS.superblock + EXT2_SB_STRUC.inodes_per_group]
        mov     edx, eax

        call    ext2_bg_read_desc
        test    eax, eax
        jz      .error

        inc     [eax + EXT2_BLOCK_GROUP_DESC.used_dirs_count]
        mov     eax, edx
        call    ext2_bg_write_desc
        test    eax, eax
        jnz     .error

    .success:
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

    .return:
        push    eax
        call    ext2_unlock
        pop     eax

        pop     edi esi edx ecx ebx
        ;DEBUGF  1, "Returning with: %x.\n", eax
        ret

    .error:
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.
        
        mov     eax, ERROR_ACCESS_DENIED
        jmp     .return

    .error_full:
        mov     eax, ERROR_DISK_FULL
        jmp     .return

self_link   db ".", 0
parent_link db "..", 0

;---------------------------------------------------------------------
; Rewrite a file.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;               ebx = bytes written.
;---------------------------------------------------------------------
ext2_Rewrite:
        ;DEBUGF  1, "Attempting Rewrite.\n"
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jz      @F

        mov     eax, ERROR_UNSUPPORTED_FS
        ret
 
    @@:
        push    ecx edx esi edi
        pushad

        call    ext2_lock

        add     esi, [esp + 16 + 32 + 4]
        ; Can't create root.
        cmp     byte [esi], 0
        jz      .error_access_denied

        push    esi 
        stdcall ext2_inode_find, 0
        pop     esi

        ; If the file is there, delete it.
        test    eax, eax
        jnz     @F

        pushad

        push    eax
        call    ext2_unlock
        pop     eax

        push    dword 0x00000000
        call    ext2_Delete
        add     esp, 4

        push    eax
        call    ext2_lock
        pop     eax

        test    eax, eax
        jnz     .error_access_denied_delete

        popad
    @@:
        ; Find parent.
        call    ext2_inode_find_parent
        test    eax, eax
        jnz     .error_access_denied

        ; Inode ID for preference.
        mov     eax, esi
        call    ext2_inode_alloc
        test    eax, eax
        jnz     .error_full

        ; Save allocated inode in EDX; filename is in EDI; parent ID in ESI.
        mov     edx, ebx

        push    edi

        xor     al, al
        mov     edi, [ebp + EXTFS.ext2_temp_inode]
        mov     ecx, [ebp + EXTFS.inode_size]
        rep stosb

        mov     edi, [ebp + EXTFS.ext2_temp_inode]
        add     edi, EXT2_INODE_STRUC.i_atime
        call    current_unix_time

        add     edi, 8
        call    current_unix_time

        pop     edi

        mov     ebx, [ebp + EXTFS.ext2_temp_inode]
        mov     [ebx + EXT2_INODE_STRUC.i_mode], EXT2_S_IFREG or PERMISSIONS
        mov     eax, edx
        call    ext2_inode_write
        test    eax, eax
        jnz     .error

        ; Link parent to child.
        mov     eax, esi
        mov     ebx, edx
        mov     esi, edi
        mov     dl, EXT2_FT_REG_FILE
        call    ext2_inode_link
        test    eax, eax
        jnz     .error

        popad
        push    eax
        call    ext2_unlock
        pop     eax

        push    dword 0x00000000
        call    ext2_Write
        add     esp, 4

        push    eax
        call    ext2_lock
        pop     eax

    .success:
        push    eax
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.
        pop     eax

    .return:
        push    eax
        call    ext2_unlock
        pop     eax

        pop     edi esi edx ecx

        ;DEBUGF  1, "And returning with: %x.\n", eax
        ret

    .error:       
        mov     eax, ERROR_ACCESS_DENIED
        jmp     .success

    .error_access_denied_delete:
        popad

    .error_access_denied:
        popad
        xor     ebx, ebx

        mov     eax, ERROR_ACCESS_DENIED
        jmp     .return

    .error_full:
        popad
        xor     ebx, ebx

        mov     eax, ERROR_DISK_FULL
        jmp     .return

;---------------------------------------------------------------------
; Write to a file.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;               ebx = number of bytes written.
;---------------------------------------------------------------------
ext2_Write:
        ;DEBUGF 1, "Attempting write, "
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jz      @F

        mov     eax, ERROR_UNSUPPORTED_FS
        ret
 
    @@:    
        push    ecx edx esi edi
        call    ext2_lock

        add     esi, [esp + 16 + 4]

        ; Can't write to root.
        cmp     byte [esi], 0
        jz      .error

        push    ebx ecx edx
        stdcall ext2_inode_find, 0
        pop     edx ecx ebx
        ; If file not there, error.
        xor     ecx, ecx
        test    eax, eax
        jnz     .error_file_not_found

        ; Save the inode.
        push    esi

        ; Check if it's a file.
        mov     edx, [ebp + EXTFS.ext2_save_inode]
        test    [edx + EXT2_INODE_STRUC.i_mode], EXT2_S_IFREG
        jz      .error

        mov     eax, esi
        mov     ecx, [ebx + 4]

        call    ext2_inode_extend
        xor     ecx, ecx
        test    eax, eax
        jnz     .error_device

        ; ECX contains the size to write, and ESI points to it.
        mov     ecx, [ebx + 0x0C]
        mov     esi, [ebx + 0x10]

        ; Save the size of the inode.
        mov     eax, [edx + EXT2_INODE_STRUC.i_size]
        push    eax

        xor     edx, edx
        div     [ebp + EXTFS.block_size]

        test    edx, edx
        jz      .start_aligned

        ; Start isn't aligned, so deal with the non-aligned bytes.
        mov     ebx, [ebp + EXTFS.block_size]
        sub     ebx, edx

        cmp     ebx, ecx
        jbe     @F

        ; If the size to copy fits in current block, limit to that, instead of the entire block.
        mov     ebx, ecx

    @@:
        ; Copy EBX bytes, in EAX indexed block.
        push    eax
        call    ext2_inode_read_entry
        test    eax, eax
        pop     eax
        jnz     .error_inode_size

        push    ecx
        
        mov     ecx, ebx
        mov     edi, ebx
        add     edi, edx
        rep movsb

        pop     ecx

        ; Write the block.
        call    ext2_inode_write_entry
        test    eax, eax
        jnz     .error_inode_size

        add     [esp], ebx
        sub     ecx, ebx
        jz      .write_inode

    .start_aligned:
        cmp     ecx, [ebp + EXTFS.block_size]
        jb      @F

        mov     eax, [esp]
        xor     edx, edx
        div     [ebp + EXTFS.block_size]

        push    eax
        mov     edx, [esp + 8]
        call    ext2_inode_blank_entry
        test    eax, eax
        pop     eax
        jnz     .error_inode_size

        push    ecx

        mov     ecx, [ebp + EXTFS.block_size]
        mov     edi, [ebp + EXTFS.ext2_save_block]
        rep movsb

        pop     ecx

        call    ext2_inode_write_entry
        test    eax, eax
        jnz     .error_inode_size

        mov     eax, [ebp + EXTFS.block_size]
        sub     ecx, eax
        add     [esp], eax
        jmp     .start_aligned        

    ; Handle the remaining bytes.
    @@:
        test    ecx, ecx
        jz      .write_inode

        mov     eax, [esp]
        xor     edx, edx
        div     [ebp + EXTFS.block_size]

        push    eax
        call    ext2_inode_read_entry
        test    eax, eax
        pop     eax
        jz      @F

        push    eax
        mov     edx, [esp + 8]

        call    ext2_inode_blank_entry
        test    eax, eax
        pop     eax
        jnz     .error_inode_size

    @@:
        push    ecx
        mov     edi, [ebp + EXTFS.ext2_save_block]
        rep movsb
        pop     ecx

        call    ext2_inode_write_entry
        test    eax, eax
        jnz     .error_inode_size

        add     [esp], ecx
        xor     ecx, ecx

    .write_inode:
        mov     ebx, [ebp + EXTFS.ext2_temp_inode]
        pop     eax
        mov     [ebx + EXT2_INODE_STRUC.i_size], eax
        mov     eax, [esp]

        call    ext2_inode_write
        test    eax, eax
        jnz     .error_device

    .success:
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

    .return:
        push    eax
        call    ext2_unlock
        pop     eax

        add     esp, 4

        mov     ebx, [esp + 12]
        sub     ebx, ecx
        pop     edi esi edx ecx

        ;DEBUGF  1, "and returning with: %x.\n", eax
        ret

    .error:
        mov     eax, ERROR_ACCESS_DENIED
        jmp     .return

    .error_file_not_found:
        mov     eax, ERROR_FILE_NOT_FOUND
        jmp     .return

    .error_inode_size:
        mov     ebx, [ebp + EXTFS.ext2_temp_inode]
        pop     eax
        mov     [ebx + EXT2_INODE_STRUC.i_size], eax
        mov     eax, [esp]

        call    ext2_inode_write

    .error_device:        
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

        mov     eax, ERROR_DEVICE
        jmp     .return

;---------------------------------------------------------------------
; Set the end of a file.
; Input:        esi + [esp + 4] = file name.
;               ebx = pointer to paramteres from sysfunc 70.
;               ebp = pointer to EXTFS structure.
; Output:       eax = error code.
;---------------------------------------------------------------------
ext2_SetFileEnd:
        test    [ebp + EXTFS.partition_flags], EXT2_RO
        jz      @F

        mov     eax, ERROR_UNSUPPORTED_FS
        ret
 
    @@:    
        push    ebx ecx edx esi edi
        call    ext2_lock

        add     esi, [esp + 20 + 4]

        ; Can't write to root.
        cmp     byte [esi], 0
        jz      .error

        stdcall ext2_inode_find, 0
        ; If file not there, error.
        test    eax, eax
        jnz     .error_file_not_found

        ; Check if it's a file.
        mov     edx, [ebp + EXTFS.ext2_save_inode]
        cmp     [edx + EXT2_INODE_STRUC.i_mode], EXT2_S_IFREG
        jne     .error

        mov     eax, esi
        mov     ecx, [ebx + 4]
        call    ext2_inode_extend
        test    eax, eax
        jnz     .error_disk_full

        mov     eax, esi
        call    ext2_inode_truncate
        test    eax, eax
        jnz     .error_disk_full

        mov     eax, esi
        mov     ebx, [ebp + EXTFS.ext2_temp_inode]
        call    ext2_inode_write

        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

    .return:
        push    eax
        call    ext2_unlock
        pop     eax

        pop     edi esi edx ecx ebx
        ret

    .error:
        mov     eax, ERROR_ACCESS_DENIED
        jmp     .return

    .error_file_not_found:
        mov     eax, ERROR_FILE_NOT_FOUND
        jmp     .return

    .error_disk_full:        
        call    ext2_sb_update

        ; Sync the disk.
        mov     esi, [ebp + PARTITION.Disk]
        call    disk_sync                       ; eax contains error code, if any.

        mov     eax, ERROR_DISK_FULL
        jmp     .return