fcoreutils 0.22.0

High-performance GNU coreutils replacement with SIMD and parallelism
Documentation
; ftty.asm — print the file name of the terminal connected to standard input
; GNU-compatible implementation of tty(1)

%include "include/linux.inc"
%include "include/macros.inc"

extern asm_write
extern asm_write_err
extern asm_exit
extern asm_strlen
extern asm_strcmp

global _start

; ============================================================
; Data section — all GNU-identical strings
; ============================================================
section .data

str_help:
    db "Usage: tty [OPTION]...", 10
    db "Print the file name of the terminal connected to standard input.", 10
    db 10
    db "  -s, --silent, --quiet   print nothing, only return an exit status", 10
    db "      --help        display this help and exit", 10
    db "      --version     output version information and exit", 10
    db 10
    db "GNU coreutils online help: <https://www.gnu.org/software/coreutils/>", 10
    db "Full documentation <https://www.gnu.org/software/coreutils/tty>", 10
    db "or available locally via: info '(coreutils) tty invocation'", 10
str_help_len equ $ - str_help

str_version:
    db "tty (GNU coreutils) 9.7", 10
    db "Packaged by Debian (9.7-3)", 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
    db 10
    db "Written by David MacKenzie.", 10
str_version_len equ $ - str_version

str_not_a_tty:
    db "not a tty", 10
str_not_a_tty_len equ $ - str_not_a_tty

str_newline:
    db 10
str_newline_len equ 1

; Error message fragments
str_err_prefix:
    db "tty: "
str_err_prefix_len equ $ - str_err_prefix

str_err_invalid_opt:
    db "invalid option -- '"
str_err_invalid_opt_len equ $ - str_err_invalid_opt

str_err_unrecognized:
    db "unrecognized option '"
str_err_unrecognized_len equ $ - str_err_unrecognized

str_err_extra_operand:
    db "extra operand ", 0xE2, 0x80, 0x98
str_err_extra_operand_len equ $ - str_err_extra_operand

str_err_quote_nl:
    db "'", 10
str_err_quote_nl_len equ $ - str_err_quote_nl

str_err_uquote_nl:
    db 0xE2, 0x80, 0x99, 10
str_err_uquote_nl_len equ $ - str_err_uquote_nl

str_try_help:
    db "Try 'tty --help' for more information.", 10
str_try_help_len equ $ - str_try_help

str_opt_help:
    db "--help", 0
str_opt_version:
    db "--version", 0
str_opt_silent:
    db "--silent", 0
str_opt_quiet:
    db "--quiet", 0
str_opt_s:
    db "-s", 0

str_proc_fd0:
    db "/proc/self/fd/0", 0

; ============================================================
; BSS section — fixed-size buffers
; ============================================================
section .bss
    termios_buf:    resb 64         ; for ioctl TCGETS
    ttyname_buf:    resb PATH_MAX   ; for readlink result

; ============================================================
; Text section
; ============================================================
section .text

_start:
    ; Get argc and argv from stack
    mov     r14, [rsp]          ; argc
    lea     r15, [rsp + 8]      ; argv

    ; r12 = silent flag (0 = not silent)
    xor     r12, r12

    ; Parse arguments: loop from argv[1] to argv[argc-1]
    cmp     r14, 1
    jle     .run_main           ; no arguments, go directly to main

    mov     r13, 1              ; i = 1 (current arg index)

.arg_loop:
    cmp     r13, r14
    jge     .run_main           ; done parsing all args

    mov     rsi, [r15 + r13*8]  ; argv[i]

    ; Check if arg starts with '-'
    cmp     byte [rsi], '-'
    jne     .err_extra_operand

    ; Check if it's just "-" (not really an option)
    cmp     byte [rsi + 1], 0
    je      .err_extra_operand

    ; Check if it starts with '--'
    cmp     byte [rsi + 1], '-'
    je      .check_long_opt

    ; Short option(s): parse each character after '-'
    jmp     .parse_short_opts

.check_long_opt:
    ; rsi points to the arg starting with '--'
    ; Check if it's exactly "--" (end of options marker)
    cmp     byte [rsi + 2], 0
    je      .end_of_opts

    ; Save arg pointer in callee-saved rbp for use across calls
    mov     rbp, rsi

    ; Check --help
    mov     rdi, rsi
    mov     rsi, str_opt_help
    call    asm_strcmp
    test    rax, rax
    jz      .do_help

    ; Check --version
    mov     rdi, rbp
    mov     rsi, str_opt_version
    call    asm_strcmp
    test    rax, rax
    jz      .do_version

    ; Check --silent
    mov     rdi, rbp
    mov     rsi, str_opt_silent
    call    asm_strcmp
    test    rax, rax
    jz      .set_silent

    ; Check --quiet
    mov     rdi, rbp
    mov     rsi, str_opt_quiet
    call    asm_strcmp
    test    rax, rax
    jz      .set_silent

    ; Unrecognized long option
    mov     rsi, rbp
    jmp     .err_unrecognized_opt

.parse_short_opts:
    ; rsi points to the arg string, start parsing from rsi+1
    mov     rbx, rsi
    add     rbx, 1             ; skip the '-'

.short_opt_loop:
    movzx   eax, byte [rbx]
    test    al, al
    jz      .next_arg          ; end of this short option string

    cmp     al, 's'
    je      .short_s

    ; Invalid short option character
    ; Save the char in r8b for error message
    mov     r8b, al
    jmp     .err_invalid_short_opt

.short_s:
    mov     r12, 1              ; set silent flag
    inc     rbx
    jmp     .short_opt_loop

.set_silent:
    mov     r12, 1
    jmp     .next_arg

.next_arg:
    inc     r13
    jmp     .arg_loop

; Handle "--" end-of-options: remaining args are operands
.end_of_opts:
    inc     r13
    cmp     r13, r14
    jge     .run_main           ; no more args after --, proceed normally
    ; First arg after -- is an extra operand
    mov     rsi, [r15 + r13*8]
    jmp     .err_extra_operand

; ── Help output ──────────────────────────────────────────────
.do_help:
    mov     rdi, STDOUT
    mov     rsi, str_help
    mov     rdx, str_help_len
    call    asm_write
    xor     rdi, rdi
    call    asm_exit

; ── Version output ───────────────────────────────────────────
.do_version:
    mov     rdi, STDOUT
    mov     rsi, str_version
    mov     rdx, str_version_len
    call    asm_write
    xor     rdi, rdi
    call    asm_exit

; ── Main logic: check if stdin is a tty ──────────────────────
.run_main:
    ; ioctl(STDIN, TCGETS, termios_buf) to check isatty
    mov     rax, SYS_IOCTL
    mov     rdi, STDIN
    mov     rsi, TCGETS
    lea     rdx, [rel termios_buf]
    syscall

    ; If rax == 0, stdin is a tty
    test    rax, rax
    jnz     .not_a_tty

    ; stdin IS a tty — get tty name via readlink("/proc/self/fd/0")
    cmp     r12, 1
    je      .exit_success       ; silent mode, just exit 0

    mov     rax, SYS_READLINK
    lea     rdi, [rel str_proc_fd0]
    lea     rsi, [rel ttyname_buf]
    mov     rdx, PATH_MAX - 1
    syscall

    ; rax = length of readlink result, or negative errno
    test    rax, rax
    jle     .not_a_tty          ; readlink failed, treat as not a tty

    ; Null-terminate the result (readlink doesn't)
    lea     rdi, [rel ttyname_buf]
    mov     byte [rdi + rax], 10  ; append newline
    inc     rax                   ; include newline in length

    ; Write the tty name
    mov     rdx, rax
    mov     rsi, rdi
    mov     rdi, STDOUT
    call    asm_write

.exit_success:
    xor     rdi, rdi            ; exit code 0
    call    asm_exit

.not_a_tty:
    ; Not a tty
    cmp     r12, 1
    je      .exit_not_tty       ; silent mode, just exit 1

    mov     rdi, STDOUT
    mov     rsi, str_not_a_tty
    mov     rdx, str_not_a_tty_len
    call    asm_write

.exit_not_tty:
    mov     rdi, 1              ; exit code 1
    call    asm_exit

; ── Error handlers ───────────────────────────────────────────

; Error: invalid short option (char in r8b)
.err_invalid_short_opt:
    ; "tty: invalid option -- 'X'\n"
    mov     rsi, str_err_prefix
    mov     rdx, str_err_prefix_len
    call    asm_write_err

    mov     rsi, str_err_invalid_opt
    mov     rdx, str_err_invalid_opt_len
    call    asm_write_err

    ; Write the single invalid character
    mov     byte [rel ttyname_buf], r8b
    mov     byte [rel ttyname_buf + 1], 0x27  ; single quote
    mov     byte [rel ttyname_buf + 2], 10    ; newline
    lea     rsi, [rel ttyname_buf]
    mov     rdx, 3
    call    asm_write_err

    ; "Try 'tty --help' for more information.\n"
    mov     rsi, str_try_help
    mov     rdx, str_try_help_len
    call    asm_write_err

    mov     rdi, 2
    call    asm_exit

; Error: unrecognized long option (rsi points to the arg)
.err_unrecognized_opt:
    ; Save the arg pointer
    mov     rbx, rsi

    ; "tty: "
    mov     rsi, str_err_prefix
    mov     rdx, str_err_prefix_len
    call    asm_write_err

    ; "unrecognized option '"
    mov     rsi, str_err_unrecognized
    mov     rdx, str_err_unrecognized_len
    call    asm_write_err

    ; the option string itself
    mov     rdi, rbx
    call    asm_strlen
    mov     rdx, rax
    mov     rsi, rbx
    call    asm_write_err

    ; "'\n"
    mov     rsi, str_err_quote_nl
    mov     rdx, str_err_quote_nl_len
    call    asm_write_err

    ; "Try 'tty --help' for more information.\n"
    mov     rsi, str_try_help
    mov     rdx, str_try_help_len
    call    asm_write_err

    mov     rdi, 2
    call    asm_exit

; Error: extra operand (rsi points to the arg)
.err_extra_operand:
    ; Save the arg pointer
    mov     rbx, rsi

    ; "tty: "
    mov     rsi, str_err_prefix
    mov     rdx, str_err_prefix_len
    call    asm_write_err

    ; "extra operand " + Unicode open quote
    mov     rsi, str_err_extra_operand
    mov     rdx, str_err_extra_operand_len
    call    asm_write_err

    ; the operand string itself
    mov     rdi, rbx
    call    asm_strlen
    mov     rdx, rax
    mov     rsi, rbx
    call    asm_write_err

    ; Unicode closing quote + newline
    mov     rsi, str_err_uquote_nl
    mov     rdx, str_err_uquote_nl_len
    call    asm_write_err

    ; "Try 'tty --help' for more information.\n"
    mov     rsi, str_try_help
    mov     rdx, str_try_help_len
    call    asm_write_err

    mov     rdi, 2
    call    asm_exit

; Mark stack as non-executable
section .note.GNU-stack noalloc noexec nowrite progbits