.include "syscalls.S" .include "constants.S" .include "string.S" .global _start /* Exits the program with the provided exit code @args: r0 => exit code */ exit: mov r7, #SYS_EXIT svc 0 /* print to stdout @args: r1 => pointer to buffer to print r2 => how many bytes to print @clobbers: r0 */ print: mov r0, #STDOUT mov r7, #SYS_WRITE svc 0 bx lr open_fail: push {r0} ldr r1, =OPEN_FAIL mov r2, #31 bl print pop {r0} bl exit dir_read_fail: push {r0} ldr r1, =DIR_READ_FAIL mov r2, #29 bl print pop {r0} bl exit fd_not_dir: ldr r1, =FD_NOT_DIR mov r2, #24 bl print mov r0, #1 bl exit fd_not_found: ldr r1, =FD_NOT_FOUND mov r2, #58 bl print mov r0, #1 bl exit _start: ldr r0, [sp] cmp r0, #1 ldrgt r0, [sp, #8] ldrle r0, =FALLBACK_DIR mov r1, #0 @ flags mov r2, #0 @ mode_t mov r7, #SYS_OPEN svc 0 cmp r0, #ENOENT beq fd_not_found cmp r0, #0 blt open_fail @ r0 needs the fd, which it should already be since the call to open() set r0 to the fd. ldr r1, =buffer @ pointer to linux_dirent structure @ TODO: we can allocate the required memory on the stack instead of in the bss section I think. /* TODO: this only reads up to 16KB of directory entries. if a directory we pass has entries with a sum larger than 16KB then we don't end up printing those. Solution: a nested loop. one for reading until the syscall returns 0 to indicate end of directory, and one for looping the directories */ mov r2, #16384 @ how many bytes to read mov r7, #SYS_GETDENTS svc 0 cmp r0, #ENOTDIR beq fd_not_dir cmp r0, #0 blt dir_read_fail mov r4, #0 @ initialize r4 to 0 ldr r5, =buffer sub sp, sp, #4096 @ allocate 4KB of mem to store the directory entries. mov r1, sp mov r10, #0 @ dir entry counter 1: add r2, r5, #10 @ offset 10 bytes into the dirent struct to reach d_name. str r2, [r1, r10, lsl #2] @ store the value in r2 in the memory address pointed to by r1[r10 << 2]. The shift is just (r10 * 4) add r10, r10, #1 @ load the value (as 16-bits zero-extended as opposed to loading 32-bits and then extending that ourselves with UXTH) @ in the memory address of r5 offset by 8 bytes to get us the d_reclen ldrh r6, [r5, #8] add r5, r5, r6 @ skip to the next dirent add r4, r4, r6 @ accumulate how many bytes we read cmp r4, r0 blt 1b /* bubble sort TODO: improvements: - a case-insensitive strcmp should be used when sorting - dots at the beginning of a file name should be ignored when sorting/comparing to match ls' behavior */ mov r5, r1 @ save the array pointer in r5 because we'll be using r1 a lot mov r3, #0 @ outer loop counter outer: mov r4, #0 @ inner loop counter sub r8, r10, r3 sub r8, r8, #1 push {r3} inner: ldr r1, [r5, r4, lsl #2] add r6, r4, #1 ldr r2, [r5, r6, lsl #2] push {r1-r2} bl strcmp_ascii pop {r1-r2} cmp r0, #1 bne 2f @ swap if a > b mov r7, r1 str r2, [r5, r4, lsl #2] str r7, [r5, r6, lsl #2] 2: add r4, r4, #1 cmp r4, r8 blt inner pop {r3} add r3, r3, #1 cmp r3, r10 blt outer print_entries: ldr r1, [r5], #4 bl bytelen mov r2, r0 bl print ldr r1, =NEWLINE mov r2, #1 bl print sub r10, r10, #1 cmp r10, #0 bgt print_entries add sp, sp, #4096 mov r0, #0 bl exit .data FALLBACK_DIR: .asciz "." OPEN_FAIL: .ascii "Failed to open file descriptor" DIR_READ_FAIL: .ascii "Failed to read the directory\n" FD_NOT_DIR: .ascii "File is not a directory\n" FD_NOT_FOUND: .ascii "Failed to open file descriptor: No such file or directory\n" NEWLINE: .ascii "\n" .bss buffer: .skip 16384