;CAUTION: THIS PROGRAM HAS BEEN DESIGNED TO REMOVE YOUR FILES WITHOUT CONFIRMATION
;IF YOU DELETE YOUR FILES, THE AUTHOR OF THE PROGRAM IS NOT RESPONSIBLE
format ELF64 executable 3
use64
entry _start

segment readable writeable
errmsg: db 'ERROR    ', 0

errmsg1: db 'NOT A DIRECTORY ERROR', 10
errmsg1_len = $ - errmsg1

errmsg2: db 'DIRECTORY DOES NOT EXIST ERROR', 10
errmsg2_len = $ - errmsg2

argerr: db 'EXPECTED SINGLE ARGUMENT ERROR', 10
argerr_len = $ - argerr

memerrormsg: db 'MEMORY ALLOCATION ERROR', 10
memerrormsg_len = $ - memerrormsg

newline: db 10

currentdir: db '.', 0

BUFLEN equ 1024
WDLEN equ 4096

struct_stat: rb 144

segment readable executable
_start:
    pop r8
    cmp r8, 2
    jne argfailure

    lea r13, [struct_stat]

;PATH EXISTS AND IS A DIRECTORY
    mov eax, 6 ;syscall number for lstat
    mov rdi, qword [rsp+8]
    mov rsi, r13
    syscall
    test eax, eax
    js failure2 ;directory does not exist

    movzx r10d, word [r13+24] ;this offset could vary because of padding in the struct
    and r10d, 170000o
    cmp r10d, 40000o
    jne failure1 ;not a directory

    mov rdi, qword [rsp+8]
    call deltree

    mov eax, 60 ;syscall number for exit
    xor edi, edi ;success
    syscall

failure:
    neg rax ;get positive error number
    mov rsi, rax
    lea rdi, [errmsg]
    add rdi, 6
    call numtostr

    mov rdi, errmsg
    call strlen

    mov edx, eax ;string length
    mov eax, 1 ;syscall number for write
    mov edi, 2 ;STDERR_FILENO
    lea rsi, [errmsg]
    syscall

    mov eax, 1
    mov edi, 2
    lea rsi, [newline]
    mov edx, 1
    syscall

    mov eax, 60
    mov edi, 1
    syscall

failure1:
    mov eax, 1
    mov edi, 2
    lea rsi, [errmsg1]
    mov edx, errmsg1_len
    syscall

    mov eax, 60
    mov edi, 1
    syscall

failure2:
    mov eax, 1
    mov edi, 2
    lea rsi, [errmsg2]
    mov edx, errmsg2_len
    syscall

    mov eax, 60
    mov edi, 1
    syscall

argfailure:
    mov eax, 1
    mov edi, 2
    lea rsi, [argerr]
    mov edx, argerr_len
    syscall

    mov eax, 60
    mov edi, 1
    syscall

mem_error:
    mov eax, 1
    mov edi, 2
    lea rsi, [memerrormsg]
    mov edx, memerrormsg_len
    syscall

    mov eax, 60
    mov edi, 1
    syscall

deltree: ;rdi(IN) - pointer to directory name
    push rbp
    mov rbp, rsp
    sub rsp, 24

    mov qword [rbp-24], rdi ;save function argument

    push rbx
    push r14
    push r15

;ALLOCATE FOR DIRECTORY PATH
    mov eax, 12 ;syscall number for brk
    xor edi, edi
    syscall ;this returns the old break in rax
    test eax, eax
    js mem_error
    mov qword [rbp-16], rax ;beginning of buffer
    lea rdi, [rax+WDLEN] ;add the number of bytes to the break point
    mov eax, 12
    syscall
    test eax, eax
    js mem_error

;SAVE CURRENT WORKING DIRECTORY
    mov eax, 79 ;syscall number for getcwd
    mov rdi, qword [rbp-16] ;buffer
    mov esi, WDLEN ;length
    syscall

;GIVE WRITE ACCESS TO THE FILES THAT ARE TO BE UNLINKED
    mov eax, 90 ;syscall number for chmod
    mov rdi, qword [rbp-24] ;directory name
    mov rsi, 775o
    syscall

;CHANGE DIRECTORY
    mov eax, 80 ;syscall number for chdir
    mov rdi, qword [rbp-24] ;directory name
    syscall
    test eax, eax
    js failure

;OPEN DIRECTORY
    mov eax, 2 ;open syscall
    lea rdi, [currentdir]
    mov rsi, 200000o ;O_DIRECTORY
    syscall
    test eax, eax
    js failure
    mov dword [rbp-8], eax ;file descriptor

;ALLOCATE BUFFER FOR GETDENTS
    mov eax, 12 ;syscall number for brk
    xor edi, edi
    syscall ;this returns the old break in rax
    test eax, eax
    js mem_error
    mov r14, rax ;beginning of buffer
    lea rdi, [rax+BUFLEN] ;add the number of bytes to the break
    mov eax, 12
    syscall
    test eax, eax
    js mem_error

.getdents:
;GET ENTRIES
    mov eax, 217 ;getdents64 syscall
    mov edi, dword [rbp-8] ;file descriptor from 'open'
    mov rsi, r14 ;memory to store dirent in
    mov edx, BUFLEN ;size of memory buffer
    syscall
    test eax, eax
    js failure
    jz .close ;no more dents
    mov r11d, eax ;number of bytes read

    mov rbx, r14 ;start of the buffer, beginning of memory space
    xor r15d, r15d ;r15 will hold sum of d_reclen lengths
    jmp .continue

.again:
    movzx eax, word [rbx+16] ;d_reclen -> eax
    add r15d, eax ;running sum in r15d

    cmp r11d, r15d
    je .getdents

    lea rbx, [r14+r15] ;next entry

.continue:
;SKIP . AND ..
    lea rsi, [rbx+19] ;filename
    mov ax, 0x002e
    cmp byte [rsi], al ;starts with a dot?
    jne .nodots ;does not start with a dot
    cmp byte [rsi+1], ah ;skip current directory
    je .again
    cmp word [rsi+1], ax ;skip parent directory
    je .again

.nodots:
;RECURSE IN CASE OF A DIRECTORY
    cmp byte [rbx+18], 4 ;d_type == DT_DIR
    jne .pass

    lea rdi, [rbx+19] ;d_name
    push r11
    call deltree
    pop r11
    jmp .over

.pass:
;UNLINK FILE
    push r11
    mov eax, 87 ;syscall number for unlink
    lea rdi, [rbx+19]
    syscall
    pop r11
    test eax, eax
    js failure

.over:
    cmp r11d, r15d
    jne .again
    jmp .getdents

.close:
;CLOSE DIRECTORY
    mov eax, 3 ;close syscall
    mov rdi, qword [rbp-8] ;file descriptor from 'open'
    syscall
    test eax, eax
    js failure

;CHANGE WORKING DIRECTORY BACK
    mov eax, 80 ;syscall number for chdir
    mov rdi, qword [rbp-16] ;saveddir
    syscall
    test eax, eax
    js failure

;REMOVE DIRECTORY
    mov eax, 84 ;syscall number for rmdir
    mov rdi, qword [rbp-24] ;directory name
    syscall
    test eax, eax
    js failure

    pop r15
    pop r14
    pop rbx
    add rsp, 24
    leave
    ret

strlen: ;rdi(IN) - string, rax(OUT) - length
    mov rax, rdi

.again:
    cmp byte [rax], 0
    jz .end
    inc rax
    jmp .again

.end:
    sub rax, rdi
    ret

numtostr: ;rdi(OUT) - string, rsi(IN) - number
    mov eax, esi ;input number
    mov r8d, 10 ;divisor
    xor ecx, ecx ;counter, length of string

.divide:
    xor edx, edx
    div r8d ;divide a by 10, remainder in d
    push rdx ;save remainder on the stack
    inc ecx
    test eax, eax
    jnz .divide

.next_digit:
    pop rax
    add al, 48 ;add to the remainder to get the correct ASCII output value
    stosb
    dec ecx
    jnz .next_digit

    xor al, al
    stosb
    ret

; vim: set ts=4 sw=4 sts=4 syn=fasm:
