;name:        extxterm.asm
;build:       nasm -felf64 extxterm.asm -o extxterm.o
;             ld -s -melf_x86_64 -o extxterm extxterm.o
;description: redirect stderr to a separate terminal, xterm in this example.
;             (idea derived from Evan's Debugger)

bits 64

[list -]
    %include "unistd.inc"
    %include "sys/stat.inc"
    %include "errors.inc"
[list +]

    %define SCRIPT      'tty > /tmp/xterm_fifo;trap \"\" INT QUIT TSTP;exec<&-;exec>&-;while :;do sleep 3600;done'
    %define COMMAND     '/usr/bin/xterm'
    %define FIFOBUFFER  '/tmp/xterm_fifo'
section .bss
    stderrchildpid:    resq    1
    stdoutchildpid:    resq    1
    fdfifo:         resq    1
    buffer:         resb    1024
    .len:           equ     $-buffer
    nbytesread:     resq    1
    fdterm:         resq    1
    oldstdout:      resq    1
    oldstderr:      resq    1
section .rodata
    ;error messages    
    mknoderror:     db      "mknod error",10
    .len:           equ     $-mknoderror
    forkerror:      db      "fork error",10
    .len:           equ     $-forkerror
    execveerror:    db      "execve error(not expected)",10
    .len:           equ     $-execveerror
    wait4error:     db      "wait4 error",10
    .len:           equ     $-wait4error
    fifobuffer:     db      FIFOBUFFER,0
    command:        db      COMMAND,0
    ;arguments to pass to xterm
    .1:             db      "/usr/bin/xterm",0
    .2:             db      "-fa",0
                    ;to chose another font:
    .3:             db      "DejaVu Sans Mono",0
    .4:             db      "-fs",0
    .5:             db      "11",0
    .6:             db      "-hold",0
    .7:             db      "-title",0
    .8:             db      DISPLAYNAME,0
    .9:             db      "-e",0
    .10:            db      "sh",0
    .11:            db      "-c",0
    .12:            db      SCRIPT,0
    argp:           dq      arg.1,arg.2,arg.3,arg.4,arg.5,arg.6,arg.7,arg.8,arg.9,arg.10,arg.11,arg.12
                    dq      0
    ;environment parameters to pass to xterm
    .1:             db      "DISPLAY=:0",0
    .2:             db      "PATH=/bin:/usr/bin",0
    envp:           dq      env.1,env.2
                    dq      0
    stderrmsg:      db      "stderr: message received",0x0A
    .len:           equ     $-stderrmsg
    stdoutmsg:      db      "stdout: message received",0x0A
    .len:           equ     $-stdoutmsg
section .text

global _start
    ;create a FIFO file to communicate with the terminal to get the tty of it.
    ;once we got it, we can delete it and communication continues over the
    ;tty device.
    syscall mknod , fifobuffer , S_IFIFO | S_IRUSR | S_IWUSR , 0    
    and     rax,rax                                     ;if mknod doesn't return zero there is a problem
    jz     .startfork                                   ;all ok here
    cmp     rax,EEXIST                                  ;if fifo already exists on disk so we can continue
    jne     .mknoderror                                 ;otherwise there is something else wrong
    ;fork to get a child process, this will terminate when the parent terminates.
    syscall fork
    mov     [stderrchildpid],rax                              ;save pid from child
    and     rax,rax
    js      .forkerror
    jnz     .parent
    ;we are in the child process.  The child starts xterm.
    syscall execve,command,argp,envp
    and     rax,rax
    js      .execveerror
    jmp     .exit                                       ;if success we will not get it to here

    ;we are in the parent process
    ;open a connection to the fifo to read from it
    syscall open,fifobuffer, 666o ,O_RDONLY
    and     rax,rax
    js      .openerror                                  ;if negative we got an error
    mov     [fdfifo],rax                                ;save filedescriptor to fifo
    ;read the message the shell script in xterm has send to us.  This should contain the
    ;pseude terminal device of xterm
    ;wait until child is done
    syscall read,[fdfifo],buffer,buffer.len
    and     rax,rax
    js      .readerror                                  ;if return value is negative we have an error
    jz      .commerror                                  ;if return value is zero we have a communication error
    mov     [nbytesread],rax                            ;save length of returned string
    ;close the fifo
    syscall close,[fdfifo]
    and     rax,rax                                     ;if negative there was an error
    js      .closeerror
    ;delete the FIFO file, we don't need it any more(?)
    syscall unlink,fifobuffer                           ;if return value negative then error
    js      .unlinkerror
    ;the problem we got is that this string contains an end of line character in it
    ;we have to replace this with a zero
    mov     rax,buffer                                  ;get address of buffer
    add     rax,[nbytesread]                            ;go to last character+1
    dec     rax                                         ;go to location of 0x0A
    mov     byte[rax],0                                 ;overwrite with 0x00
    ;the buffer contains the path to xterm
    ;at this point we can check, when debugging that we can send messages to the terminal with
    ;for example: echo "message" > /dev/pts/6. Just replace /dev/pts/6 with the string in the buffer.
    ;open a connection to the new path, we just want to write
    syscall open, buffer, 0666o ,O_WRONLY               ;we just want to write
    js      .openerror                                  ;if negative we got an error
    mov     [fdterm],rax                                ;save filedescriptor to terminal
    syscall close,stderr
    syscall dup,[fdterm]
    syscall close,[fdterm]
    syscall write,stderr,stderrmsg,stderrmsg.len
    syscall write,stdout,stdoutmsg,stdoutmsg.len
    ;wait for the kid to end
    syscall wait4,0,0,0,0
    and     rax,rax
    js     .waiterror
    ;exit program
    syscall exit,0

;this section can be used to deal with errors
    ;jmp     .errexit
    ;jmp     .errexit
    ;jmp     .errexit
    ;jmp     .errexit
    ;jmp     .errexit
    ;jmp     .errexit
    ;jmp     .errexit   
    ;jmp     .errexit    
    ;jmp     .errexit    
    ;jmp     .errexit

    syscall exit,1