; fstat.asm — GNU-compatible 'stat' command (dev build)
; Full implementation for nasm -f elf64 + ld linking
;
; Register allocation:
; r14d = argc, r15 = argv
; ebx = flags (bit 0 = -L/deref, bit 1 = -f/filesystem, bit 2 = -t/terse,
; bit 3 = -c/format given)
; r12 = current file arg index
; r13 = format string pointer (if -c/--format)
%define SYS_WRITE 1
%define SYS_STAT 4
%define SYS_LSTAT 6
%define SYS_EXIT 60
%define SYS_RT_SIGPROCMASK 14
%define SYS_READLINKAT 267
%define SYS_STATFS 137
%define SYS_STATX 332
%define STDOUT 1
%define STDERR 2
%define SIG_BLOCK 0
%define SIGPIPE 13
%define AT_FDCWD -100
%define AT_SYMLINK_NOFOLLOW 0x100
%define AT_EMPTY_PATH 0x1000
%define STATX_BASIC_STATS 0x7FF
%define STATX_BTIME 0x800
; struct stat offsets (x86-64 Linux)
%define ST_DEV 0
%define ST_INO 8
%define ST_NLINK 16
%define ST_MODE 24
%define ST_UID 28
%define ST_GID 32
%define ST_PAD0 36
%define ST_RDEV 40
%define ST_SIZE 48
%define ST_BLKSIZE 56
%define ST_BLOCKS 64
%define ST_ATIME 72
%define ST_ATIME_NSEC 80
%define ST_MTIME 88
%define ST_MTIME_NSEC 96
%define ST_CTIME 104
%define ST_CTIME_NSEC 112
; struct statx offsets
%define STATX_MASK 0
%define STATX_BTIME_SEC 80
%define STATX_BTIME_NSEC 88
; struct statfs offsets (x86-64)
%define SF_TYPE 0
%define SF_BSIZE 8
%define SF_BLOCKS 16
%define SF_BFREE 24
%define SF_BAVAIL 32
%define SF_FILES 40
%define SF_FFREE 48
%define SF_FSID 56
%define SF_NAMELEN 64
; File type masks
%define S_IFMT 0xF000
%define S_IFSOCK 0xC000
%define S_IFLNK 0xA000
%define S_IFREG 0x8000
%define S_IFBLK 0x6000
%define S_IFDIR 0x4000
%define S_IFCHR 0x2000
%define S_IFIFO 0x1000
; Flag bits
%define FLAG_DEREF 0
%define FLAG_FS 1
%define FLAG_TERSE 2
%define FLAG_FORMAT 3
section .bss
stat_buf: resb 144
statfs_buf: resb 256
num_buf: resb 64
out_buf: resb 8192
link_buf: resb 4096
mode_buf: resb 16
out_pos: resq 1
statx_buf: resb 256
section .data
str_help:
db "Usage: stat [OPTION]... FILE...", 10
db "Display file or file system status.", 10, 10
db " -L, --dereference follow links", 10
db " -f, --file-system display file system status instead of file status", 10
db " -c --format=FORMAT use the specified FORMAT instead of the default;", 10
db " output a newline after each use of FORMAT", 10
db " --printf=FORMAT like --format, but interpret backslash escapes,", 10
db " and do not output a mandatory trailing newline;", 10
db " if you want a newline, include \n in FORMAT", 10
db " -t, --terse print the information in terse form", 10
db " --help display this help and exit", 10
db " --version output version information and exit", 10, 10
db "GNU coreutils online help: <https://www.gnu.org/software/coreutils/>", 10
db "Full documentation <https://www.gnu.org/software/coreutils/stat>", 10
db "or available locally via: info '(coreutils) stat invocation'", 10
str_help_len equ $ - str_help
str_version:
db "stat (GNU coreutils) 9.7", 10
db "Copyright (C) 2025 Free Software Foundation, Inc.", 10
db "License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.", 10
db "This is free software: you are free to change and redistribute it.", 10
db "There is NO WARRANTY, to the extent permitted by law.", 10, 10
db "Written by Michael Meskes.", 10
str_version_len equ $ - str_version
str_prefix: db "stat: "
str_prefix_len equ $ - str_prefix
str_unrecog: db "unrecognized option '"
str_unrecog_len equ $ - str_unrecog
str_invalid: db "invalid option -- '"
str_invalid_len equ $ - str_invalid
str_missing: db "missing operand", 10
str_missing_len equ $ - str_missing
str_missing_fmt: db "option requires an argument -- 'c'", 10
str_missing_fmt_len equ $ - str_missing_fmt
str_sq_nl: db "'", 10
str_sq: db "'"
str_try: db "Try 'stat --help' for more information.", 10
str_try_len equ $ - str_try
str_cannot_stat: db "cannot stat '"
str_cannot_stat_len equ $ - str_cannot_stat
str_newline: db 10
str_space: db ' '
str_slash: db '/'
str_lparen: db '('
str_rparen: db ')'
str_dash: db '-'
str_zero: db '0'
str_512: db "512"
str_help_flag: db "--help", 0
str_version_flag: db "--version", 0
str_deref_flag: db "--dereference", 0
str_filesys_flag: db "--file-system", 0
str_terse_flag: db "--terse", 0
str_format_eq: db "--format=", 0
str_printf_eq: db "--printf=", 0
; Default output labels
str_file_label: db " File: "
str_file_label_len equ $ - str_file_label
str_arrow: db " -> "
str_arrow_len equ $ - str_arrow
str_size_label: db " Size: "
str_size_label_len equ $ - str_size_label
str_blocks_label: db 9, "Blocks: "
str_blocks_label_len equ $ - str_blocks_label
str_ioblock_label: db " IO Block: "
str_ioblock_label_len equ $ - str_ioblock_label
str_type_sep: db " "
str_type_sep_len equ $ - str_type_sep
str_device_label: db "Device: "
str_device_label_len equ $ - str_device_label
str_inode_label: db 9, "Inode: "
str_inode_label_len equ $ - str_inode_label
str_links_label: db " Links: "
str_links_label_len equ $ - str_links_label
str_access_label: db "Access: "
str_access_label_len equ $ - str_access_label
str_uid_label: db " Uid: ( "
str_uid_label_len equ $ - str_uid_label
str_uid_sep: db "/ ?)"
str_uid_sep_len equ $ - str_uid_sep
str_gid_label: db " Gid: ( "
str_gid_label_len equ $ - str_gid_label
str_gid_sep: db "/ ?)"
str_gid_sep_len equ $ - str_gid_sep
str_access_ts: db "Access: "
str_access_ts_len equ $ - str_access_ts
str_modify_ts: db "Modify: "
str_modify_ts_len equ $ - str_modify_ts
str_change_ts: db "Change: "
str_change_ts_len equ $ - str_change_ts
str_birth_ts: db " Birth: -"
str_birth_ts_len equ $ - str_birth_ts
str_birth_label: db " Birth: "
str_birth_label_len equ $ - str_birth_label
; File type strings
str_type_reg: db "regular file"
str_type_reg_len equ $ - str_type_reg
str_type_reg_empty: db "regular empty file"
str_type_reg_empty_len equ $ - str_type_reg_empty
str_type_dir: db "directory"
str_type_dir_len equ $ - str_type_dir
str_type_lnk: db "symbolic link"
str_type_lnk_len equ $ - str_type_lnk
str_type_chr: db "character special file"
str_type_chr_len equ $ - str_type_chr
str_type_blk: db "block special file"
str_type_blk_len equ $ - str_type_blk
str_type_fifo: db "fifo"
str_type_fifo_len equ $ - str_type_fifo
str_type_sock: db "socket"
str_type_sock_len equ $ - str_type_sock
str_type_unknown: db "weird file"
str_type_unknown_len equ $ - str_type_unknown
; Filesystem labels
str_fs_file_label: db " File: "
str_fs_file_label_len equ $ - str_fs_file_label
str_fs_id_label: db " ID: "
str_fs_id_label_len equ $ - str_fs_id_label
str_fs_namelen_label: db " Namelen: "
str_fs_namelen_label_len equ $ - str_fs_namelen_label
str_fs_type_label: db " Type: "
str_fs_type_label_len equ $ - str_fs_type_label
str_fs_bsize_label: db "Block size: "
str_fs_bsize_label_len equ $ - str_fs_bsize_label
str_fs_blocks_label: db " Blocks: Total: "
str_fs_blocks_label_len equ $ - str_fs_blocks_label
str_fs_bfree_label: db " Free: "
str_fs_bfree_label_len equ $ - str_fs_bfree_label
str_fs_bavail_label: db " Available: "
str_fs_bavail_label_len equ $ - str_fs_bavail_label
str_fs_files_label: db "Inodes: Total: "
str_fs_files_label_len equ $ - str_fs_files_label
str_fs_ffree_label: db " Free: "
str_fs_ffree_label_len equ $ - str_fs_ffree_label
; Error message parts
str_err_noent: db ": No such file or directory", 10
str_err_noent_len equ $ - str_err_noent
str_err_acces: db ": Permission denied", 10
str_err_acces_len equ $ - str_err_acces
str_err_notdir: db ": Not a directory", 10
str_err_notdir_len equ $ - str_err_notdir
section .text
global _start
_start:
; Block SIGPIPE
sub rsp, 16
mov qword [rsp], 0
bts qword [rsp], SIGPIPE
mov eax, SYS_RT_SIGPROCMASK
mov edi, SIG_BLOCK
mov rsi, rsp
xor edx, edx
mov r10d, 8
syscall
add rsp, 16
; Save argc/argv
mov r14d, [rsp]
lea r15, [rsp + 8]
; Initialize
xor ebx, ebx ; flags
xor r13d, r13d ; format ptr
mov ecx, 1 ; arg index
; Parse options
.parse_opts:
cmp ecx, r14d
jge .err_missing_operand
mov rdi, [r15 + rcx*8]
cmp byte [rdi], '-'
jne .done_opts
cmp byte [rdi + 1], 0
je .done_opts
cmp byte [rdi + 1], '-'
je .check_long
; Short options
inc rdi
.short_loop:
movzx eax, byte [rdi]
test al, al
jz .next_opt
cmp al, 'L'
je .set_deref
cmp al, 'f'
je .set_fs
cmp al, 't'
je .set_terse
cmp al, 'c'
je .set_format
; Invalid short flag
mov r13, rdi
mov rsi, str_prefix
mov edx, str_prefix_len
call do_write_err
mov rsi, str_invalid
mov edx, str_invalid_len
call do_write_err
mov rsi, r13
mov edx, 1
call do_write_err
mov rsi, str_sq_nl
mov edx, 2
call do_write_err
mov rsi, str_try
mov edx, str_try_len
call do_write_err
mov edi, 1
jmp do_exit
.set_deref:
or bl, (1 << FLAG_DEREF)
inc rdi
jmp .short_loop
.set_fs:
or bl, (1 << FLAG_FS)
inc rdi
jmp .short_loop
.set_terse:
or bl, (1 << FLAG_TERSE)
inc rdi
jmp .short_loop
.set_format:
or bl, (1 << FLAG_FORMAT)
inc rdi
; Check if rest of short opt string is the format
cmp byte [rdi], 0
jne .format_inline
; Next arg is the format string
inc ecx
cmp ecx, r14d
jge .err_missing_format
mov r13, [r15 + rcx*8]
inc ecx
jmp .parse_opts
.format_inline:
mov r13, rdi
jmp .next_opt
.check_long:
cmp byte [rdi + 2], 0
je .double_dash
push rcx
mov r9, rdi
; --help
mov rsi, str_help_flag
call str_eq
test eax, eax
jnz .pop_show_help
; --version
mov rdi, r9
mov rsi, str_version_flag
call str_eq
test eax, eax
jnz .pop_show_version
; --dereference
mov rdi, r9
mov rsi, str_deref_flag
call str_eq
test eax, eax
jnz .pop_set_deref
; --file-system
mov rdi, r9
mov rsi, str_filesys_flag
call str_eq
test eax, eax
jnz .pop_set_fs
; --terse
mov rdi, r9
mov rsi, str_terse_flag
call str_eq
test eax, eax
jnz .pop_set_terse
; --format=...
mov rdi, r9
mov rsi, str_format_eq
mov edx, 9
call str_prefix_match
test eax, eax
jnz .pop_set_format_long
; --printf=...
mov rdi, r9
mov rsi, str_printf_eq
mov edx, 9
call str_prefix_match
test eax, eax
jnz .pop_set_printf_long
; Unrecognized
mov rsi, str_prefix
mov edx, str_prefix_len
call do_write_err
mov rsi, str_unrecog
mov edx, str_unrecog_len
call do_write_err
mov rdi, r9
call str_len
mov edx, eax
mov rsi, r9
call do_write_err
mov rsi, str_sq_nl
mov edx, 2
call do_write_err
mov rsi, str_try
mov edx, str_try_len
call do_write_err
pop rcx
mov edi, 1
jmp do_exit
.pop_show_help:
pop rcx
mov edi, STDOUT
mov rsi, str_help
mov edx, str_help_len
call do_write
xor edi, edi
jmp do_exit
.pop_show_version:
pop rcx
mov edi, STDOUT
mov rsi, str_version
mov edx, str_version_len
call do_write
xor edi, edi
jmp do_exit
.pop_set_deref:
pop rcx
or bl, (1 << FLAG_DEREF)
inc ecx
jmp .parse_opts
.pop_set_fs:
pop rcx
or bl, (1 << FLAG_FS)
inc ecx
jmp .parse_opts
.pop_set_terse:
pop rcx
or bl, (1 << FLAG_TERSE)
inc ecx
jmp .parse_opts
.pop_set_format_long:
pop rcx
or bl, (1 << FLAG_FORMAT)
lea r13, [r9 + 9] ; skip "--format="
inc ecx
jmp .parse_opts
.pop_set_printf_long:
pop rcx
or bl, (1 << FLAG_FORMAT)
lea r13, [r9 + 9] ; skip "--printf="
inc ecx
jmp .parse_opts
.double_dash:
inc ecx
jmp .done_opts
.next_opt:
inc ecx
jmp .parse_opts
.done_opts:
cmp ecx, r14d
jge .err_missing_operand
; Process files
mov r12d, ecx
xor ebp, ebp ; exit code
.file_loop:
cmp r12d, r14d
jge .exit_done
mov rdi, [r15 + r12*8]
; Reset output buffer
mov qword [rel out_pos], 0
test bl, (1 << FLAG_FS)
jnz .do_statfs
; stat or lstat the file
test bl, (1 << FLAG_DEREF)
jnz .do_stat_deref
; lstat (default: don't follow symlinks)
mov eax, SYS_LSTAT
lea rsi, [rel stat_buf]
syscall
test rax, rax
js .stat_failed
; rdi still has filename
; Call statx with AT_SYMLINK_NOFOLLOW to get birth time
mov rsi, rdi ; rsi = pathname
mov eax, SYS_STATX
mov edi, AT_FDCWD ; dirfd
mov edx, AT_SYMLINK_NOFOLLOW ; flags
mov r10d, STATX_BASIC_STATS | STATX_BTIME ; mask
lea r8, [rel statx_buf] ; statxbuf
syscall
; If statx fails, zero out birth time
test rax, rax
jns .stat_ok
mov qword [rel statx_buf + STATX_BTIME_SEC], 0
mov dword [rel statx_buf + STATX_BTIME_NSEC], 0
jmp .stat_ok
.do_stat_deref:
mov eax, SYS_STAT
lea rsi, [rel stat_buf]
syscall
test rax, rax
js .stat_failed
; rdi still has filename
; Call statx without AT_SYMLINK_NOFOLLOW (follow symlinks) to get birth time
mov rsi, rdi ; rsi = pathname
mov eax, SYS_STATX
mov edi, AT_FDCWD ; dirfd
xor edx, edx ; flags = 0 (follow symlinks)
mov r10d, STATX_BASIC_STATS | STATX_BTIME ; mask
lea r8, [rel statx_buf] ; statxbuf
syscall
; If statx fails, zero out birth time
test rax, rax
jns .stat_ok
mov qword [rel statx_buf + STATX_BTIME_SEC], 0
mov dword [rel statx_buf + STATX_BTIME_NSEC], 0
jmp .stat_ok
.do_statfs:
mov eax, SYS_STATFS
lea rsi, [rel statfs_buf]
syscall
test rax, rax
js .stat_failed
; Output filesystem info
test bl, (1 << FLAG_FORMAT)
jnz .format_fs_custom
test bl, (1 << FLAG_TERSE)
jnz .terse_fs_output
jmp .default_fs_output
.stat_ok:
test bl, (1 << FLAG_FORMAT)
jnz .format_custom
test bl, (1 << FLAG_TERSE)
jnz .terse_output
jmp .default_output
.stat_failed:
; Print error: "stat: cannot stat 'file': No such file or directory"
neg rax
mov r9, rax ; save errno
mov rsi, str_prefix
mov edx, str_prefix_len
call do_write_err
mov rsi, str_cannot_stat
mov edx, str_cannot_stat_len
call do_write_err
; Print the filename in quotes
mov rsi, str_sq
mov edx, 1
call do_write_err
mov rdi, [r15 + r12*8]
call str_len
mov edx, eax
mov rsi, rdi
call do_write_err
mov rsi, str_sq_nl
mov edx, 2
call do_write_err
; Pick error message based on errno
cmp r9d, 2 ; ENOENT
je .stat_err_noent
cmp r9d, 13 ; EACCES
je .stat_err_acces
cmp r9d, 20 ; ENOTDIR
je .stat_err_notdir
; Default: No such file or directory
.stat_err_noent:
jmp .stat_err_done
.stat_err_acces:
jmp .stat_err_done
.stat_err_notdir:
.stat_err_done:
mov ebp, 1
inc r12d
jmp .file_loop
; ============================================================
; Default output format (like GNU stat)
; ============================================================
.default_output:
push r12
mov r12, [r15 + r12*8] ; filename ptr
; " File: " + filename
mov rsi, str_file_label
mov edx, str_file_label_len
call buf_write
mov rdi, r12
call str_len
mov edx, eax
mov rsi, r12
call buf_write
; Check if symlink, append " -> target"
movzx eax, word [rel stat_buf + ST_MODE]
and eax, S_IFMT
cmp eax, S_IFLNK
jne .def_after_link
mov rsi, str_arrow
mov edx, str_arrow_len
call buf_write
; readlinkat to get target
mov eax, SYS_READLINKAT
mov edi, AT_FDCWD
mov rsi, r12
lea rdx, [rel link_buf]
mov r10d, 4095
syscall
test rax, rax
js .def_after_link
mov byte [rel link_buf + rax], 0
mov edx, eax
lea rsi, [rel link_buf]
call buf_write
.def_after_link:
mov rsi, str_newline
mov edx, 1
call buf_write
; " Size: N Blocks: N IO Block: N type"
mov rsi, str_size_label
mov edx, str_size_label_len
call buf_write
mov rdi, [rel stat_buf + ST_SIZE]
call format_u64
call buf_write
mov rsi, str_blocks_label
mov edx, str_blocks_label_len
call buf_write
mov rdi, [rel stat_buf + ST_BLOCKS]
call format_u64
call buf_write
mov rsi, str_ioblock_label
mov edx, str_ioblock_label_len
call buf_write
mov rdi, [rel stat_buf + ST_BLKSIZE]
call format_u64
call buf_write
; File type
mov rsi, str_type_sep
mov edx, str_type_sep_len
call buf_write
movzx edi, word [rel stat_buf + ST_MODE]
call get_file_type_str
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; "Device: Xh/Yd Inode: N Links: N"
mov rsi, str_device_label
mov edx, str_device_label_len
call buf_write
mov rdi, [rel stat_buf + ST_DEV]
call format_dev_hex
call buf_write
mov rsi, str_inode_label
mov edx, str_inode_label_len
call buf_write
mov rdi, [rel stat_buf + ST_INO]
call format_u64
call buf_write
mov rsi, str_links_label
mov edx, str_links_label_len
call buf_write
mov rdi, [rel stat_buf + ST_NLINK]
call format_u64
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; "Access: (OCTAL/PERMS) Uid: ( N/ name) Gid: ( N/ name)"
mov rsi, str_access_label
mov edx, str_access_label_len
call buf_write
mov rsi, str_lparen
mov edx, 1
call buf_write
movzx edi, word [rel stat_buf + ST_MODE]
call format_octal_mode
call buf_write
mov rsi, str_slash
mov edx, 1
call buf_write
movzx edi, word [rel stat_buf + ST_MODE]
call format_rwx_mode
call buf_write
mov rsi, str_rparen
mov edx, 1
call buf_write
mov rsi, str_uid_label
mov edx, str_uid_label_len
call buf_write
mov edi, dword [rel stat_buf + ST_UID]
call format_u64_rdi
call buf_write
mov rsi, str_uid_sep
mov edx, str_uid_sep_len
call buf_write
mov rsi, str_gid_label
mov edx, str_gid_label_len
call buf_write
mov edi, dword [rel stat_buf + ST_GID]
call format_u64_rdi
call buf_write
mov rsi, str_gid_sep
mov edx, str_gid_sep_len
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; "Access: timestamp"
mov rsi, str_access_ts
mov edx, str_access_ts_len
call buf_write
mov rdi, [rel stat_buf + ST_ATIME]
mov rsi, [rel stat_buf + ST_ATIME_NSEC]
call format_timestamp
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; "Modify: timestamp"
mov rsi, str_modify_ts
mov edx, str_modify_ts_len
call buf_write
mov rdi, [rel stat_buf + ST_MTIME]
mov rsi, [rel stat_buf + ST_MTIME_NSEC]
call format_timestamp
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; "Change: timestamp"
mov rsi, str_change_ts
mov edx, str_change_ts_len
call buf_write
mov rdi, [rel stat_buf + ST_CTIME]
mov rsi, [rel stat_buf + ST_CTIME_NSEC]
call format_timestamp
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; " Birth: <timestamp>" or " Birth: -"
mov rdi, [rel statx_buf + STATX_BTIME_SEC]
test rdi, rdi
jz .def_birth_unknown
; Have birth time
mov rsi, str_birth_label
mov edx, str_birth_label_len
call buf_write
mov rdi, [rel statx_buf + STATX_BTIME_SEC]
mov esi, dword [rel statx_buf + STATX_BTIME_NSEC]
call format_timestamp
call buf_write
jmp .def_birth_done
.def_birth_unknown:
mov rsi, str_birth_ts
mov edx, str_birth_ts_len
call buf_write
.def_birth_done:
mov rsi, str_newline
mov edx, 1
call buf_write
; Flush output buffer
call buf_flush
pop r12
inc r12d
jmp .file_loop
; ============================================================
; Terse output: %n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o
; ============================================================
.terse_output:
push r12
mov r12, [r15 + r12*8]
; name
mov rdi, r12
call str_len
mov edx, eax
mov rsi, r12
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; size
mov rdi, [rel stat_buf + ST_SIZE]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; blocks
mov rdi, [rel stat_buf + ST_BLOCKS]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; raw mode hex
movzx edi, word [rel stat_buf + ST_MODE]
call format_hex_lower
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; uid
mov edi, dword [rel stat_buf + ST_UID]
call format_u64_rdi
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; gid
mov edi, dword [rel stat_buf + ST_GID]
call format_u64_rdi
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; device hex
mov rdi, [rel stat_buf + ST_DEV]
call format_hex_lower_full
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; inode
mov rdi, [rel stat_buf + ST_INO]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; links
mov rdi, [rel stat_buf + ST_NLINK]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; major hex
mov rdi, [rel stat_buf + ST_RDEV]
shr rdi, 8
and edi, 0xFFF
call format_hex_lower
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; minor hex
mov rdi, [rel stat_buf + ST_RDEV]
mov rax, rdi
and edi, 0xFF
shr rax, 12
and eax, 0xFFF00
or edi, eax
call format_hex_lower
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; atime epoch
mov rdi, [rel stat_buf + ST_ATIME]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; mtime epoch
mov rdi, [rel stat_buf + ST_MTIME]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; ctime epoch
mov rdi, [rel stat_buf + ST_CTIME]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; birth epoch (from statx, 0 if unavailable)
mov rdi, [rel statx_buf + STATX_BTIME_SEC]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; IO block size
mov rdi, [rel stat_buf + ST_BLKSIZE]
call format_u64
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
call buf_flush
pop r12
inc r12d
jmp .file_loop
; ============================================================
; Custom format output (-c FORMAT)
; ============================================================
.format_custom:
push r12
mov r12, [r15 + r12*8] ; filename
mov rdi, r13 ; format string
.fc_loop:
movzx eax, byte [rdi]
test al, al
jz .fc_done
cmp al, '%'
je .fc_percent
cmp al, '\'
je .fc_escape
; Regular char
lea rcx, [rel num_buf]
mov byte [rcx], al
push rdi
mov rsi, rcx
mov edx, 1
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_escape:
inc rdi
movzx eax, byte [rdi]
test al, al
jz .fc_done
cmp al, 'n'
je .fc_esc_nl
cmp al, 't'
je .fc_esc_tab
cmp al, '\'
je .fc_esc_bslash
; Unknown escape - output literally
lea rcx, [rel num_buf]
mov byte [rcx], '\'
push rdi
mov rsi, rcx
mov edx, 1
call buf_write
pop rdi
jmp .fc_loop
.fc_esc_nl:
push rdi
mov rsi, str_newline
mov edx, 1
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_esc_tab:
push rdi
lea rcx, [rel num_buf]
mov byte [rcx], 9
mov rsi, rcx
mov edx, 1
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_esc_bslash:
push rdi
lea rcx, [rel num_buf]
mov byte [rcx], '\'
mov rsi, rcx
mov edx, 1
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_percent:
inc rdi
movzx eax, byte [rdi]
cmp al, '%'
je .fc_pct_literal
cmp al, 'a'
je .fc_pct_a
cmp al, 'A'
je .fc_pct_A
cmp al, 'b'
je .fc_pct_b
cmp al, 'B'
je .fc_pct_B
cmp al, 'd'
je .fc_pct_d
cmp al, 'D'
je .fc_pct_D
cmp al, 'f'
je .fc_pct_f
cmp al, 'F'
je .fc_pct_F
cmp al, 'g'
je .fc_pct_g
cmp al, 'G'
je .fc_pct_G
cmp al, 'h'
je .fc_pct_h
cmp al, 'i'
je .fc_pct_i
cmp al, 'n'
je .fc_pct_n
cmp al, 'N'
je .fc_pct_N
cmp al, 'o'
je .fc_pct_o
cmp al, 's'
je .fc_pct_s
cmp al, 't'
je .fc_pct_t
cmp al, 'T'
je .fc_pct_T
cmp al, 'u'
je .fc_pct_u
cmp al, 'U'
je .fc_pct_U
cmp al, 'w'
je .fc_pct_w
cmp al, 'W'
je .fc_pct_W
cmp al, 'x'
je .fc_pct_x
cmp al, 'X'
je .fc_pct_X
cmp al, 'y'
je .fc_pct_y
cmp al, 'Y'
je .fc_pct_Y
cmp al, 'z'
je .fc_pct_z
cmp al, 'Z'
je .fc_pct_Z
; Unknown %spec - output %X literally
lea rcx, [rel num_buf]
mov byte [rcx], '%'
mov byte [rcx + 1], al
push rdi
mov rsi, rcx
mov edx, 2
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_literal:
push rdi
lea rcx, [rel num_buf]
mov byte [rcx], '%'
mov rsi, rcx
mov edx, 1
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_a: ; octal mode
push rdi
movzx edi, word [rel stat_buf + ST_MODE]
call format_octal_mode
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_A: ; rwx mode
push rdi
movzx edi, word [rel stat_buf + ST_MODE]
call format_rwx_mode
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_b: ; blocks
push rdi
mov rdi, [rel stat_buf + ST_BLOCKS]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_B: ; block size (512)
push rdi
mov rsi, str_512
mov edx, 3
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_d: ; device decimal
push rdi
mov rdi, [rel stat_buf + ST_DEV]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_D: ; device hex
push rdi
mov rdi, [rel stat_buf + ST_DEV]
call format_hex_lower_full
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_f: ; raw mode hex
push rdi
movzx edi, word [rel stat_buf + ST_MODE]
call format_hex_lower
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_F: ; file type
push rdi
movzx edi, word [rel stat_buf + ST_MODE]
call get_file_type_str
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_g: ; gid
push rdi
mov edi, dword [rel stat_buf + ST_GID]
call format_u64_rdi
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_G: ; group name (just numeric for assembly)
push rdi
mov edi, dword [rel stat_buf + ST_GID]
call format_u64_rdi
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_h: ; nlinks
push rdi
mov rdi, [rel stat_buf + ST_NLINK]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_i: ; inode
push rdi
mov rdi, [rel stat_buf + ST_INO]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_n: ; name
push rdi
mov rdi, r12
call str_len
mov edx, eax
mov rsi, r12
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_N: ; quoted name (with link target for symlinks)
push rdi
; Print quoted name
mov rsi, str_sq
mov edx, 1
call buf_write
mov rdi, r12
call str_len
mov edx, eax
mov rsi, r12
call buf_write
mov rsi, str_sq
mov edx, 1
call buf_write
; Check if symlink
movzx eax, word [rel stat_buf + ST_MODE]
and eax, S_IFMT
cmp eax, S_IFLNK
jne .fc_pct_N_done
mov rsi, str_arrow
mov edx, str_arrow_len
call buf_write
mov rsi, str_sq
mov edx, 1
call buf_write
; readlinkat
mov eax, SYS_READLINKAT
mov edi, AT_FDCWD
mov rsi, r12
lea rdx, [rel link_buf]
mov r10d, 4095
syscall
test rax, rax
js .fc_pct_N_done
mov byte [rel link_buf + rax], 0
mov edx, eax
lea rsi, [rel link_buf]
call buf_write
mov rsi, str_sq
mov edx, 1
call buf_write
.fc_pct_N_done:
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_o: ; IO block
push rdi
mov rdi, [rel stat_buf + ST_BLKSIZE]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_s: ; size
push rdi
mov rdi, [rel stat_buf + ST_SIZE]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_t: ; major hex
push rdi
mov rdi, [rel stat_buf + ST_RDEV]
shr rdi, 8
and edi, 0xFFF
call format_hex_lower
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_T: ; minor hex
push rdi
mov rdi, [rel stat_buf + ST_RDEV]
mov rax, rdi
and edi, 0xFF
shr rax, 12
and eax, 0xFFF00
or edi, eax
call format_hex_lower
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_u: ; uid
push rdi
mov edi, dword [rel stat_buf + ST_UID]
call format_u64_rdi
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_U: ; user name (just numeric)
push rdi
mov edi, dword [rel stat_buf + ST_UID]
call format_u64_rdi
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_w: ; birth time (formatted timestamp or "-")
push rdi
mov rdi, [rel statx_buf + STATX_BTIME_SEC]
test rdi, rdi
jz .fc_pct_w_unknown
mov esi, dword [rel statx_buf + STATX_BTIME_NSEC]
call format_timestamp
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_w_unknown:
mov rsi, str_dash
mov edx, 1
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_W: ; birth epoch (seconds or 0)
push rdi
mov rdi, [rel statx_buf + STATX_BTIME_SEC]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_x: ; access time
push rdi
mov rdi, [rel stat_buf + ST_ATIME]
mov rsi, [rel stat_buf + ST_ATIME_NSEC]
call format_timestamp
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_X: ; access epoch
push rdi
mov rdi, [rel stat_buf + ST_ATIME]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_y: ; modify time
push rdi
mov rdi, [rel stat_buf + ST_MTIME]
mov rsi, [rel stat_buf + ST_MTIME_NSEC]
call format_timestamp
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_Y: ; modify epoch
push rdi
mov rdi, [rel stat_buf + ST_MTIME]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_z: ; change time
push rdi
mov rdi, [rel stat_buf + ST_CTIME]
mov rsi, [rel stat_buf + ST_CTIME_NSEC]
call format_timestamp
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_pct_Z: ; change epoch
push rdi
mov rdi, [rel stat_buf + ST_CTIME]
call format_u64
call buf_write
pop rdi
inc rdi
jmp .fc_loop
.fc_done:
; Flush and add newline for -c (not --printf)
mov rsi, str_newline
mov edx, 1
call buf_write
call buf_flush
pop r12
inc r12d
jmp .file_loop
; ============================================================
; Default filesystem output
; ============================================================
.default_fs_output:
push r12
mov r12, [r15 + r12*8]
mov rsi, str_fs_file_label
mov edx, str_fs_file_label_len
call buf_write
mov rdi, r12
call str_len
mov edx, eax
mov rsi, r12
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; ID / Namelen / Type
mov rsi, str_fs_id_label
mov edx, str_fs_id_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_FSID]
call format_hex_lower_full
call buf_write
mov rsi, str_fs_namelen_label
mov edx, str_fs_namelen_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_NAMELEN]
call format_u64
call buf_write
mov rsi, str_fs_type_label
mov edx, str_fs_type_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_TYPE]
call format_hex_lower
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; Block size / Blocks / Free / Available
mov rsi, str_fs_bsize_label
mov edx, str_fs_bsize_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_BSIZE]
call format_u64
call buf_write
mov rsi, str_fs_blocks_label
mov edx, str_fs_blocks_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_BLOCKS]
call format_u64
call buf_write
mov rsi, str_fs_bfree_label
mov edx, str_fs_bfree_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_BFREE]
call format_u64
call buf_write
mov rsi, str_fs_bavail_label
mov edx, str_fs_bavail_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_BAVAIL]
call format_u64
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
; Inodes / Free inodes
mov rsi, str_fs_files_label
mov edx, str_fs_files_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_FILES]
call format_u64
call buf_write
mov rsi, str_fs_ffree_label
mov edx, str_fs_ffree_label_len
call buf_write
mov rdi, [rel statfs_buf + SF_FFREE]
call format_u64
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
call buf_flush
pop r12
inc r12d
jmp .file_loop
; ============================================================
; Terse filesystem output
; ============================================================
.terse_fs_output:
push r12
mov r12, [r15 + r12*8]
; name
mov rdi, r12
call str_len
mov edx, eax
mov rsi, r12
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; fs id hex
mov rdi, [rel statfs_buf + SF_FSID]
call format_hex_lower_full
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; namelen
mov rdi, [rel statfs_buf + SF_NAMELEN]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; type hex
mov rdi, [rel statfs_buf + SF_TYPE]
call format_hex_lower
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; bsize
mov rdi, [rel statfs_buf + SF_BSIZE]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; blocks
mov rdi, [rel statfs_buf + SF_BLOCKS]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; bfree
mov rdi, [rel statfs_buf + SF_BFREE]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; bavail
mov rdi, [rel statfs_buf + SF_BAVAIL]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; files
mov rdi, [rel statfs_buf + SF_FILES]
call format_u64
call buf_write
mov rsi, str_space
mov edx, 1
call buf_write
; ffree
mov rdi, [rel statfs_buf + SF_FFREE]
call format_u64
call buf_write
mov rsi, str_newline
mov edx, 1
call buf_write
call buf_flush
pop r12
inc r12d
jmp .file_loop
; Filesystem custom format stub
.format_fs_custom:
jmp .terse_fs_output
.exit_done:
mov edi, ebp
jmp do_exit
.err_missing_operand:
mov rsi, str_prefix
mov edx, str_prefix_len
call do_write_err
mov rsi, str_missing
mov edx, str_missing_len
call do_write_err
mov rsi, str_try
mov edx, str_try_len
call do_write_err
mov edi, 1
jmp do_exit
.err_missing_format:
mov rsi, str_prefix
mov edx, str_prefix_len
call do_write_err
mov rsi, str_missing_fmt
mov edx, str_missing_fmt_len
call do_write_err
mov edi, 1
jmp do_exit
; ============================================================
; Output buffer routines
; ============================================================
buf_write:
; rsi = data, edx = length
push rdi
push rcx
mov rcx, [rel out_pos]
; Copy to out_buf + rcx
xor eax, eax
.bw_loop:
cmp eax, edx
jge .bw_done
movzx r8d, byte [rsi + rax]
lea r9, [rel out_buf]
mov byte [r9 + rcx], r8b
inc rcx
inc eax
; Flush if buffer near full
cmp ecx, 8000
jl .bw_loop
mov [rel out_pos], rcx
push rsi
push rdx
push rax
call buf_flush
pop rax
pop rdx
pop rsi
xor ecx, ecx
jmp .bw_loop
.bw_done:
mov [rel out_pos], rcx
pop rcx
pop rdi
ret
buf_flush:
mov rcx, [rel out_pos]
test rcx, rcx
jz .bf_done
mov edi, STDOUT
lea rsi, [rel out_buf]
mov edx, ecx
call do_write
mov qword [rel out_pos], 0
.bf_done:
ret
; ============================================================
; Number formatting
; ============================================================
; format_u64: format rdi as unsigned decimal
; Returns: rsi = start, edx = length
format_u64:
mov rax, rdi
lea rcx, [rel num_buf + 63]
mov byte [rcx], 0
mov r8d, 10
.fu_loop:
xor edx, edx
div r8
add dl, '0'
dec rcx
mov byte [rcx], dl
test rax, rax
jnz .fu_loop
mov rsi, rcx
lea rax, [rel num_buf + 63]
sub rax, rcx
mov edx, eax
ret
; format_u64_rdi: same but for 32-bit values zero-extended
format_u64_rdi:
mov edi, edi ; zero-extend edi to rdi
jmp format_u64
; format_hex_lower: format edi as lowercase hex (no leading zeros, no 0x prefix)
format_hex_lower:
mov eax, edi
lea rcx, [rel num_buf + 63]
mov byte [rcx], 0
test eax, eax
jnz .fhl_loop
dec rcx
mov byte [rcx], '0'
jmp .fhl_done
.fhl_loop:
test eax, eax
jz .fhl_done
mov edx, eax
and edx, 0xF
cmp edx, 10
jl .fhl_digit
add dl, ('a' - 10)
jmp .fhl_store
.fhl_digit:
add dl, '0'
.fhl_store:
dec rcx
mov byte [rcx], dl
shr eax, 4
jmp .fhl_loop
.fhl_done:
mov rsi, rcx
lea rax, [rel num_buf + 63]
sub rax, rcx
mov edx, eax
ret
; format_hex_lower_full: format rdi as lowercase hex (no leading zeros)
format_hex_lower_full:
mov rax, rdi
lea rcx, [rel num_buf + 63]
mov byte [rcx], 0
test rax, rax
jnz .fhlf_loop
dec rcx
mov byte [rcx], '0'
jmp .fhlf_done
.fhlf_loop:
test rax, rax
jz .fhlf_done
mov edx, eax
and edx, 0xF
cmp edx, 10
jl .fhlf_digit
add dl, ('a' - 10)
jmp .fhlf_store
.fhlf_digit:
add dl, '0'
.fhlf_store:
dec rcx
mov byte [rcx], dl
shr rax, 4
jmp .fhlf_loop
.fhlf_done:
mov rsi, rcx
lea rax, [rel num_buf + 63]
sub rax, rcx
mov edx, eax
ret
; format_octal_mode: format lower 12 bits of edi as octal (no leading zeros)
; Returns: rsi = start, edx = length
format_octal_mode:
and edi, 0xFFF
mov eax, edi
lea rcx, [rel num_buf + 10]
mov byte [rcx], 0
; Handle zero specially
test eax, eax
jnz .fom_loop
dec rcx
mov byte [rcx], '0'
jmp .fom_done
.fom_loop:
test eax, eax
jz .fom_done
mov edx, eax
and edx, 7
add dl, '0'
dec rcx
mov byte [rcx], dl
shr eax, 3
jmp .fom_loop
.fom_done:
mov rsi, rcx
lea rax, [rel num_buf + 10]
sub rax, rcx
mov edx, eax
ret
; format_rwx_mode: format mode as drwxrwxrwx string (10 chars)
; Input: edi = mode, Returns: rsi = mode_buf, edx = 10
format_rwx_mode:
push rdi
; First char: file type
mov eax, edi
and eax, S_IFMT
lea r8, [rel mode_buf]
cmp eax, S_IFDIR
je .frm_dir
cmp eax, S_IFLNK
je .frm_lnk
cmp eax, S_IFCHR
je .frm_chr
cmp eax, S_IFBLK
je .frm_blk
cmp eax, S_IFIFO
je .frm_fifo
cmp eax, S_IFSOCK
je .frm_sock
mov byte [r8], '-'
jmp .frm_perms
.frm_dir:
mov byte [r8], 'd'
jmp .frm_perms
.frm_lnk:
mov byte [r8], 'l'
jmp .frm_perms
.frm_chr:
mov byte [r8], 'c'
jmp .frm_perms
.frm_blk:
mov byte [r8], 'b'
jmp .frm_perms
.frm_fifo:
mov byte [r8], 'p'
jmp .frm_perms
.frm_sock:
mov byte [r8], 's'
jmp .frm_perms
.frm_perms:
pop rdi
lea r8, [rel mode_buf]
; Owner rwx
mov byte [r8 + 1], '-'
test edi, 0x100
jz .frm_p2
mov byte [r8 + 1], 'r'
.frm_p2:
mov byte [r8 + 2], '-'
test edi, 0x80
jz .frm_p3
mov byte [r8 + 2], 'w'
.frm_p3:
mov byte [r8 + 3], '-'
test edi, 0x40
jz .frm_p3s
mov byte [r8 + 3], 'x'
.frm_p3s:
test edi, 0x800 ; setuid
jz .frm_p4
test edi, 0x40
jz .frm_p3S
mov byte [r8 + 3], 's'
jmp .frm_p4
.frm_p3S:
mov byte [r8 + 3], 'S'
.frm_p4:
; Group rwx
mov byte [r8 + 4], '-'
test edi, 0x20
jz .frm_p5
mov byte [r8 + 4], 'r'
.frm_p5:
mov byte [r8 + 5], '-'
test edi, 0x10
jz .frm_p6
mov byte [r8 + 5], 'w'
.frm_p6:
mov byte [r8 + 6], '-'
test edi, 0x8
jz .frm_p6s
mov byte [r8 + 6], 'x'
.frm_p6s:
test edi, 0x400 ; setgid
jz .frm_p7
test edi, 0x8
jz .frm_p6S
mov byte [r8 + 6], 's'
jmp .frm_p7
.frm_p6S:
mov byte [r8 + 6], 'S'
.frm_p7:
; Other rwx
mov byte [r8 + 7], '-'
test edi, 0x4
jz .frm_p8
mov byte [r8 + 7], 'r'
.frm_p8:
mov byte [r8 + 8], '-'
test edi, 0x2
jz .frm_p9
mov byte [r8 + 8], 'w'
.frm_p9:
mov byte [r8 + 9], '-'
test edi, 0x1
jz .frm_p9t
mov byte [r8 + 9], 'x'
.frm_p9t:
test edi, 0x200 ; sticky
jz .frm_done
test edi, 0x1
jz .frm_p9T
mov byte [r8 + 9], 't'
jmp .frm_done
.frm_p9T:
mov byte [r8 + 9], 'T'
.frm_done:
lea rsi, [rel mode_buf]
mov edx, 10
ret
; get_file_type_str: return file type string
; Input: edi = mode, Returns: rsi = string, edx = length
get_file_type_str:
and edi, S_IFMT
cmp edi, S_IFREG
je .gft_reg
cmp edi, S_IFDIR
je .gft_dir
cmp edi, S_IFLNK
je .gft_lnk
cmp edi, S_IFCHR
je .gft_chr
cmp edi, S_IFBLK
je .gft_blk
cmp edi, S_IFIFO
je .gft_fifo
cmp edi, S_IFSOCK
je .gft_sock
mov rsi, str_type_unknown
mov edx, str_type_unknown_len
ret
.gft_reg:
; Check if file size is 0 -> "regular empty file"
cmp qword [rel stat_buf + ST_SIZE], 0
jne .gft_reg_nonempty
mov rsi, str_type_reg_empty
mov edx, str_type_reg_empty_len
ret
.gft_reg_nonempty:
mov rsi, str_type_reg
mov edx, str_type_reg_len
ret
.gft_dir:
mov rsi, str_type_dir
mov edx, str_type_dir_len
ret
.gft_lnk:
mov rsi, str_type_lnk
mov edx, str_type_lnk_len
ret
.gft_chr:
mov rsi, str_type_chr
mov edx, str_type_chr_len
ret
.gft_blk:
mov rsi, str_type_blk
mov edx, str_type_blk_len
ret
.gft_fifo:
mov rsi, str_type_fifo
mov edx, str_type_fifo_len
ret
.gft_sock:
mov rsi, str_type_sock
mov edx, str_type_sock_len
ret
; format_dev_hex: format device number as "Xh/Yd"
; Input: rdi = st_dev, Returns: rsi, edx
format_dev_hex:
push rdi
call format_hex_lower_full
push rsi
push rdx
; Write hex part to num_buf+20 area
lea rdi, [rel num_buf + 20]
pop rcx ; hex len
pop rsi ; hex ptr
xor eax, eax
.fdh_copy:
cmp eax, ecx
jge .fdh_hex_done
movzx r8d, byte [rsi + rax]
mov byte [rdi + rax], r8b
inc eax
jmp .fdh_copy
.fdh_hex_done:
mov byte [rdi + rax], 'h'
inc eax
mov rsi, rdi
mov edx, eax
pop rdi
ret
; format_timestamp: format epoch time as "YYYY-MM-DD HH:MM:SS.NNNNNNNNN +0000"
; Input: rdi = epoch seconds, rsi = nanoseconds
; Returns: rsi = start, edx = length
format_timestamp:
push rbp
push rbx
push r12
push r13
push r14
mov r12, rdi ; epoch
mov r13, rsi ; nsec
; Compute date from epoch (days since 1970-01-01)
mov rax, r12
xor edx, edx
mov rcx, 86400
div rcx
mov rbx, rax ; days since epoch
mov r14, rdx ; remaining seconds
; Civil date from days (algorithm from Howard Hinnant)
add rax, 719468
mov rbp, rax
; era = floor(days / 146097)
xor edx, edx
mov rcx, 146097
cmp rbp, 0
jge .ts_pos_era
sub rbp, 146096
.ts_pos_era:
mov rax, rbp
xor edx, edx
div rcx
mov r8, rax ; era
; doe = days - era * 146097
imul rax, rcx
mov r9, rbp
sub r9, rax ; doe
; yoe = (doe - doe/1460 + doe/36524 - doe/146096) / 365
mov rax, r9
xor edx, edx
mov rcx, 1460
div rcx
mov r10, rax
mov rax, r9
xor edx, edx
mov rcx, 36524
div rcx
mov r11, rax
mov rax, r9
xor edx, edx
mov rcx, 146096
div rcx
mov rcx, r9
sub rcx, r10
add rcx, r11
sub rcx, rax
mov rax, rcx
xor edx, edx
mov rcx, 365
div rcx
mov r10, rax ; yoe
; year = yoe + era * 400
imul r8, 400
add r10, r8 ; year (March-based)
; doy = doe - (365*yoe + yoe/4 - yoe/100)
mov rax, r10
sub rax, r8 ; back to yoe
push rax
imul rax, 365
mov r11, rax
pop rax
push rax
xor edx, edx
mov rcx, 4
div rcx
add r11, rax
pop rax
xor edx, edx
mov rcx, 100
div rcx
sub r11, rax
mov rax, r9
sub rax, r11 ; doy
; mp = (5*doy + 2) / 153
imul rcx, rax, 5
add rcx, 2
push rax
mov rax, rcx
xor edx, edx
mov rcx, 153
div rcx
mov r8, rax ; mp
pop rax
; day = doy - (153*mp+2)/5 + 1
imul rcx, r8, 153
add rcx, 2
push rax
mov rax, rcx
xor edx, edx
mov rcx, 5
div rcx
mov r9, rax
pop rax
sub eax, r9d
inc eax
mov r9d, eax ; day (1-31)
; month = mp + (mp < 10 ? 3 : -9)
cmp r8d, 10
jl .ts_m_lt10
sub r8d, 9
jmp .ts_m_done
.ts_m_lt10:
add r8d, 3
.ts_m_done:
; If month <= 2, year++
cmp r8d, 2
jg .ts_y_done
inc r10
.ts_y_done:
; Now format: YYYY-MM-DD HH:MM:SS.NNNNNNNNN +0000
lea rdi, [rel num_buf]
; Year (4 digits)
mov eax, r10d
mov ecx, 1000
xor edx, edx
div ecx
add al, '0'
mov byte [rdi], al
mov eax, edx
mov ecx, 100
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+1], al
mov eax, edx
mov ecx, 10
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+2], al
add dl, '0'
mov byte [rdi+3], dl
mov byte [rdi+4], '-'
; Month (2 digits)
mov eax, r8d
mov ecx, 10
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+5], al
add dl, '0'
mov byte [rdi+6], dl
mov byte [rdi+7], '-'
; Day (2 digits)
mov eax, r9d
mov ecx, 10
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+8], al
add dl, '0'
mov byte [rdi+9], dl
mov byte [rdi+10], ' '
; Time from remaining seconds
mov rax, r14
xor edx, edx
mov ecx, 3600
div ecx
; al = hour
push rdx
mov ecx, 10
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+11], al
add dl, '0'
mov byte [rdi+12], dl
mov byte [rdi+13], ':'
pop rax
xor edx, edx
mov ecx, 60
div ecx
; al = minute
push rdx
mov ecx, 10
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+14], al
add dl, '0'
mov byte [rdi+15], dl
mov byte [rdi+16], ':'
pop rax
; al = second
mov ecx, 10
xor edx, edx
div ecx
add al, '0'
mov byte [rdi+17], al
add dl, '0'
mov byte [rdi+18], dl
mov byte [rdi+19], '.'
; Nanoseconds (9 digits)
mov rax, r13
mov ecx, 9
lea r8, [rdi + 29]
.ts_ns_loop:
xor edx, edx
push rcx
mov rcx, 10
div rcx
pop rcx
add dl, '0'
dec r8
mov byte [r8], dl
dec ecx
jnz .ts_ns_loop
; " +0000"
mov byte [rdi+29], ' '
mov byte [rdi+30], '+'
mov byte [rdi+31], '0'
mov byte [rdi+32], '0'
mov byte [rdi+33], '0'
mov byte [rdi+34], '0'
mov byte [rdi+35], 0
mov rsi, rdi
mov edx, 35
pop r14
pop r13
pop r12
pop rbx
pop rbp
ret
; ============================================================
; Utility functions
; ============================================================
do_write:
mov eax, SYS_WRITE
syscall
cmp rax, -4
je do_write
ret
do_write_err:
mov edi, STDERR
jmp do_write
do_exit:
mov eax, SYS_EXIT
syscall
str_len:
xor eax, eax
.sl_loop:
cmp byte [rdi + rax], 0
je .sl_done
inc eax
jmp .sl_loop
.sl_done:
ret
str_eq:
xor r8d, r8d
.se_loop:
movzx eax, byte [rdi + r8]
movzx edx, byte [rsi + r8]
cmp al, dl
jne .se_ne
test al, al
jz .se_eq
inc r8d
jmp .se_loop
.se_eq:
mov eax, 1
ret
.se_ne:
xor eax, eax
ret
str_prefix_match:
xor r8d, r8d
.sp_loop:
cmp r8d, edx
jge .sp_match
movzx eax, byte [rdi + r8]
cmp al, byte [rsi + r8]
jne .sp_nomatch
inc r8d
jmp .sp_loop
.sp_match:
mov eax, 1
ret
.sp_nomatch:
xor eax, eax
ret