Skip to content

Instantly share code, notes, and snippets.

@bboe
Created June 8, 2026 16:02
Show Gist options
  • Select an option

  • Save bboe/ffc4cf02b1f69f3a48556a1180946481 to your computer and use it in GitHub Desktop.

Select an option

Save bboe/ffc4cf02b1f69f3a48556a1180946481 to your computer and use it in GitHub Desktop.
NASM jmp_match self-shrink bug — real-world reproducer (linked to PR netwide-assembler/nasm#239)

NASM jmp_match self-shrink bug — real-world reproducer

Real-world trigger for the forward-jcc/jmp self-shrink miss in NASM's asm/assemble.c:jmp_match(). Fix submitted as netwide-assembler/nasm#239.

What's here

File Purpose
shell.asm NASM source — output of bboeos's cc.py on user/programs/shell.c at commit 2a11a229 (2026-05-17). 2925 lines.
shell.c C source that shell.asm was generated from, at the same 2a11a229 commit (1064 lines). Included for context; not needed to reproduce.
constants.asm Required include for shell.asm. Stripped from bboeos kernel/include/constants.asm (383 lines, 237 symbols) down to just the 34 symbols shell.asm references transitively.
check.py Verifier that scans an assembled binary for the bug signature (forward near jcc/jmp with rel32 = 127).

Reproduce

nasm -Ox -f bin -o shell.bin shell.asm    # constants.asm picked up from cwd
python3 check.py shell.bin

On stock NASM 2.16.01 (Ubuntu 24.04 ships this):

BUG REPRODUCED: 1 jcc/jmp(s) that NASM left near but should be short:
  offset 0x199b: forward near jcc (cc=4, disp=127), should be short

Output is 7493 bytes (buggy). A self-shrink-aware assembler emits 7489 bytes (4-byte saving from a single forward je that fits rel8 once shrinking is accounted for).

Bug summary

The borderline rel8 fit check in jmp_match() compares against the current-layout displacement (target - offset - short_size) instead of the post-shrink displacement (target - offset - near_size). For a forward jump that a previous pass had emitted as NEAR, shrinking to SHORT moves the target near_size - short_size bytes closer in the current layout, so the real post-shrink rel8 is that many bytes smaller than what the existing code tests. Net effect: a valid shrink to SHORT is silently rejected, leaving the encoding 4 bytes larger than it has to be (3 bytes for jmp).

Present since jmp_match was introduced in NASM 0.98.03 (2002).

Provenance

Surfaced by the bboeos CI job #25983865211 (2026-05-17 06:47 UTC), which compares NASM's output against the project's self-hosted assembler byte-for-byte. The self-hosted assembler picked the short form; NASM kept it as near; the test flagged the 4-byte mismatch.

#!/usr/bin/env python3
"""Detect the NASM jcc-shrink miss in an assembled binary.
A forward jcc near (0F 8x XX XX XX XX) with rel32 = 127 indicates
that NASM kept the instruction as 6-byte near, but the post-self-shrink
short rel8 would be exactly 127 (just fits). Self-hosted assemblers
that account for self-shrink would emit the 2-byte short form, saving
4 bytes per occurrence.
"""
import sys, struct
if len(sys.argv) != 2:
print("usage: check_repro.py <assembled.bin>")
sys.exit(1)
data = open(sys.argv[1], 'rb').read()
hits = []
i = 0
while i < len(data) - 5:
if data[i] == 0x0f and 0x80 <= data[i+1] <= 0x8f:
disp = struct.unpack('<i', data[i+2:i+6])[0]
if disp == 127:
cc = data[i+1] - 0x80
hits.append(f" offset 0x{i:x}: forward near jcc (cc={cc}, disp=127), should be short")
i += 6
continue
if data[i] == 0xe9:
disp = struct.unpack('<i', data[i+1:i+5])[0]
if disp == 127:
hits.append(f" offset 0x{i:x}: forward near jmp (disp=127), should be short")
i += 5
continue
i += 1
if hits:
print(f"BUG REPRODUCED: {len(hits)} jcc/jmp(s) that NASM left near but should be short:")
for h in hits:
print(h)
sys.exit(0)
else:
print("BUG NOT PRESENT: NASM correctly shrunk all eligible forward jumps")
sys.exit(1)
; constants.asm - stripped for the NASM jmp_match self-shrink reproducer.
; Contains ONLY the symbols shell.asm references (transitively).
; Original at https://github.com/bboe/bboeos/blob/2a11a229/kernel/include/constants.asm (383 lines, 237 symbols).
; This stripped version: 34 symbols.
%assign ERROR_FAULT 03h ; Bad user pointer: out of user range, wraps, or filename has no NUL within MAX_PATH
%assign ERROR_NOT_EXECUTE 07h ; Exec error: file exists but is not executable
%assign FUNCTION_TABLE 00010000h ; libbboeos shared code page; kernel copies libbboeos.bin here at boot
%assign FUNCTION_EXIT FUNCTION_TABLE + 5 ; Exit program (reload shell)
%assign FUNCTION_GET_CHARACTER FUNCTION_TABLE + 10 ; Read one byte from stdin; returns AL
%assign FUNCTION_PRINT_CHARACTER FUNCTION_TABLE + 20 ; AL=char: print to stdout
%assign FUNCTION_PRINT_STRING FUNCTION_TABLE + 50 ; DI=null-terminated string: write to stdout
%assign FUNCTION_PRINTF FUNCTION_TABLE + 55 ; cdecl: push args R-to-L, push fmt, call
%assign MAX_INPUT 256
%assign MAX_ARGV_ENTRIES 64 ; per-side argv pointer-slot cap (excluding the NULL terminator); kernel rejects with ERROR_INVALID above this.
%assign MAX_PATH 64 ; Hard cap on user-supplied filename byte count (incl. NUL); enough for "<24-char dir>/<24-char file>" plus headroom
%assign O_CREAT 10h
%assign O_RDONLY 00h
%assign O_TRUNC 20h
%assign O_WRONLY 01h
%assign STDOUT 1
%assign SYS_IO_CLOSE 10h ; BX=fd; CF on error
%assign SYS_IO_DUP 11h ; BX=old_fd; returns AX=new_fd, CF on error
%assign SYS_IO_DUP2 12h ; BX=old_fd, DX=target_fd; returns AX=target, CF on error
%assign SYS_IO_IOCTL 15h ; BX=fd, AL=cmd, other regs per (fd_type,cmd); CF on error
%assign SYS_IO_OPEN 16h ; SI=filename, AL=flags, DL=mode; returns AX=fd, CF on error
%assign SYS_IO_SEEK 18h ; BX=fd, ECX=offset, AL=whence (0/1/2); returns EAX=new position, CF on error
%assign SYS_IO_WRITE 19h ; BX=fd, SI=buffer, CX=count; returns AX=bytes written, CF on error
%assign SEEK_END 2
%assign SYS_RTC_SLEEP 33h ; ECX=milliseconds: hlt-park; PIT IRQ 0 wakes to re-check the deadline
%assign SYS_SYS_EXEC 0F1h
%assign SYS_SYS_PIPELINE2 0F3h ; SI=left_path, DI=right_path, DX=left_argv (char**, 0=none), CX=right_argv (char**, 0=none); returns AX=wait_status (cmd2's), CF on error
%assign SYS_SYS_REBOOT 0F4h
%assign SYS_SYS_SHUTDOWN 0F5h
%assign SYS_SYS_SIGNAL 0F6h ; EBX = signum (SIGINT, SIGPIPE, or SIGALRM); ECX = handler (SIG_DFL/SIG_IGN/user-virt); EAX = previous handler; CF on bad signum / handler
%assign SIGINT 2
%assign SIG_IGN 1
%assign VGA_IOCTL_MODE 01h ; DL=mode; also clears screen and serial
%assign VIDEO_MODE_TEXT_80x25 03h ; 80x25 color text (default)
[bits 32]
org 08048000h
%include "constants.asm"
%define HISTORY_SIZE 16
%define MAX_REDIRECTS 3
%define MAX_SEGMENTS 32
%define OP_AND 2
%define OP_END 0
%define OP_OR 3
%define OP_SEMI 1
%define REDIRECT_OP_APPND 0
%define REDIRECT_OP_IN 1
%define REDIRECT_OP_NONE 2
%define REDIRECT_OP_OUT 3
%define WAIT_H
main:
push edx
mov ebx, SIGINT
mov ecx, SIG_IGN
mov ah, SYS_SYS_SIGNAL
int 30h
pop edx
mov ebx, STDOUT
mov esi, _str_0
mov ecx, 14
mov ah, SYS_IO_WRITE
int 30h
mov eax, _g_input_buf
mov [_l_buf], eax
push edx
mov esi, _str_1
mov al, O_WRONLY
mov ah, SYS_IO_OPEN
int 30h
pop edx
mov [_l_vga_fd], eax
xor eax, eax
mov [_l_kill_len], eax
.while_0:
mov ebx, STDOUT
mov esi, _str_2
mov ecx, 2
mov ah, SYS_IO_WRITE
int 30h
xor ebp, ebp
xor edx, edx
.while_1:
call FUNCTION_GET_CHARACTER
movzx eax, al
cmp al, 1
jne .if_2_else
test ebp, ebp
jle .if_3_end
push edx
push ebp
call cursor_back
add esp, 4
pop edx
xor ebp, ebp
.if_3_end:
jmp .if_2_end
.if_2_else:
cmp al, 2
jne .if_4_else
test ebp, ebp
jle .if_5_end
push edx
push 1
call cursor_back
add esp, 4
pop edx
dec ebp
.if_5_end:
jmp .if_4_end
.if_4_else:
cmp al, 3
jne .if_6_else
mov al, 10
call FUNCTION_PRINT_CHARACTER
xor edx, edx
xor eax, eax
mov [_g_history_view], eax
jmp .while_1_end
.if_6_else:
cmp al, 4
jne .if_7_else
mov ah, SYS_SYS_SHUTDOWN
int 30h
jmp .if_7_end
.if_7_else:
cmp al, 5
jne .if_8_else
mov ebx, STDOUT
mov esi, [_l_buf]
add esi, ebp
mov eax, edx
sub eax, ebp
mov ecx, eax
mov ah, SYS_IO_WRITE
int 30h
mov eax, edx
mov ebp, eax
jmp .if_8_end
.if_8_else:
cmp al, 6
jne .if_9_else
cmp ebp, edx
jge .if_10_end
mov esi, [_l_buf]
add esi, ebp
movzx eax, byte [esi]
call FUNCTION_PRINT_CHARACTER
inc ebp
.if_10_end:
jmp .if_9_end
.if_9_else:
cmp al, 8
je .lor_12
cmp al, 127
jne .if_11_else
.lor_12:
test ebp, ebp
jle .if_13_end
push edx
push 1
call cursor_back
add esp, 4
pop edx
dec ebp
push edx
mov edi, [_l_buf]
mov ebx, ebp
mov ecx, edx
call delete_at_cursor
pop edx
mov edx, eax
.if_13_end:
jmp .if_11_end
.if_11_else:
cmp al, 11
jne .if_14_else
cmp ebp, edx
jge .if_15_end
mov eax, edx
sub eax, ebp
mov [_l_span], eax
cmp eax, MAX_INPUT
jle .if_16_end
mov eax, MAX_INPUT
mov [_l_span], eax
.if_16_end:
mov eax, [_l_span]
mov [_l_kill_len], eax
xor eax, eax
mov [_l_copy_index], eax
.while_17:
mov eax, [_l_copy_index]
cmp eax, [_l_span]
jge .while_17_end
mov esi, [_l_buf]
push esi
mov eax, ebp
add eax, [_l_copy_index]
pop esi
add esi, eax
movzx eax, byte [esi]
mov esi, [_l_copy_index]
mov [_g_kill_buf+esi], al
inc dword [_l_copy_index]
jmp .while_17
.while_17_end:
xor eax, eax
mov [_l_erase_index], eax
.while_18:
mov eax, [_l_erase_index]
cmp eax, [_l_span]
jge .while_18_end
mov al, 32
call FUNCTION_PRINT_CHARACTER
inc dword [_l_erase_index]
jmp .while_18
.while_18_end:
push edx
mov eax, [_l_span]
push eax
call cursor_back
add esp, 4
pop edx
mov eax, ebp
mov edx, eax
.if_15_end:
jmp .if_14_end
.if_14_else:
cmp al, 12
jne .if_19_else
push edx
mov eax, VIDEO_MODE_TEXT_80x25
mov dl, al
mov ebx, [_l_vga_fd]
mov al, VGA_IOCTL_MODE
mov ah, SYS_IO_IOCTL
int 30h
pop edx
xor edx, edx
xor eax, eax
mov [_g_history_view], eax
jmp .while_1_end
.if_19_else:
cmp al, 14
jne .if_20_else
push edx
push edx
push ebp
mov eax, [_l_buf]
push eax
call history_down
add esp, 12
pop edx
mov edx, eax
mov ebp, eax
jmp .if_20_end
.if_20_else:
cmp al, 16
jne .if_21_else
push edx
push edx
push ebp
mov eax, [_l_buf]
push eax
call history_up
add esp, 12
pop edx
mov edx, eax
mov ebp, eax
jmp .if_21_end
.if_21_else:
cmp al, 10
jne .if_22_else
mov al, 10
call FUNCTION_PRINT_CHARACTER
jmp .while_1_end
.if_22_else:
cmp al, 25
jne .if_23_else
xor eax, eax
mov [_l_yank_index], eax
.while_24:
mov eax, [_l_yank_index]
cmp eax, [_l_kill_len]
jge .if_23_end
cmp edx, MAX_INPUT
jl .if_25_end
push edx
call visual_bell
pop edx
jmp .if_23_end
.if_25_end:
push edx
mov esi, [_l_yank_index]
movzx eax, byte [_g_kill_buf+esi]
push eax
push edx
push ebp
mov eax, [_l_buf]
push eax
call insert_char
add esp, 16
pop edx
mov edx, eax
inc ebp
inc dword [_l_yank_index]
jmp .while_24
.if_23_else:
cmp al, 27
jne .if_26_else
call FUNCTION_GET_CHARACTER
movzx eax, al
cmp al, 91
jne .if_27_end
call FUNCTION_GET_CHARACTER
movzx eax, al
mov [_l_final_byte], al
.while_28:
cmp byte [_l_final_byte], 48
jl .while_28_end
cmp byte [_l_final_byte], 63
jg .while_28_end
call FUNCTION_GET_CHARACTER
movzx eax, al
mov [_l_final_byte], al
jmp .while_28
.while_28_end:
movzx eax, byte [_l_final_byte]
cmp al, 65
jne .if_29_else
push edx
push edx
push ebp
mov eax, [_l_buf]
push eax
call history_up
add esp, 12
pop edx
mov edx, eax
mov ebp, eax
jmp .if_29_end
.if_29_else:
cmp al, 66
jne .if_30_end
push edx
push edx
push ebp
mov eax, [_l_buf]
push eax
call history_down
add esp, 12
pop edx
mov edx, eax
mov ebp, eax
.if_30_end:
.if_29_end:
.if_27_end:
jmp .if_26_end
.if_26_else:
cmp al, 32
jl .if_31_end
cmp edx, MAX_INPUT
jl .if_32_else
push edx
call visual_bell
pop edx
jmp .if_32_end
.if_32_else:
push edx
push eax
push edx
push ebp
mov eax, [_l_buf]
push eax
call insert_char
add esp, 16
pop edx
mov edx, eax
inc ebp
.if_32_end:
.if_31_end:
.if_26_end:
.if_23_end:
.if_22_end:
.if_21_end:
.if_20_end:
.if_19_end:
.if_14_end:
.if_11_end:
.if_9_end:
.if_8_end:
.if_7_end:
.if_6_end:
.if_4_end:
.if_2_end:
jmp .while_1
.while_1_end:
xor eax, eax
push eax
mov esi, [_l_buf]
mov eax, edx
add esi, eax
pop eax
mov [esi], al
test edx, edx
jle .if_33_end
mov eax, [_g_history_count]
dec eax
mov ecx, 16
push edx
xor edx, edx
div ecx
mov eax, edx
pop edx
mov [_l_previous_slot], eax
xor eax, eax
mov [_l_is_duplicate], eax
cmp dword [_g_history_count], 0
jle .if_34_end
push edx
mov eax, _g_history
push eax
mov eax, [_l_previous_slot]
push eax
mov ecx, MAX_INPUT
pop eax
push edx
mul ecx
pop edx
mov ecx, eax
pop eax
add eax, ecx
push eax
mov eax, [_l_buf]
push eax
call strcmp
add esp, 8
pop edx
test eax, eax
jne .if_34_end
mov eax, 1
mov [_l_is_duplicate], eax
.if_34_end:
cmp dword [_l_is_duplicate], 0
jne .if_35_end
mov eax, [_g_history_count]
mov ecx, 16
push edx
xor edx, edx
div ecx
mov eax, edx
pop edx
mov [_l_slot], eax
mov eax, _g_history
push eax
mov eax, [_l_slot]
push eax
mov ecx, MAX_INPUT
pop eax
push edx
mul ecx
pop edx
mov ecx, eax
pop eax
add eax, ecx
mov [_l_entry], eax
mov edi, eax
mov esi, [_l_buf]
mov ecx, edx
cld
rep movsb
xor eax, eax
push eax
mov esi, [_l_entry]
mov eax, edx
add esi, eax
pop eax
mov [esi], al
inc dword [_g_history_count]
.if_35_end:
.if_33_end:
xor eax, eax
mov [_g_history_view], eax
test edx, edx
je .while_0
mov edi, _g_chain_buf
mov esi, [_l_buf]
mov ecx, edx
inc ecx
cld
rep movsb
push edx
mov ebx, _g_chain_buf
call parse_chain
pop edx
mov [_l_n_segments], eax
test eax, eax
jge .if_37_end
push edx
mov edi, _str_3
call FUNCTION_PRINT_STRING
pop edx
jmp .while_0
.if_37_end:
xor eax, eax
mov [_l_seg_index], eax
.while_38:
mov eax, [_l_seg_index]
cmp eax, [_l_n_segments]
jge .while_0
mov eax, 1
mov [_l_run], eax
cmp dword [_l_seg_index], 0
jle .if_39_end
mov esi, [_l_seg_index]
movzx eax, byte [_g_segment_ops-1+esi]
cmp eax, 2
jne .if_40_else
mov eax, [_g_last_exec_status]
mov ecx, 0
cmp eax, ecx
mov eax, 0
jne .bool_41
inc eax
.bool_41:
mov [_l_run], eax
jmp .if_40_end
.if_40_else:
cmp eax, 3
jne .if_42_end
mov eax, [_g_last_exec_status]
mov ecx, 0
cmp eax, ecx
mov eax, 0
je .bool_43
inc eax
.bool_43:
mov [_l_run], eax
.if_42_end:
.if_40_end:
.if_39_end:
mov eax, _g_chain_buf
push eax
mov esi, [_l_seg_index]
shl esi, 2
mov eax, [_g_segment_offsets+esi]
mov ecx, eax
pop eax
add eax, ecx
mov [_l_segment], eax
cmp dword [_l_run], 0
je .if_44_end
mov esi, [_l_segment]
cmp byte [esi], 0
je .if_44_end
mov edi, eax
xor al, al
mov ecx, 0FFFFh
cld
repne scasb
mov eax, 0FFFEh
sub eax, ecx
mov edi, [_l_buf]
inc eax
mov ecx, eax
rep movsb
push edx
mov eax, [_l_buf]
push eax
call find_top_level_pipe
add esp, 4
pop edx
mov [_l_pipe_at], eax
cmp eax, -2
jne .if_45_else
mov ebx, STDOUT
mov esi, _str_4
mov ecx, 36
mov ah, SYS_IO_WRITE
int 30h
mov eax, 256
mov [_g_last_exec_status], eax
jmp .if_45_end
.if_45_else:
test eax, eax
jl .if_46_else
xor eax, eax
mov [_l_pi], eax
.while_47:
mov eax, [_l_pi]
cmp eax, [_l_pipe_at]
jge .while_47_end
mov esi, [_l_buf]
mov eax, [_l_pi]
add esi, eax
movzx eax, byte [esi]
mov esi, [_l_pi]
mov [_g_pipe_left_buf+esi], al
inc dword [_l_pi]
jmp .while_47
.while_47_end:
.while_48:
cmp dword [_l_pi], 0
jle .while_48_end
mov esi, [_l_pi]
movzx eax, byte [_g_pipe_left_buf-1+esi]
cmp eax, 32
jne .while_48_end
dec dword [_l_pi]
jmp .while_48
.while_48_end:
xor eax, eax
mov esi, [_l_pi]
mov [_g_pipe_left_buf+esi], al
mov eax, [_l_pipe_at]
inc eax
mov [_l_pi], eax
.while_49:
mov esi, [_l_buf]
mov eax, [_l_pi]
add esi, eax
movzx eax, byte [esi]
cmp eax, 32
jne .while_49_end
inc dword [_l_pi]
jmp .while_49
.while_49_end:
xor eax, eax
mov [_l_rj], eax
.while_50:
mov esi, [_l_buf]
mov eax, [_l_pi]
add esi, eax
movzx eax, byte [esi]
test eax, eax
je .while_50_end
mov esi, [_l_buf]
mov eax, [_l_pi]
add esi, eax
movzx eax, byte [esi]
mov esi, [_l_rj]
mov [_g_pipe_right_buf+esi], al
inc dword [_l_pi]
inc dword [_l_rj]
jmp .while_50
.while_50_end:
xor eax, eax
mov esi, [_l_rj]
mov [_g_pipe_right_buf+esi], al
push edx
mov edi, _g_pipe_left_buf
call contains_redirect_token
pop edx
test eax, eax
jne .lor_52
push edx
mov edi, _g_pipe_right_buf
call contains_redirect_token
pop edx
test eax, eax
je .if_51_else
.lor_52:
mov ebx, STDOUT
mov esi, _str_5
mov ecx, 40
mov ah, SYS_IO_WRITE
int 30h
mov eax, 256
mov [_g_last_exec_status], eax
jmp .if_51_end
.if_51_else:
push edx
push _g_pipe_left_buf
mov ebx, _g_pipe_left_name
mov edi, _g_pipe_left_argv
call tokenize_pipeline_side
add esp, 4
pop edx
mov [_l_la], eax
push edx
push _g_pipe_right_buf
mov ebx, _g_pipe_right_name
mov edi, _g_pipe_right_argv
call tokenize_pipeline_side
add esp, 4
pop edx
cmp dword [_l_la], 0
jl .lor_54
test eax, eax
jge .if_53_else
.lor_54:
mov ebx, STDOUT
mov esi, _str_6
mov ecx, 35
mov ah, SYS_IO_WRITE
int 30h
mov eax, 256
mov [_g_last_exec_status], eax
jmp .if_53_end
.if_53_else:
mov byte [_g_pipe_left_path], 98
mov byte [_g_pipe_left_path+1], 105
mov byte [_g_pipe_left_path+2], 110
mov byte [_g_pipe_left_path+3], 47
xor eax, eax
mov [_l_ci], eax
.while_55:
mov esi, [_l_ci]
movzx eax, byte [_g_pipe_left_name+esi]
test eax, eax
je .while_55_end
mov eax, [_l_ci]
push eax
mov ecx, (MAX_PATH-5)
pop eax
cmp eax, ecx
jge .while_55_end
movzx eax, byte [_g_pipe_left_name+esi]
push eax
mov esi, 4
add esi, [_l_ci]
pop eax
mov [_g_pipe_left_path+esi], al
inc dword [_l_ci]
jmp .while_55
.while_55_end:
xor eax, eax
push eax
mov esi, 4
add esi, [_l_ci]
pop eax
mov [_g_pipe_left_path+esi], al
mov byte [_g_pipe_right_path], 98
mov byte [_g_pipe_right_path+1], 105
mov byte [_g_pipe_right_path+2], 110
mov byte [_g_pipe_right_path+3], 47
xor eax, eax
mov [_l_ci], eax
.while_56:
mov esi, [_l_ci]
movzx eax, byte [_g_pipe_right_name+esi]
test eax, eax
je .while_56_end
mov eax, [_l_ci]
push eax
mov ecx, (MAX_PATH-5)
pop eax
cmp eax, ecx
jge .while_56_end
movzx eax, byte [_g_pipe_right_name+esi]
push eax
mov esi, 4
add esi, [_l_ci]
pop eax
mov [_g_pipe_right_path+esi], al
inc dword [_l_ci]
jmp .while_56
.while_56_end:
xor eax, eax
push eax
mov esi, 4
add esi, [_l_ci]
pop eax
mov [_g_pipe_right_path+esi], al
push edx
mov esi, _g_pipe_left_path
mov edi, _g_pipe_right_path
mov edx, _g_pipe_left_argv
mov ecx, _g_pipe_right_argv
mov ah, SYS_SYS_PIPELINE2
int 30h
jc .pipeline2_failed_57
movzx eax, ax
jmp .pipeline2_done_57
.pipeline2_failed_57:
movzx eax, al
neg eax
.pipeline2_done_57:
pop edx
mov [_l_rc], eax
test eax, eax
jge .if_58_else
mov ebx, STDOUT
mov esi, _str_7
mov ecx, 23
mov ah, SYS_IO_WRITE
int 30h
xor eax, eax
sub eax, [_l_rc]
mov [_g_last_exec_status], eax
jmp .if_58_end
.if_58_else:
mov [_g_last_exec_status], eax
.if_58_end:
.if_53_end:
.if_51_end:
jmp .if_46_end
.if_46_else:
push edx
mov eax, [_l_buf]
push eax
call parse_redirections
add esp, 4
pop edx
test eax, eax
jge .if_59_else
push edx
mov edi, _str_8
call FUNCTION_PRINT_STRING
pop edx
mov eax, 256
mov [_g_last_exec_status], eax
jmp .if_59_end
.if_59_else:
push edx
call apply_redirections
pop edx
test eax, eax
jne .if_60_end
push edx
mov eax, [_l_buf]
push eax
call dispatch_buffer
add esp, 4
pop edx
push edx
call restore_redirections
pop edx
.if_60_end:
.if_59_end:
.if_46_end:
.if_45_end:
.if_44_end:
inc dword [_l_seg_index]
jmp .while_38
.while_0_end:
xor eax, eax
jmp FUNCTION_EXIT
_l_buf: dd 0
_l_ch: db 0
_l_ci: dd 0
_l_copy_index: dd 0
_l_entry: dd 0
_l_erase_index: dd 0
_l_escape_next: db 0
_l_final_byte: db 0
_l_is_duplicate: dd 0
_l_kill_len: dd 0
_l_la: dd 0
_l_n_segments: dd 0
_l_pi: dd 0
_l_pipe_at: dd 0
_l_prev_op: dd 0
_l_previous_slot: dd 0
_l_ra: dd 0
_l_rc: dd 0
_l_rj: dd 0
_l_run: dd 0
_l_seg_index: dd 0
_l_seg_len: dd 0
_l_segment: dd 0
_l_slot: dd 0
_l_span: dd 0
_l_vga_fd: dd 0
_l_yank_index: dd 0
contains_redirect_token:
push ebp
mov ebp, esp
sub esp, 32
xor edx, edx
xor ecx, ecx
xor ebx, ebx
._ir_wloop0:
mov esi, edi
add esi, edx
movzx eax, byte [esi]
mov [ebp-4], eax
test eax, eax
je ._ir_wend1
mov esi, edi
add esi, edx
movzx eax, byte [esi]
mov [ebp-8], eax
cmp eax, 39
jne ._ir_else3
test ebx, ebx
jne ._ir_else3
mov eax, 1
sub eax, ecx
mov [ebp-12], eax
mov ecx, eax
jmp ._ir_endif4
._ir_else3:
mov esi, edi
add esi, edx
movzx eax, byte [esi]
mov [ebp-16], eax
cmp eax, 34
jne ._ir_else7
test ecx, ecx
jne ._ir_else7
mov eax, 1
sub eax, ebx
mov [ebp-20], eax
mov ebx, eax
jmp ._ir_endif8
._ir_else7:
test ecx, ecx
jne ._ir_endif11
test ebx, ebx
jne ._ir_endif11
mov esi, edi
add esi, edx
movzx eax, byte [esi]
mov [ebp-24], eax
cmp eax, 60
je ._ir_lor13
mov esi, edi
add esi, edx
movzx eax, byte [esi]
mov [ebp-28], eax
cmp eax, 62
jne ._ir_endif12
._ir_lor13:
mov eax, 1
mov esp, ebp
pop ebp
ret
._ir_endif12:
._ir_endif11:
._ir_endif8:
._ir_endif4:
inc edx
jmp ._ir_wloop0
._ir_wend1:
xor eax, eax
mov esp, ebp
pop ebp
ret
apply_redirections:
push ebp
mov ebp, esp
sub esp, 40
mov dword [_g_saved_fds], -1
mov dword [_g_saved_fds+4], -1
xor edx, edx
._ir_wloop17:
cmp edx, [_g_redirect_count]
jge ._ir_wend18
mov esi, edx
movzx eax, byte [_g_redirect_targets+esi]
mov ecx, eax
movzx eax, byte [_g_redirect_ops+esi]
mov [ebp-4], eax
push ecx
mov eax, _g_redirect_names
push eax
push ecx
mov eax, edx
push eax
mov ecx, MAX_PATH
pop eax
push edx
mul ecx
pop edx
pop ecx
mov ecx, eax
pop eax
add eax, ecx
pop ecx
mov [ebp-8], eax
mov esi, ecx
shl esi, 2
mov eax, [_g_saved_fds+esi]
mov [ebp-20], eax
test eax, eax
jge ._ir_endif19
push ebx
mov ebx, ecx
mov ah, SYS_IO_DUP
int 30h
pop ebx
mov [ebp-12], eax
test eax, eax
jge ._ir_endif21
pusha
mov edi, _ir_s0
call FUNCTION_PRINT_STRING
popa
mov eax, 256
mov [_g_last_exec_status], eax
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif21:
mov eax, [ebp-12]
mov esi, ecx
shl esi, 2
mov [_g_saved_fds+esi], eax
._ir_endif19:
cmp dword [ebp-4], 1
jne ._ir_else22
mov ebx, O_RDONLY
jmp ._ir_endif23
._ir_else22:
cmp dword [ebp-4], 3
jne ._ir_else24
mov eax, (O_WRONLY|O_CREAT)
mov [ebp-24], eax
push ecx
push eax
mov ecx, O_TRUNC
pop eax
or eax, ecx
pop ecx
mov [ebp-28], eax
mov ebx, eax
jmp ._ir_endif25
._ir_else24:
mov ebx, (O_WRONLY|O_CREAT)
._ir_endif25:
._ir_endif23:
push edx
mov eax, [ebp-8]
mov esi, eax
mov eax, ebx
mov ah, SYS_IO_OPEN
int 30h
pop edx
mov [ebp-16], eax
test eax, eax
jge ._ir_endif29
pusha
mov eax, [ebp-8]
push eax
push _ir_s1
call FUNCTION_PRINTF
add esp, 8
popa
mov eax, 256
mov [_g_last_exec_status], eax
pusha
call restore_redirections
popa
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif29:
cmp dword [ebp-4], 0
jne ._ir_endif30
push ebx
push ecx
mov ebx, [ebp-16]
mov ecx, 0
mov al, SEEK_END
mov ah, SYS_IO_SEEK
int 30h
pop ecx
pop ebx
._ir_endif30:
push ebx
push edx
mov ebx, [ebp-16]
mov edx, ecx
mov ah, SYS_IO_DUP2
int 30h
pop edx
pop ebx
test eax, eax
jge ._ir_endif31
push ebx
mov ebx, [ebp-16]
mov ah, SYS_IO_CLOSE
int 30h
pop ebx
pusha
mov edi, _ir_s2
call FUNCTION_PRINT_STRING
popa
mov eax, 256
mov [_g_last_exec_status], eax
pusha
call restore_redirections
popa
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif31:
push ebx
mov ebx, [ebp-16]
mov ah, SYS_IO_CLOSE
int 30h
pop ebx
mov eax, edx
inc eax
mov [ebp-40], eax
mov edx, eax
jmp ._ir_wloop17
._ir_wend18:
xor eax, eax
mov esp, ebp
pop ebp
ret
cursor_back:
push ebp
mov ebp, esp
mov edx, [ebp+8]
test edx, edx
jle ._ir_endif34
push edx
mov eax, edx
push eax
push _ir_s3
call FUNCTION_PRINTF
add esp, 8
pop edx
._ir_endif34:
xor eax, eax
pop ebp
ret
delete_at_cursor:
push ebp
mov ebp, esp
sub esp, 36
mov eax, ebx
mov edx, eax
._ir_wloop35:
mov eax, ecx
dec eax
mov [ebp-4], eax
cmp edx, [ebp-4]
jge ._ir_wend36
mov eax, edx
inc eax
mov [ebp-8], eax
mov esi, edi
mov eax, [ebp-8]
add esi, eax
movzx eax, byte [esi]
mov [ebp-12], eax
push eax
mov esi, edi
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, edx
inc eax
mov [ebp-16], eax
mov edx, eax
jmp ._ir_wloop35
._ir_wend36:
mov eax, ecx
dec eax
mov [ebp-20], eax
mov ecx, eax
mov eax, edi
add eax, ebx
mov [ebp-24], eax
mov eax, ecx
sub eax, ebx
mov [ebp-28], eax
push ebx
push ecx
mov ebx, STDOUT
mov esi, [ebp-24]
mov ecx, eax
mov ah, SYS_IO_WRITE
int 30h
pop ecx
pop ebx
mov al, 32
call FUNCTION_PRINT_CHARACTER
mov eax, ecx
sub eax, ebx
inc eax
pusha
push eax
call cursor_back
add esp, 4
popa
mov eax, ecx
mov esp, ebp
pop ebp
ret
dispatch_buffer:
push ebp
mov ebp, esp
sub esp, 112
xor edx, edx
._ir_wloop46:
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-28], eax
test eax, eax
je ._ir_wend47
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 32
je ._ir_wend47
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov esi, edx
mov [_g_dispatch_name+esi], al
mov eax, edx
inc eax
mov [ebp-40], eax
mov edx, eax
jmp ._ir_wloop46
._ir_wend47:
xor eax, eax
mov esi, edx
mov [_g_dispatch_name+esi], al
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 32
jne ._ir_endif52
mov eax, [ebp+8]
add eax, edx
mov [ebp-48], eax
inc eax
mov [ebp-52], eax
mov eax, edx
inc eax
mov [ebp-56], eax
mov eax, MAX_INPUT
sub eax, [ebp-56]
push edx
push eax
mov eax, [ebp-52]
push eax
call expand_dollar_question
add esp, 8
pop edx
test eax, eax
jge ._ir_endif54
push edx
mov edi, _ir_s4
call FUNCTION_PRINT_STRING
pop edx
mov eax, 256
mov [_g_last_exec_status], eax
mov esp, ebp
pop ebp
ret
._ir_endif54:
._ir_endif52:
push edx
push _g_dispatch_argv
mov eax, [ebp+8]
push eax
call tokenize_argv
add esp, 8
pop edx
mov [ebp-4], eax
test eax, eax
jge ._ir_endif60
push edx
mov edi, _ir_s5
call FUNCTION_PRINT_STRING
pop edx
mov eax, 256
mov [_g_last_exec_status], eax
mov esp, ebp
pop ebp
ret
._ir_endif60:
cmp dword [ebp-4], 0
jne ._ir_endif61
mov esp, ebp
pop ebp
ret
._ir_endif61:
mov eax, [_g_dispatch_argv]
mov [ebp-8], eax
xor eax, eax
mov [ebp-12], eax
._ir_wloop62:
mov esi, [ebp-8]
mov eax, [ebp-12]
add esi, eax
movzx eax, byte [esi]
test eax, eax
je ._ir_wend63
mov esi, [ebp-8]
mov eax, [ebp-12]
add esi, eax
movzx eax, byte [esi]
mov [ebp-72], eax
mov esi, [ebp-12]
mov [_g_dispatch_name+esi], al
mov eax, [ebp-12]
inc eax
mov [ebp-12], eax
jmp ._ir_wloop62
._ir_wend63:
xor eax, eax
mov esi, [ebp-12]
mov [_g_dispatch_name+esi], al
push edx
push _ir_s6
push _g_dispatch_name
call strcmp
add esp, 8
pop edx
mov [ebp-80], eax
test eax, eax
jne ._ir_else67
push edx
mov edi, _ir_s7
call FUNCTION_PRINT_STRING
pop edx
xor eax, eax
mov [_g_last_exec_status], eax
jmp ._ir_endif68
._ir_else67:
push edx
push _ir_s8
push _g_dispatch_name
call strcmp
add esp, 8
pop edx
test eax, eax
jne ._ir_else70
mov ah, SYS_SYS_REBOOT
int 30h
jmp ._ir_endif71
._ir_else70:
push edx
push _ir_s9
push _g_dispatch_name
call strcmp
add esp, 8
pop edx
test eax, eax
jne ._ir_else73
mov ah, SYS_SYS_SHUTDOWN
int 30h
push edx
mov edi, _ir_s10
call FUNCTION_PRINT_STRING
pop edx
mov eax, 256
mov [_g_last_exec_status], eax
jmp ._ir_endif74
._ir_else73:
push edx
mov edx, _g_dispatch_name
mov ecx, _g_dispatch_argv
call try_exec
pop edx
mov [ebp-16], eax
cmp eax, 1
jne ._ir_else76
mov eax, 32256
mov [_g_last_exec_status], eax
push edx
mov edi, _ir_s11
call FUNCTION_PRINT_STRING
pop edx
jmp ._ir_endif77
._ir_else76:
cmp dword [ebp-16], 0
jne ._ir_endif78
mov byte [_g_dispatch_bin], 98
mov byte [_g_dispatch_bin+1], 105
mov byte [_g_dispatch_bin+2], 110
mov byte [_g_dispatch_bin+3], 47
xor eax, eax
mov [ebp-20], eax
._ir_wloop79:
mov esi, [ebp-20]
movzx eax, byte [_g_dispatch_name+esi]
test eax, eax
je ._ir_wend80
mov eax, (MAX_PATH-5)
mov [ebp-96], eax
mov eax, [ebp-20]
cmp eax, [ebp-96]
jge ._ir_wend80
mov eax, 4
add eax, [ebp-20]
mov [ebp-100], eax
movzx eax, byte [_g_dispatch_name+esi]
mov esi, [ebp-100]
mov [_g_dispatch_bin+esi], al
mov eax, [ebp-20]
inc eax
mov [ebp-20], eax
jmp ._ir_wloop79
._ir_wend80:
mov eax, 4
add eax, [ebp-20]
mov [ebp-112], eax
xor eax, eax
mov esi, [ebp-112]
mov [_g_dispatch_bin+esi], al
push edx
mov edx, _g_dispatch_bin
mov ecx, _g_dispatch_argv
call try_exec
pop edx
mov [ebp-24], eax
cmp eax, 1
jne ._ir_else87
mov eax, 32256
mov [_g_last_exec_status], eax
push edx
mov edi, _ir_s11
call FUNCTION_PRINT_STRING
pop edx
jmp ._ir_endif88
._ir_else87:
cmp dword [ebp-24], 0
jne ._ir_endif89
mov eax, 32512
mov [_g_last_exec_status], eax
push edx
mov edi, _ir_s13
call FUNCTION_PRINT_STRING
pop edx
._ir_endif89:
._ir_endif88:
._ir_endif78:
._ir_endif77:
._ir_endif74:
._ir_endif71:
._ir_endif68:
mov esp, ebp
pop ebp
ret
expand_dollar_question:
push ebp
mov ebp, esp
sub esp, 169
mov eax, [_g_last_exec_status]
and eax, 127
test eax, eax
jne ._ir_else90
mov eax, [_g_last_exec_status]
shr eax, 8
mov [ebp-41], eax
and eax, 255
mov [ebp-4], eax
jmp ._ir_endif91
._ir_else90:
mov eax, [_g_last_exec_status]
and eax, 127
mov [ebp-49], eax
mov eax, 128
add eax, [ebp-49]
mov [ebp-4], eax
._ir_endif91:
xor edi, edi
mov eax, [ebp-4]
mov [ebp-12], eax
test eax, eax
jne ._ir_else97
lea esi, [ebp-8]
mov byte [esi], 48
mov edi, 1
jmp ._ir_endif98
._ir_else97:
._ir_wloop99:
cmp dword [ebp-12], 0
jle ._ir_wend100
mov eax, [ebp-12]
mov ecx, 10
push edx
xor edx, edx
div ecx
mov eax, edx
pop edx
mov [ebp-57], eax
mov eax, 48
add eax, [ebp-57]
push eax
lea esi, [ebp-8]
mov eax, edi
add esi, eax
pop eax
mov [esi], al
inc edi
mov eax, [ebp-12]
mov ecx, 10
push edx
xor edx, edx
div ecx
pop edx
mov [ebp-12], eax
jmp ._ir_wloop99
._ir_wend100:
._ir_endif98:
xor eax, eax
mov [ebp-16], eax
mov eax, edi
dec eax
mov [ebp-20], eax
._ir_wloop105:
mov eax, [ebp-16]
cmp eax, [ebp-20]
jge ._ir_wend106
lea esi, [ebp-8]
mov eax, [ebp-16]
add esi, eax
movzx eax, byte [esi]
mov [ebp-21], al
lea esi, [ebp-8]
mov eax, [ebp-20]
add esi, eax
movzx eax, byte [esi]
push eax
lea esi, [ebp-8]
mov eax, [ebp-16]
add esi, eax
pop eax
mov [esi], al
movzx eax, byte [ebp-21]
push eax
lea esi, [ebp-8]
mov eax, [ebp-20]
add esi, eax
pop eax
mov [esi], al
mov eax, [ebp-16]
inc eax
mov [ebp-16], eax
mov eax, [ebp-20]
dec eax
mov [ebp-20], eax
jmp ._ir_wloop105
._ir_wend106:
xor edx, edx
push edi
mov edi, [ebp+8]
xor al, al
mov ecx, 0FFFFh
cld
repne scasb
mov eax, 0FFFEh
sub eax, ecx
pop edi
mov [ebp-25], eax
._ir_wloop110:
mov eax, [ebp-25]
dec eax
mov [ebp-85], eax
cmp edx, [ebp-85]
jge ._ir_wend111
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 36
jne ._ir_else113
mov eax, edx
inc eax
mov [ebp-93], eax
mov esi, [ebp+8]
mov eax, [ebp-93]
add esi, eax
movzx eax, byte [esi]
cmp eax, 63
jne ._ir_else113
mov eax, edi
sub eax, 2
mov [ebp-29], eax
mov eax, [ebp-25]
add eax, [ebp-29]
cmp eax, [ebp+12]
jl ._ir_endif118
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif118:
cmp dword [ebp-29], 0
jle ._ir_else120
mov eax, [ebp-25]
mov ebx, eax
._ir_wloop122:
mov eax, edx
add eax, 2
mov [ebp-105], eax
cmp ebx, [ebp-105]
jle ._ir_wend123
mov eax, ebx
add eax, [ebp-29]
mov [ebp-109], eax
mov esi, [ebp+8]
add esi, ebx
movzx eax, byte [esi]
mov esi, [ebp+8]
add esi, [ebp-109]
mov [esi], al
dec ebx
jmp ._ir_wloop122
._ir_wend123:
mov eax, edx
add eax, 2
add eax, [ebp-29]
mov [ebp-125], eax
mov eax, edx
add eax, 2
mov [ebp-129], eax
mov esi, [ebp+8]
mov eax, [ebp-129]
add esi, eax
movzx eax, byte [esi]
mov esi, [ebp+8]
add esi, [ebp-125]
mov [esi], al
jmp ._ir_endif121
._ir_else120:
cmp dword [ebp-29], 0
jge ._ir_endif132
mov ebx, edx
add ebx, 2
._ir_wloop133:
cmp ebx, [ebp-25]
jg ._ir_wend134
mov eax, ebx
add eax, [ebp-29]
mov [ebp-137], eax
mov esi, [ebp+8]
add esi, ebx
movzx eax, byte [esi]
mov esi, [ebp+8]
add esi, [ebp-137]
mov [esi], al
inc ebx
jmp ._ir_wloop133
._ir_wend134:
._ir_endif132:
._ir_endif121:
xor eax, eax
mov [ebp-33], eax
._ir_wloop138:
mov eax, [ebp-33]
cmp eax, edi
jge ._ir_wend139
mov eax, edx
add eax, [ebp-33]
mov [ebp-149], eax
lea esi, [ebp-8]
mov eax, [ebp-33]
add esi, eax
movzx eax, byte [esi]
mov esi, [ebp+8]
add esi, [ebp-149]
mov [esi], al
mov eax, [ebp-33]
inc eax
mov [ebp-33], eax
jmp ._ir_wloop138
._ir_wend139:
mov eax, [ebp-25]
add eax, [ebp-29]
mov [ebp-25], eax
add edx, edi
jmp ._ir_endif114
._ir_else113:
inc edx
._ir_endif114:
jmp ._ir_wloop110
._ir_wend111:
xor eax, eax
mov esi, [ebp+8]
add esi, [ebp-25]
mov [esi], al
mov eax, [ebp-25]
mov esp, ebp
pop ebp
ret
find_top_level_pipe:
push ebp
mov ebp, esp
sub esp, 40
xor edx, edx
xor ecx, ecx
xor ebx, ebx
mov edi, -1
._ir_wloop146:
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-4], eax
test eax, eax
je ._ir_wend147
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-8], eax
cmp eax, 39
jne ._ir_else149
test ebx, ebx
jne ._ir_else149
mov eax, 1
sub eax, ecx
mov [ebp-12], eax
mov ecx, eax
jmp ._ir_endif150
._ir_else149:
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-16], eax
cmp eax, 34
jne ._ir_else153
test ecx, ecx
jne ._ir_else153
mov eax, 1
sub eax, ebx
mov [ebp-20], eax
mov ebx, eax
jmp ._ir_endif154
._ir_else153:
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-24], eax
cmp eax, 124
jne ._ir_endif157
test ecx, ecx
jne ._ir_endif157
test ebx, ebx
jne ._ir_endif157
mov eax, edx
inc eax
mov [ebp-28], eax
mov esi, [ebp+8]
mov eax, [ebp-28]
add esi, eax
movzx eax, byte [esi]
cmp eax, 124
jne ._ir_endif159
add edx, 2
jmp ._ir_wloop146
._ir_endif159:
test edi, edi
jge ._ir_else163
mov eax, edx
mov edi, eax
jmp ._ir_endif164
._ir_else163:
mov eax, -2
mov esp, ebp
pop ebp
ret
._ir_endif164:
._ir_endif157:
._ir_endif154:
._ir_endif150:
mov eax, edx
inc eax
mov [ebp-40], eax
mov edx, eax
jmp ._ir_wloop146
._ir_wend147:
mov eax, edi
mov esp, ebp
pop ebp
ret
history_down:
push ebp
mov ebp, esp
sub esp, 24
cmp dword [_g_history_view], 0
jne ._ir_endif166
mov eax, [ebp+16]
mov esp, ebp
pop ebp
ret
._ir_endif166:
mov eax, [_g_history_view]
dec eax
mov [ebp-12], eax
mov [_g_history_view], eax
test eax, eax
jne ._ir_endif168
mov eax, [_g_saved_line_length]
push eax
push _g_saved_line
mov eax, [ebp+16]
push eax
mov eax, [ebp+12]
push eax
mov eax, [ebp+8]
push eax
call replace_line
add esp, 20
mov [ebp-16], eax
mov esp, ebp
pop ebp
ret
._ir_endif168:
mov eax, [_g_history_count]
sub eax, [_g_history_view]
mov ecx, 16
xor edx, edx
div ecx
mov [ebp-4],edx
mov eax, _g_history
push eax
mov eax, [ebp-4]
push eax
mov ecx, MAX_INPUT
pop eax
mul ecx
mov ecx, eax
pop eax
add eax, ecx
mov [ebp-8], eax
mov edi, eax
xor al, al
mov ecx, 0FFFFh
cld
repne scasb
mov eax, 0FFFEh
sub eax, ecx
mov [ebp-20], eax
push eax
mov eax, [ebp-8]
push eax
mov eax, [ebp+16]
push eax
mov eax, [ebp+12]
push eax
mov eax, [ebp+8]
push eax
call replace_line
add esp, 20
mov [ebp-24], eax
mov esp, ebp
pop ebp
ret
history_up:
push ebp
mov ebp, esp
sub esp, 20
mov eax, [_g_history_count]
mov edx, eax
cmp edx, 16
jle ._ir_endif172
mov edx, 16
._ir_endif172:
mov eax, [_g_history_view]
cmp eax, edx
jl ._ir_endif173
push edx
call visual_bell
pop edx
mov eax, [ebp+16]
mov esp, ebp
pop ebp
ret
._ir_endif173:
cmp dword [_g_history_view], 0
jne ._ir_endif174
mov edi, _g_saved_line
mov esi, [ebp+8]
mov ecx, [ebp+16]
cld
rep movsb
mov eax, [ebp+16]
mov [_g_saved_line_length], eax
._ir_endif174:
mov eax, [_g_history_view]
inc eax
mov [ebp-12], eax
mov [_g_history_view], eax
mov eax, [_g_history_count]
sub eax, [_g_history_view]
mov ecx, 16
push edx
xor edx, edx
div ecx
mov eax, edx
pop edx
mov [ebp-4], eax
mov eax, _g_history
push eax
mov eax, [ebp-4]
push eax
mov ecx, MAX_INPUT
pop eax
push edx
mul ecx
pop edx
mov ecx, eax
pop eax
add eax, ecx
mov [ebp-8], eax
mov edi, eax
xor al, al
mov ecx, 0FFFFh
cld
repne scasb
mov eax, 0FFFEh
sub eax, ecx
mov [ebp-16], eax
push edx
push eax
mov eax, [ebp-8]
push eax
mov eax, [ebp+16]
push eax
mov eax, [ebp+12]
push eax
mov eax, [ebp+8]
push eax
call replace_line
add esp, 20
pop edx
mov [ebp-20], eax
mov esp, ebp
pop ebp
ret
insert_char:
push ebp
mov ebp, esp
sub esp, 32
mov edi, [ebp+8]
mov ebx, [ebp+12]
mov ecx, [ebp+16]
mov eax, ecx
mov edx, eax
._ir_wloop178:
cmp edx, ebx
jle ._ir_wend179
dec eax
mov [ebp-4], eax
mov esi, edi
mov eax, [ebp-4]
add esi, eax
movzx eax, byte [esi]
mov [ebp-8], eax
push eax
mov esi, edi
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, edx
dec eax
mov [ebp-12], eax
mov edx, eax
jmp ._ir_wloop178
._ir_wend179:
mov eax, [ebp+20]
push eax
mov esi, edi
mov eax, ebx
add esi, eax
pop eax
mov [esi], al
mov eax, ecx
inc eax
mov [ebp-16], eax
mov ecx, eax
mov eax, edi
add eax, ebx
mov [ebp-20], eax
mov eax, ecx
sub eax, ebx
mov [ebp-24], eax
push ebx
push ecx
mov ebx, STDOUT
mov esi, [ebp-20]
mov ecx, eax
mov ah, SYS_IO_WRITE
int 30h
pop ecx
pop ebx
mov eax, ecx
sub eax, ebx
mov [ebp-28], eax
dec eax
pusha
push eax
call cursor_back
add esp, 4
popa
mov eax, ecx
mov esp, ebp
pop ebp
ret
parse_chain:
push ebp
mov ebp, esp
sub esp, 104
xor ecx, ecx
xor edx, edx
push ecx
mov edi, ebx
xor al, al
mov ecx, 0FFFFh
cld
repne scasb
mov eax, 0FFFEh
sub eax, ecx
pop ecx
mov [ebp-4], eax
._ir_wloop188:
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
mov [ebp-8], eax
cmp eax, 32
jne ._ir_wend189
mov eax, edx
inc eax
mov [ebp-12], eax
mov edx, eax
jmp ._ir_wloop188
._ir_wend189:
mov eax, edx
mov esi, ecx
shl esi, 2
mov [_g_segment_offsets+esi], eax
._ir_wloop192:
cmp edx, [ebp-4]
jge ._ir_wend193
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
mov [ebp-16], eax
cmp eax, 59
jne ._ir_else194
xor eax, eax
push eax
mov esi, ebx
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, 1
mov esi, ecx
mov [_g_segment_ops+esi], al
mov eax, ecx
inc eax
mov [ebp-20], eax
mov ecx, eax
cmp ecx, 32
jl ._ir_endif198
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif198:
mov eax, edx
inc eax
mov [ebp-24], eax
mov edx, eax
._ir_wloop200:
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
mov [ebp-28], eax
cmp eax, 32
jne ._ir_wend201
inc edx
jmp ._ir_wloop200
._ir_wend201:
mov eax, edx
mov esi, ecx
shl esi, 2
mov [_g_segment_offsets+esi], eax
jmp ._ir_endif195
._ir_else194:
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
cmp eax, 38
jne ._ir_else204
mov eax, edx
inc eax
mov [ebp-40], eax
mov esi, ebx
mov eax, [ebp-40]
add esi, eax
movzx eax, byte [esi]
cmp eax, 38
jne ._ir_else204
xor eax, eax
push eax
mov esi, ebx
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, edx
inc eax
mov [ebp-48], eax
xor eax, eax
push eax
mov esi, ebx
mov eax, [ebp-48]
add esi, eax
pop eax
mov [esi], al
mov eax, 2
mov esi, ecx
mov [_g_segment_ops+esi], al
mov eax, ecx
inc eax
mov [ebp-52], eax
mov ecx, eax
cmp ecx, 32
jl ._ir_endif211
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif211:
mov eax, edx
add eax, 2
mov [ebp-56], eax
mov edx, eax
._ir_wloop213:
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
cmp eax, 32
jne ._ir_wend214
inc edx
jmp ._ir_wloop213
._ir_wend214:
mov eax, edx
mov esi, ecx
shl esi, 2
mov [_g_segment_offsets+esi], eax
jmp ._ir_endif205
._ir_else204:
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
cmp eax, 124
jne ._ir_else217
mov eax, edx
inc eax
mov [ebp-72], eax
mov esi, ebx
mov eax, [ebp-72]
add esi, eax
movzx eax, byte [esi]
cmp eax, 124
jne ._ir_else217
xor eax, eax
push eax
mov esi, ebx
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, edx
inc eax
mov [ebp-80], eax
xor eax, eax
push eax
mov esi, ebx
mov eax, [ebp-80]
add esi, eax
pop eax
mov [esi], al
mov eax, 3
mov esi, ecx
mov [_g_segment_ops+esi], al
inc ecx
cmp ecx, 32
jl ._ir_endif224
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif224:
add edx, 2
._ir_wloop226:
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
cmp eax, 32
jne ._ir_wend227
mov eax, edx
inc eax
mov [ebp-96], eax
mov edx, eax
jmp ._ir_wloop226
._ir_wend227:
mov eax, edx
mov esi, ecx
shl esi, 2
mov [_g_segment_offsets+esi], eax
jmp ._ir_endif218
._ir_else217:
mov eax, edx
inc eax
mov [ebp-100], eax
mov edx, eax
._ir_endif218:
._ir_endif205:
._ir_endif195:
jmp ._ir_wloop192
._ir_wend193:
xor eax, eax
mov esi, ecx
mov [_g_segment_ops+esi], al
mov eax, ecx
inc eax
mov esp, ebp
pop ebp
ret
parse_redirections:
push ebp
mov ebp, esp
sub esp, 101
xor eax, eax
mov [_g_redirect_count], eax
xor ecx, ecx
push ecx
push edi
mov edi, [ebp+8]
xor al, al
mov ecx, 0FFFFh
cld
repne scasb
mov eax, 0FFFEh
sub eax, ecx
pop edi
pop ecx
mov [ebp-4], eax
._ir_wloop232:
cmp ecx, [ebp-4]
jge ._ir_wend233
mov esi, [ebp+8]
add esi, ecx
movzx eax, byte [esi]
mov [ebp-5], al
mov edi, 2
xor eax, eax
mov [ebp-9], eax
xor eax, eax
mov [ebp-13], eax
cmp byte [ebp-5], 62
jne ._ir_else234
mov eax, ecx
inc eax
cmp eax, [ebp-4]
jge ._ir_else234
mov eax, ecx
inc eax
mov [ebp-41], eax
mov esi, [ebp+8]
mov eax, [ebp-41]
add esi, eax
movzx eax, byte [esi]
cmp eax, 62
jne ._ir_else234
xor edi, edi
mov eax, 2
mov [ebp-9], eax
mov eax, 1
mov [ebp-13], eax
jmp ._ir_endif235
._ir_else234:
cmp byte [ebp-5], 62
jne ._ir_else239
mov edi, 3
mov eax, 1
mov [ebp-9], eax
mov eax, 1
mov [ebp-13], eax
jmp ._ir_endif240
._ir_else239:
cmp byte [ebp-5], 60
jne ._ir_else241
mov edi, 1
mov eax, 1
mov [ebp-9], eax
xor eax, eax
mov [ebp-13], eax
jmp ._ir_endif242
._ir_else241:
mov eax, ecx
inc eax
mov [ebp-49], eax
mov ecx, eax
jmp ._ir_wloop232
._ir_endif242:
._ir_endif240:
._ir_endif235:
cmp dword [_g_redirect_count], 3
jl ._ir_endif244
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif244:
mov eax, ecx
mov [ebp-17], eax
mov edx, ecx
add edx, [ebp-9]
._ir_wloop245:
cmp edx, [ebp-4]
jge ._ir_wend246
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 32
jne ._ir_wend246
mov eax, edx
inc eax
mov [ebp-57], eax
mov edx, eax
jmp ._ir_wloop245
._ir_wend246:
cmp edx, [ebp-4]
je ._ir_lor250
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 62
je ._ir_lor250
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 60
jne ._ir_endif249
._ir_lor250:
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif249:
mov [ebp-21],edx
._ir_wloop253:
cmp edx, [ebp-4]
jge ._ir_wend254
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 32
je ._ir_wend254
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 62
je ._ir_wend254
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
cmp eax, 60
je ._ir_wend254
inc edx
jmp ._ir_wloop253
._ir_wend254:
mov eax, edx
sub eax, [ebp-21]
mov [ebp-25], eax
cmp eax, MAX_PATH
jl ._ir_endif259
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif259:
push ecx
mov eax, _g_redirect_names
push eax
push ecx
mov eax, [_g_redirect_count]
push eax
mov ecx, MAX_PATH
pop eax
push edx
mul ecx
pop edx
pop ecx
mov ecx, eax
pop eax
add eax, ecx
pop ecx
mov [ebp-29], eax
xor ebx, ebx
._ir_wloop260:
cmp ebx, [ebp-25]
jge ._ir_wend261
mov eax, [ebp-21]
add eax, ebx
mov [ebp-85], eax
mov esi, [ebp+8]
mov eax, [ebp-85]
add esi, eax
movzx eax, byte [esi]
push eax
mov esi, [ebp-29]
mov eax, ebx
add esi, eax
pop eax
mov [esi], al
mov eax, ebx
inc eax
mov [ebp-93], eax
mov ebx, eax
jmp ._ir_wloop260
._ir_wend261:
xor eax, eax
mov esi, [ebp-29]
add esi, [ebp-25]
mov [esi], al
mov eax, edi
mov esi, [_g_redirect_count]
mov [_g_redirect_ops+esi], al
mov eax, [ebp-13]
mov [_g_redirect_targets+esi], al
mov eax, [_g_redirect_count]
inc eax
mov [_g_redirect_count], eax
mov eax, [ebp-17]
mov [ebp-33], eax
._ir_wloop266:
mov eax, [ebp-33]
cmp eax, edx
jge ._ir_wend267
mov eax, 32
mov esi, [ebp+8]
add esi, [ebp-33]
mov [esi], al
mov eax, [ebp-33]
inc eax
mov [ebp-33], eax
jmp ._ir_wloop266
._ir_wend267:
mov eax, edx
mov ecx, eax
jmp ._ir_wloop232
._ir_wend233:
xor eax, eax
mov esp, ebp
pop ebp
ret
replace_line:
push ebp
mov ebp, esp
sub esp, 4
mov ecx, [ebp+24]
push ecx
push edx
mov eax, [ebp+12]
push eax
call cursor_back
add esp, 4
pop edx
pop ecx
xor edx, edx
._ir_wloop269:
cmp edx, [ebp+16]
jge ._ir_wend270
mov al, 32
call FUNCTION_PRINT_CHARACTER
mov eax, edx
inc eax
mov [ebp-4], eax
mov edx, eax
jmp ._ir_wloop269
._ir_wend270:
push ecx
push edx
mov eax, [ebp+16]
push eax
call cursor_back
add esp, 4
pop edx
pop ecx
push ecx
mov edi, [ebp+8]
mov esi, [ebp+20]
cld
rep movsb
pop ecx
test ecx, ecx
jle ._ir_endif272
push ecx
mov ebx, STDOUT
mov esi, [ebp+8]
mov ah, SYS_IO_WRITE
int 30h
pop ecx
._ir_endif272:
mov eax, ecx
mov esp, ebp
pop ebp
ret
restore_redirections:
push ebp
mov ebp, esp
sub esp, 16
xor edx, edx
._ir_wloop273:
cmp edx, 2
jge ._ir_wend274
mov esi, edx
shl esi, 2
mov eax, [_g_saved_fds+esi]
mov [ebp-4], eax
test eax, eax
jl ._ir_endif275
mov esi, edx
shl esi, 2
mov eax, [_g_saved_fds+esi]
mov [ebp-8], eax
push edx
mov ebx, eax
mov ah, SYS_IO_DUP2
int 30h
pop edx
mov esi, edx
shl esi, 2
mov eax, [_g_saved_fds+esi]
mov [ebp-12], eax
mov ebx, eax
mov ah, SYS_IO_CLOSE
int 30h
mov eax, -1
mov esi, edx
shl esi, 2
mov [_g_saved_fds+esi], eax
._ir_endif275:
mov eax, edx
inc eax
mov [ebp-16], eax
mov edx, eax
jmp ._ir_wloop273
._ir_wend274:
xor eax, eax
mov esp, ebp
pop ebp
ret
strcmp:
push ebp
mov ebp, esp
sub esp, 28
mov ecx, [ebp+8]
mov ebx, [ebp+12]
xor edx, edx
._ir_wloop280:
mov esi, ecx
add esi, edx
movzx eax, byte [esi]
mov [ebp-4], eax
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
mov [ebp-8], eax
mov eax, [ebp-4]
cmp eax, [ebp-8]
je ._ir_endif282
mov esi, ecx
add esi, edx
movzx eax, byte [esi]
mov [ebp-12], eax
mov esi, ebx
add esi, edx
movzx eax, byte [esi]
mov [ebp-16], eax
mov eax, [ebp-12]
sub eax, [ebp-16]
mov [ebp-20], eax
mov esp, ebp
pop ebp
ret
._ir_endif282:
mov esi, ecx
add esi, edx
movzx eax, byte [esi]
mov [ebp-24], eax
test eax, eax
jne ._ir_endif288
xor eax, eax
mov esp, ebp
pop ebp
ret
._ir_endif288:
mov eax, edx
inc eax
mov [ebp-28], eax
mov edx, eax
jmp ._ir_wloop280
._ir_wend281:
mov esp, ebp
pop ebp
ret
tokenize_argv:
push ebp
mov ebp, esp
sub esp, 61
xor edx, edx
xor ecx, ecx
._ir_wloop291:
._ir_wloop293:
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-13], eax
cmp eax, 32
jne ._ir_wend294
mov eax, edx
inc eax
mov [ebp-17], eax
mov edx, eax
jmp ._ir_wloop293
._ir_wend294:
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-21], eax
test eax, eax
je ._ir_wend292
cmp ecx, MAX_ARGV_ENTRIES
jl ._ir_endif299
mov eax, -1
mov esp, ebp
pop ebp
ret
._ir_endif299:
mov eax, edx
mov ebx, eax
mov eax, [ebp+8]
add eax, ebx
mov [ebp-25], eax
push eax
mov esi, [ebp+12]
mov eax, ecx
shl eax, 2
add esi, eax
pop eax
mov [esi], eax
mov eax, ecx
inc eax
mov [ebp-29], eax
mov ecx, eax
xor edi, edi
xor eax, eax
mov [ebp-4], eax
xor eax, eax
mov [ebp-8], eax
._ir_wloop302:
cmp dword [ebp-8], 0
jne ._ir_wend303
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-33], eax
test eax, eax
je ._ir_wend303
mov esi, [ebp+8]
add esi, edx
movzx eax, byte [esi]
mov [ebp-9], al
cmp al, 39
jne ._ir_else305
cmp dword [ebp-4], 0
jne ._ir_else305
mov eax, 1
sub eax, edi
mov edi, eax
mov eax, edx
inc eax
mov [ebp-41], eax
mov edx, eax
jmp ._ir_endif306
._ir_else305:
cmp byte [ebp-9], 34
jne ._ir_else309
test edi, edi
jne ._ir_else309
mov eax, 1
sub eax, [ebp-4]
mov [ebp-4], eax
mov eax, edx
inc eax
mov [ebp-49], eax
mov edx, eax
jmp ._ir_endif310
._ir_else309:
cmp byte [ebp-9], 32
jne ._ir_else313
test edi, edi
jne ._ir_else313
cmp dword [ebp-4], 0
jne ._ir_else313
inc edx
mov eax, 1
mov [ebp-8], eax
jmp ._ir_endif314
._ir_else313:
movzx eax, byte [ebp-9]
push eax
mov esi, [ebp+8]
mov eax, ebx
add esi, eax
pop eax
mov [esi], al
mov eax, ebx
inc eax
mov [ebp-57], eax
mov ebx, eax
inc edx
._ir_endif314:
._ir_endif310:
._ir_endif306:
jmp ._ir_wloop302
._ir_wend303:
xor eax, eax
push eax
mov esi, [ebp+8]
mov eax, ebx
add esi, eax
pop eax
mov [esi], al
jmp ._ir_wloop291
._ir_wend292:
xor eax, eax
push eax
mov esi, [ebp+12]
mov eax, ecx
shl eax, 2
add esi, eax
pop eax
mov [esi], eax
mov eax, ecx
mov esp, ebp
pop ebp
ret
tokenize_pipeline_side:
push ebp
mov ebp, esp
sub esp, 16
push ebx
push ecx
push edi
push edx
push edi
mov eax, [ebp+8]
push eax
call tokenize_argv
add esp, 8
pop edx
pop edi
pop ecx
pop ebx
mov [ebp-4], eax
test eax, eax
jg ._ir_endif318
mov esi, ebx
mov byte [esi], 0
mov eax, [ebp-4]
mov esp, ebp
pop ebp
ret
._ir_endif318:
mov esi, edi
mov eax, [esi]
mov ecx, eax
xor edx, edx
._ir_wloop319:
mov esi, ecx
add esi, edx
movzx eax, byte [esi]
mov [ebp-8], eax
test eax, eax
je ._ir_wend320
mov esi, ecx
add esi, edx
movzx eax, byte [esi]
mov [ebp-12], eax
push eax
mov esi, ebx
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, edx
inc eax
mov [ebp-16], eax
mov edx, eax
jmp ._ir_wloop319
._ir_wend320:
xor eax, eax
push eax
mov esi, ebx
mov eax, edx
add esi, eax
pop eax
mov [esi], al
mov eax, [ebp-4]
mov esp, ebp
pop ebp
ret
try_exec:
push ebp
mov ebp, esp
sub esp, 12
mov eax, edx
mov esi, eax
mov edx, ecx
mov ah, SYS_SYS_EXEC
int 30h
jc .exec_failed_61
movzx eax, ax
jmp .exec_done_61
.exec_failed_61:
movzx eax, al
neg eax
.exec_done_61:
mov [ebp-4], eax
test eax, eax
jl ._ir_endif324
mov [_g_last_exec_status], eax
mov eax, 2
mov esp, ebp
pop ebp
ret
._ir_endif324:
xor eax, eax
sub eax, [ebp-4]
mov [ebp-8], eax
cmp eax, ERROR_NOT_EXECUTE
jne ._ir_endif325
mov eax, 1
mov esp, ebp
pop ebp
ret
._ir_endif325:
xor eax, eax
sub eax, [ebp-4]
mov [ebp-12], eax
cmp eax, ERROR_FAULT
jne ._ir_endif327
push ecx
push edx
mov edi, _ir_s14
call FUNCTION_PRINT_STRING
pop edx
pop ecx
mov eax, 256
mov [_g_last_exec_status], eax
mov eax, 3
mov esp, ebp
pop ebp
ret
._ir_endif327:
xor eax, eax
mov esp, ebp
pop ebp
ret
visual_bell:
push ebp
mov ebp, esp
mov edi, _ir_s15
call FUNCTION_PRINT_STRING
mov ecx, 50
mov ah, SYS_RTC_SLEEP
int 30h
mov edi, _ir_s16
call FUNCTION_PRINT_STRING
xor eax, eax
pop ebp
ret
;; --- global data ---
;; --- string literals ---
_str_0: db `[shell:start]\n\0`
_str_1: db `/dev/vga\0`
_str_2: db `$ \0`
_str_3: db `too many commands in chain\n\0`
_str_4: db `shell: pipelines support only one |\n\0`
_str_5: db `shell: pipes cannot combine with < > >>\n\0`
_str_6: db `shell: too many pipeline arguments\n\0`
_str_7: db `shell: pipeline failed\n\0`
_str_8: db `redirection syntax error\n\0`
_ir_s0: db `dup failed\n\0`
_ir_s1: db `cannot open %s\n\0`
_ir_s2: db `dup2 failed\n\0`
_ir_s3: db `\e[%dD\0`
_ir_s4: db `$? expansion exceeded MAX_INPUT\n\0`
_ir_s5: db `too many arguments\n\0`
_ir_s6: db `help\0`
_ir_s7: db `Commands: help reboot shutdown\n\0`
_ir_s8: db `reboot\0`
_ir_s9: db `shutdown\0`
_ir_s10: db `APM shutdown failed\n\0`
_ir_s11: db `not executable\n\0`
_ir_s12: db `not executable\n\0`
_ir_s13: db `unknown command\n\0`
_ir_s14: db `exec: out of memory\n\0`
_ir_s15: db `\e[48;5;4m\0`
_ir_s16: db `\e[48;5;0m\0`
dd _bss_total_size
dw 0B032h
_program_end:
_bss_end equ _program_end + _bss_total_size
;; --- BSS (zero-initialized) ---
_g_history_count equ _program_end
_g_history_view equ _g_history_count + 4
_g_last_exec_status equ _g_history_view + 4
_g_redirect_count equ _g_last_exec_status + 4
_g_saved_line_length equ _g_redirect_count + 4
_g_chain_buf equ _g_saved_line_length + 4
_g_dispatch_argv equ _g_chain_buf + MAX_INPUT
_g_dispatch_bin equ _g_dispatch_argv + ((MAX_ARGV_ENTRIES+1))*4
_g_dispatch_name equ _g_dispatch_bin + MAX_PATH
_g_history equ _g_dispatch_name + MAX_INPUT
_g_input_buf equ _g_history + (16*MAX_INPUT)
_g_kill_buf equ _g_input_buf + MAX_INPUT
_g_pipe_left_argv equ _g_kill_buf + MAX_INPUT
_g_pipe_left_buf equ _g_pipe_left_argv + ((MAX_ARGV_ENTRIES+1))*4
_g_pipe_left_name equ _g_pipe_left_buf + MAX_INPUT
_g_pipe_left_path equ _g_pipe_left_name + MAX_INPUT
_g_pipe_right_argv equ _g_pipe_left_path + MAX_PATH
_g_pipe_right_buf equ _g_pipe_right_argv + ((MAX_ARGV_ENTRIES+1))*4
_g_pipe_right_name equ _g_pipe_right_buf + MAX_INPUT
_g_pipe_right_path equ _g_pipe_right_name + MAX_INPUT
_g_redirect_names equ _g_pipe_right_path + MAX_PATH
_g_redirect_ops equ _g_redirect_names + 192
_g_redirect_targets equ _g_redirect_ops + 3
_g_saved_fds equ _g_redirect_targets + 3
_g_saved_line equ _g_saved_fds + 8
_g_segment_offsets equ _g_saved_line + MAX_INPUT
_g_segment_ops equ _g_segment_offsets + 128
_bss_total_size equ _g_segment_ops + 32 - _program_end
#include "wait.h"
#define HISTORY_SIZE 16
#define MAX_SEGMENTS 32
#define OP_AND 2
#define OP_END 0
#define OP_OR 3
#define OP_SEMI 1
/* Tokenized command chain: a single line may contain multiple commands
joined by `;`, `&&`, or `||`. parse_chain() splits chain_buf in
place by replacing operator chars with NUL, fills segment_offsets[]
with byte offsets into chain_buf, and writes the operator type
*following* each segment into segment_ops[] (the last entry is
OP_END). Each segment is memcpy'd into input_buf one-at-a-time and
dispatched (tokenize_argv splits input_buf in place into dispatch_argv,
handed straight to the exec() syscall). cc.py global arrays only
support char/int/uint8_t/struct elements, hence offsets-not-pointers. */
char chain_buf[MAX_INPUT];
/* Redirection state for one dispatch_buffer call. parse_redirections
fills these; apply_redirections consumes them. Each filename is
null-terminated and lives in redirect_names; the redirect entries
point in via byte offsets (cc.py arrays don't carry pointer-element
types end-to-end). Operator kinds: IN = `<` ; OUT = `>` (truncate);
APPND = `>>` (append). */
#define MAX_REDIRECTS 3
#define REDIRECT_OP_APPND 0
#define REDIRECT_OP_IN 1
#define REDIRECT_OP_NONE 2
#define REDIRECT_OP_OUT 3
int redirect_count;
char redirect_names[192]; /* MAX_REDIRECTS * MAX_PATH = 3 * 64 */
char redirect_ops[MAX_REDIRECTS];
char redirect_targets[MAX_REDIRECTS]; /* target fd: 0 (stdin) or 1 (stdout) */
/* saved_fds[target] = saved-fd-number from dup(target) before the
redirect was applied, or -1 if no save was taken. Indexed by
target fd (0 or 1). apply_redirections fills it; restore_redirections
consumes and clears it. */
int saved_fds[2];
/* Command history ring. history is a flat array of HISTORY_SIZE slots,
each MAX_INPUT bytes. Access slot i as history + (i * MAX_INPUT).
history_count is the lifetime push count, clipped to HISTORY_SIZE
for browsing range. history_view = 0 means the live edit line;
1..min(history_count, HISTORY_SIZE) walks backward. */
char history[HISTORY_SIZE * MAX_INPUT];
int history_count;
int history_view;
/* Live input line buffer — the line editor writes characters here as
the user types; after Enter the line is copied into chain_buf for
chain parsing and then memcpy'd back into input_buf one segment at
a time for dispatch. Used to live at the static user-data frame
(USER_DATA_BASE+0x500 = 0x1500, named BUFFER) shared by every
program, but the EXEC_ARG handoff was the only cross-program use
and is gone — so this is now plain shell-private .bss. */
char input_buf[MAX_INPUT];
/* Ctrl-K kill buffer. File-scope so it lands in the program's BSS;
per-program PDs do not alias the low 1 MB so a fixed-address scratch
like SECTOR_BUFFER would page-fault. */
char kill_buf[MAX_INPUT];
/* Wait status of the most recently exec()'d child. Written by
try_exec() on a successful exec; read by the dispatch loop and
expand_dollar_question() to expose $? to the user. */
int last_exec_status;
/* Snapshot of the live edit line taken on the first Up keypress.
Restored when Down walks back past the newest history entry to
history_view == 0, matching bash's partial-line restore behaviour. */
char saved_line[MAX_INPUT];
int saved_line_length;
int segment_offsets[MAX_SEGMENTS];
char segment_ops[MAX_SEGMENTS];
/* Scratch buffers for the pipeline parser (`cmd1 | cmd2`). File-scope
so that (a) cc.py passes their addresses correctly on function calls
and (b) they live in BSS without consuming additional user stack.
pipe_left_buf / pipe_right_buf hold the trimmed command tokens;
pipe_left_name / pipe_right_name hold the bare basenames (for
building the bin/-prefixed path);
pipe_left_path / pipe_right_path hold the bin/-prefixed paths;
pipe_left_argv / pipe_right_argv are NULL-terminated char**
arrays pointing into the matching pipe_*_buf (which tokenize_argv
has split in place by replacing whitespace runs with NULs). Passed
through SYS_SYS_PIPELINE2 to the kernel, which validates each array
under the shell's PD and then, during each child's PD build, reads
the strings back through the shell's mappings and writes them into
the new program's stack frame via a kmap alias — building the
Linux SysV i386 startup frame in place with no kernel-side
scratch. */
int pipe_left_argv[MAX_ARGV_ENTRIES + 1]; /* cc.py: 32-bit ints = pointer-sized; stores char* values */
char pipe_left_buf[MAX_INPUT];
char pipe_left_name[MAX_INPUT];
char pipe_left_path[MAX_PATH];
int pipe_right_argv[MAX_ARGV_ENTRIES + 1];
char pipe_right_buf[MAX_INPUT];
char pipe_right_name[MAX_INPUT];
char pipe_right_path[MAX_PATH];
/* contains_redirect_token — return non-zero if `s` has an unquoted
`<`, `>`, or `>>`. Used to reject pipe + redirect on the same
side (v1 limitation). */
int contains_redirect_token(char *s) {
int i = 0;
int in_single = 0;
int in_double = 0;
while (s[i] != '\0') {
if (s[i] == '\'' && in_double == 0) {
in_single = 1 - in_single;
} else if (s[i] == '"' && in_single == 0) {
in_double = 1 - in_double;
} else if (in_single == 0 && in_double == 0) {
if (s[i] == '<' || s[i] == '>') {
return 1;
}
}
i += 1;
}
return 0;
}
/* Forward decl: apply_redirections calls restore_redirections on the
error-rollback path; restore_redirections sorts after apply in
source order. cc.py resolves forward refs silently; clang under
-std=c99 needs the prototype. */
int restore_redirections();
int apply_redirections() {
/* For each redirect in order: save the original target via dup,
open the file, dup2 over the target, close the temp fd.
Returns 0 on success, -1 on error (with prior saves rolled
back). Sets last_exec_status on failure. */
saved_fds[0] = -1;
saved_fds[1] = -1;
int index = 0;
while (index < redirect_count) {
int target = redirect_targets[index];
int op = redirect_ops[index];
char *path = redirect_names + (index * MAX_PATH);
/* Save target only once (later same-target redirects reuse the
same save — bash "last wins" semantics; the earlier file is
opened+truncated+closed by the later dup2). */
if (saved_fds[target] < 0) {
int saved = dup(target);
if (saved < 0) {
printf("dup failed\n");
last_exec_status = 1 << 8;
return -1;
}
saved_fds[target] = saved;
}
int flags;
if (op == REDIRECT_OP_IN) {
flags = O_RDONLY;
} else if (op == REDIRECT_OP_OUT) {
flags = O_WRONLY | O_CREAT | O_TRUNC;
} else {
flags = O_WRONLY | O_CREAT;
}
int new_fd = open(path, flags);
if (new_fd < 0) {
printf("cannot open %s\n", path);
last_exec_status = 1 << 8;
restore_redirections();
return -1;
}
if (op == REDIRECT_OP_APPND) {
seek(new_fd, 0, SEEK_END);
}
if (dup2(new_fd, target) < 0) {
close(new_fd);
printf("dup2 failed\n");
last_exec_status = 1 << 8;
restore_redirections();
return -1;
}
close(new_fd);
index = index + 1;
}
return 0;
}
int cursor_back(int count) {
if (count > 0) {
printf("\e[%dD", count);
}
return 0;
}
int delete_at_cursor(char *buf, int cursor, int end) {
/* Shift buf[cursor+1..end) left one slot, redraw, erase the stale
trailing character, reposition cursor. Returns the new end. */
int shift = cursor;
while (shift < end - 1) {
buf[shift] = buf[shift + 1];
shift += 1;
}
end -= 1;
write(STDOUT, buf + cursor, end - cursor);
putchar(' ');
cursor_back(end - cursor + 1);
return end;
}
/* Forward decls: dispatch_buffer calls expand_dollar_question and
try_exec, both of which sort later in this file. cc.py resolves
forward refs silently; clang under -std=c99 needs them up front. */
int expand_dollar_question(char *buffer, int max_len);
int tokenize_argv(char *buf, int *argv_slots);
int try_exec(char *name, int *argv);
/* dispatch_* scratch buffers — file-scope so they stay live for
try_exec() without consuming user stack on every call.
dispatch_argv is the NULL-terminated char** array (stored as int
slots since cc.py globals don't accept char *[]) the kernel walks
when staging the new program's user-stack argv frame; dispatch_bin
holds the `bin/<name>` fallback path; dispatch_name holds the bare
program name (no `bin/` prefix), used to build dispatch_bin. */
int dispatch_argv[MAX_ARGV_ENTRIES + 1];
char dispatch_bin[MAX_PATH];
char dispatch_name[MAX_INPUT];
void dispatch_buffer(char *buf) {
/* Run a single command sitting in input_buf. The Linux-style argv
layout requires argv[0] to be the program name; we extract that
name into dispatch_name (used for the bin/-prefixed retry below),
expand any $? in the args portion, then tokenise the whole buf
in place into dispatch_argv (NULL-terminated char** array). The
array is handed to exec(), which forwards it to the kernel; the
kernel walks it under the shell's PD, copies the strings into
per-side argv scratch, and writes a Linux SysV i386 startup
frame (argc / argv pointers / NULL / empty envp) onto the new
program's user stack before iretd. Updates last_exec_status so
that chain operators (&&, ||) and subsequent $? expansions see
the result. */
int scan = 0;
while (buf[scan] != '\0' && buf[scan] != ' ') {
dispatch_name[scan] = buf[scan];
scan += 1;
}
dispatch_name[scan] = '\0';
if (buf[scan] == ' ') {
if (expand_dollar_question(buf + scan + 1, MAX_INPUT - (scan + 1)) < 0) {
printf("$? expansion exceeded MAX_INPUT\n");
last_exec_status = 1 << 8;
return;
}
}
int argc = tokenize_argv(buf, dispatch_argv);
if (argc < 0) {
printf("too many arguments\n");
last_exec_status = 1 << 8;
return;
}
if (argc == 0) {
return;
}
/* Refresh dispatch_name from the stripped argv[0] so a quoted
command name (e.g. `'echo'`) resolves to its bare basename. */
char *first_arg = (char *)dispatch_argv[0];
int name_index = 0;
while (first_arg[name_index] != '\0') {
dispatch_name[name_index] = first_arg[name_index];
name_index += 1;
}
dispatch_name[name_index] = '\0';
if (strcmp(dispatch_name, "help") == 0) {
printf("Commands: help reboot shutdown\n");
last_exec_status = 0;
} else if (strcmp(dispatch_name, "reboot") == 0) {
reboot();
} else if (strcmp(dispatch_name, "shutdown") == 0) {
shutdown();
printf("APM shutdown failed\n");
last_exec_status = 1 << 8;
} else {
int result = try_exec(dispatch_name, dispatch_argv);
if (result == 1) {
last_exec_status = 126 << 8; /* bash: not executable */
printf("not executable\n");
} else if (result == 0) {
/* Not found in root — retry inside bin/ */
dispatch_bin[0] = 'b';
dispatch_bin[1] = 'i';
dispatch_bin[2] = 'n';
dispatch_bin[3] = '/';
int copy_index = 0;
while (dispatch_name[copy_index] != '\0' && copy_index < MAX_PATH - 5) {
dispatch_bin[4 + copy_index] = dispatch_name[copy_index];
copy_index += 1;
}
dispatch_bin[4 + copy_index] = '\0';
int bin_result = try_exec(dispatch_bin, dispatch_argv);
if (bin_result == 1) {
last_exec_status = 126 << 8;
printf("not executable\n");
} else if (bin_result == 0) {
last_exec_status = 127 << 8;
printf("unknown command\n");
}
}
}
}
int expand_dollar_question(char *buffer, int max_len) {
/* In-place replace every "$?" in buffer with the decimal representation
of the bash-shaped last status:
normal exit -> WEXITSTATUS (bits 15..8 of last_exec_status)
signal kill -> 128 + WTERMSIG (low 7 bits)
Returns the new length, or -1 if the expansion would exceed max_len.
Decoding uses the POSIX-shaped macros from include/wait.h. */
int bash_status;
if (WIFEXITED(last_exec_status)) {
bash_status = WEXITSTATUS(last_exec_status);
} else {
bash_status = 128 + WTERMSIG(last_exec_status);
}
char digits[4];
int digit_count = 0;
int n = bash_status;
if (n == 0) {
digits[0] = '0';
digit_count = 1;
} else {
while (n > 0) {
digits[digit_count] = '0' + (n % 10);
digit_count = digit_count + 1;
n = n / 10;
}
}
/* Reverse digits in place. */
int i = 0;
int j = digit_count - 1;
while (i < j) {
char tmp = digits[i];
digits[i] = digits[j];
digits[j] = tmp;
i = i + 1;
j = j - 1;
}
/* Walk buffer, replace each $? with digits[0..digit_count). */
int read_index = 0;
int len = strlen(buffer);
while (read_index < len - 1) {
if (buffer[read_index] == '$' && buffer[read_index + 1] == '?') {
int growth = digit_count - 2;
if (len + growth >= max_len) {
return -1;
}
/* Shift tail right by growth (could be -1, 0, 1, 2). */
if (growth > 0) {
int tail_index = len;
while (tail_index > read_index + 2) {
buffer[tail_index + growth] = buffer[tail_index];
tail_index = tail_index - 1;
}
buffer[read_index + 2 + growth] = buffer[read_index + 2];
} else if (growth < 0) {
/* Shift tail left. */
int tail_index = read_index + 2;
while (tail_index <= len) {
buffer[tail_index + growth] = buffer[tail_index];
tail_index = tail_index + 1;
}
}
int digit_index = 0;
while (digit_index < digit_count) {
buffer[read_index + digit_index] = digits[digit_index];
digit_index = digit_index + 1;
}
len = len + growth;
read_index = read_index + digit_count;
} else {
read_index = read_index + 1;
}
}
buffer[len] = '\0';
return len;
}
/* find_top_level_pipe — scan `segment` for a `|` outside of quoted
regions that is NOT part of a `||` operator. Returns the byte
offset of the first lone `|`, or -1 if none is present. Returns
-2 if a second lone `|` is present (rejects double pipelines). */
int find_top_level_pipe(char *segment) {
int i = 0;
int in_single = 0;
int in_double = 0;
int first = -1;
while (segment[i] != '\0') {
if (segment[i] == '\'' && in_double == 0) {
in_single = 1 - in_single;
} else if (segment[i] == '"' && in_single == 0) {
in_double = 1 - in_double;
} else if (segment[i] == '|' && in_single == 0 && in_double == 0) {
/* Skip `||` — that is a chain operator already handled. */
if (segment[i + 1] == '|') {
i += 2;
continue;
}
if (first < 0) {
first = i;
} else {
return -2;
}
}
i += 1;
}
return first;
}
/* Forward declarations: history_down / history_up are defined here in
alphabetical order but call replace_line and visual_bell, which sort
after them. cc.py resolves these forward references silently; clang
under -std=c99 (test_cc_compatibility's reference build) requires
explicit declarations. */
int replace_line(char *buf, int cursor, int end, char *new_content, int new_length);
int tokenize_argv(char *buf, int *argv_slots);
int tokenize_pipeline_side(char *source, char *name_out, int *argv_slots);
int visual_bell();
int history_down(char *buf, int cursor, int end) {
/* Walk history one entry forward (toward the live line). Returns
the new end; cursor follows. Silent no-op when already at the
live line — matches bash. On reaching the live line, restores
the partial line that was being edited before the first Up. */
if (history_view == 0) {
return end;
}
history_view = history_view - 1;
if (history_view == 0) {
return replace_line(buf, cursor, end, saved_line, saved_line_length);
}
int slot = (history_count - history_view) % HISTORY_SIZE;
char *entry = history + (slot * MAX_INPUT);
return replace_line(buf, cursor, end, entry, strlen(entry));
}
int history_up(char *buf, int cursor, int end) {
/* Walk history one entry back (toward older commands). Returns
the new end; cursor follows. Visual-bell at the oldest entry.
On the first Up (history_view == 0), snapshot the live edit line
into saved_line so history_down can restore it. */
int max_view = history_count;
if (max_view > HISTORY_SIZE) {
max_view = HISTORY_SIZE;
}
if (history_view >= max_view) {
visual_bell();
return end;
}
if (history_view == 0) {
memcpy(saved_line, buf, end);
saved_line_length = end;
}
history_view = history_view + 1;
int slot = (history_count - history_view) % HISTORY_SIZE;
char *entry = history + (slot * MAX_INPUT);
return replace_line(buf, cursor, end, entry, strlen(entry));
}
int insert_char(char *buf, int cursor, int end, char ch) {
/* Shift buf[cursor..end) right one slot, write ch at cursor, redraw
tail, and reposition cursor. Returns the new end index. Caller
guarantees end < MAX_INPUT. */
int shift = end;
while (shift > cursor) {
buf[shift] = buf[shift - 1];
shift -= 1;
}
buf[cursor] = ch;
end += 1;
write(STDOUT, buf + cursor, end - cursor);
cursor_back(end - cursor - 1);
return end;
}
int parse_chain(char *line) {
/* Tokenize `line` in place: replace operator chars with NUL, fill
segments[]/segment_ops[]. Returns segment count, or -1 if the
line has more than MAX_SEGMENTS segments. Leading whitespace
between operators is trimmed; trailing whitespace within a
segment is left to the existing first-space split. */
int count = 0;
int i = 0;
int len = strlen(line);
while (line[i] == ' ') {
i += 1;
}
segment_offsets[count] = i;
while (i < len) {
if (line[i] == ';') {
line[i] = '\0';
segment_ops[count] = OP_SEMI;
count += 1;
if (count >= MAX_SEGMENTS) {
return -1;
}
i += 1;
while (line[i] == ' ') {
i += 1;
}
segment_offsets[count] = i;
} else if (line[i] == '&' && line[i + 1] == '&') {
line[i] = '\0';
line[i + 1] = '\0';
segment_ops[count] = OP_AND;
count += 1;
if (count >= MAX_SEGMENTS) {
return -1;
}
i += 2;
while (line[i] == ' ') {
i += 1;
}
segment_offsets[count] = i;
} else if (line[i] == '|' && line[i + 1] == '|') {
line[i] = '\0';
line[i + 1] = '\0';
segment_ops[count] = OP_OR;
count += 1;
if (count >= MAX_SEGMENTS) {
return -1;
}
i += 2;
while (line[i] == ' ') {
i += 1;
}
segment_offsets[count] = i;
} else {
i += 1;
}
}
segment_ops[count] = OP_END;
return count + 1;
}
int parse_redirections(char *segment) {
/* Scan `segment` for `>>`, `>`, `<` tokens; for each, capture the
following whitespace-delimited filename into redirect_names and
overwrite the operator+filename region with spaces so the
remaining text dispatches normally. Returns 0 on success or -1
on syntax error (missing filename, filename too long, more than
MAX_REDIRECTS). Sets redirect_count. */
redirect_count = 0;
int i = 0;
int len = strlen(segment);
while (i < len) {
char ch = segment[i];
int op = REDIRECT_OP_NONE;
int op_length = 0;
int target_fd = 0;
if (ch == '>' && i + 1 < len && segment[i + 1] == '>') {
op = REDIRECT_OP_APPND;
op_length = 2;
target_fd = 1;
} else if (ch == '>') {
op = REDIRECT_OP_OUT;
op_length = 1;
target_fd = 1;
} else if (ch == '<') {
op = REDIRECT_OP_IN;
op_length = 1;
target_fd = 0;
} else {
i = i + 1;
continue;
}
if (redirect_count >= MAX_REDIRECTS) {
return -1;
}
int token_start = i;
int scan = i + op_length;
while (scan < len && segment[scan] == ' ') {
scan = scan + 1;
}
if (scan == len || segment[scan] == '>' || segment[scan] == '<') {
return -1;
}
int name_start = scan;
while (scan < len && segment[scan] != ' '
&& segment[scan] != '>' && segment[scan] != '<') {
scan = scan + 1;
}
int name_length = scan - name_start;
if (name_length >= MAX_PATH) {
return -1;
}
char *destination = redirect_names + (redirect_count * MAX_PATH);
int copy_index = 0;
while (copy_index < name_length) {
destination[copy_index] = segment[name_start + copy_index];
copy_index = copy_index + 1;
}
destination[name_length] = '\0';
redirect_ops[redirect_count] = op;
redirect_targets[redirect_count] = target_fd;
redirect_count = redirect_count + 1;
/* Blank the operator+filename region in the segment. */
int blank_index = token_start;
while (blank_index < scan) {
segment[blank_index] = ' ';
blank_index = blank_index + 1;
}
i = scan;
}
return 0;
}
int replace_line(char *buf, int cursor, int end, char *new_content, int new_length) {
/* Erase the current input area on screen by stepping cursor back
to col 0 of input, overprinting end with spaces, stepping back,
then writing new_content. Returns new_length so the caller can
update both cursor and end to the returned value.
Caller guarantees new_length <= MAX_INPUT - 1. */
cursor_back(cursor);
int erase_index = 0;
while (erase_index < end) {
putchar(' ');
erase_index = erase_index + 1;
}
cursor_back(end);
memcpy(buf, new_content, new_length);
if (new_length > 0) {
write(STDOUT, buf, new_length);
}
return new_length;
}
int restore_redirections() {
/* Reverse-apply: for each saved fd, dup2 it back onto the target
and close the saved. Idempotent — saved_fds[i] == -1 means no
save was taken for that target. */
int index = 0;
while (index < 2) {
if (saved_fds[index] >= 0) {
dup2(saved_fds[index], index);
close(saved_fds[index]);
saved_fds[index] = -1;
}
index = index + 1;
}
return 0;
}
int strcmp(const char *a, const char *b) {
int index = 0;
while (1) {
if (a[index] != b[index]) {
return a[index] - b[index];
}
if (a[index] == '\0') {
return 0;
}
index += 1;
}
}
/* tokenize_argv — split `buf` in place on whitespace runs (replacing
the first space of each run with a NUL), filling argv_slots[0..argc-1]
with pointers to each token and writing argv_slots[argc] = NULL.
Returns argc on success, or -1 if argc would exceed MAX_ARGV_ENTRIES.
Single- and double-quoted runs are treated as part of a single
token; the quote characters themselves are left in place for now
(the kernel hands the raw bytes through as argv strings). argv[0]
ends up pointing at the program name token (first whitespace-bounded
chunk of buf). */
int tokenize_argv(char *buf, int *argv_slots) {
/* In-place tokenizer with shell-style quote stripping. Toggling
quote chars (', ") are consumed without being written, so write
lags behind scan inside quoted runs. Quoted spaces are preserved
as part of the token. */
int scan = 0;
int argc = 0;
while (1) {
while (buf[scan] == ' ') {
scan += 1;
}
if (buf[scan] == '\0') {
break;
}
if (argc >= MAX_ARGV_ENTRIES) {
return -1;
}
int write = scan;
argv_slots[argc] = (int)(buf + write);
argc += 1;
int in_single = 0;
int in_double = 0;
int done = 0;
while (!done && buf[scan] != '\0') {
char c = buf[scan];
if (c == '\'' && in_double == 0) {
in_single = 1 - in_single;
scan += 1;
} else if (c == '"' && in_single == 0) {
in_double = 1 - in_double;
scan += 1;
} else if (c == ' ' && in_single == 0 && in_double == 0) {
scan += 1;
done = 1;
} else {
buf[write] = c;
write += 1;
scan += 1;
}
}
buf[write] = '\0';
}
argv_slots[argc] = 0;
return argc;
}
/* tokenize_pipeline_side — for one half of a pipeline (`cmd1` or `cmd2`),
copy the bare basename out of `source` into `name_out` (NUL-terminated)
and tokenise the full source into argv_slots in place. Mirrors what
dispatch_buffer does for a single command. Returns argc or -1 on
overflow. */
int tokenize_pipeline_side(char *source, char *name_out, int *argv_slots) {
/* tokenize_argv strips quotes in place, so the post-tokenize
argv[0] is the basename with no surrounding quotes to copy. */
int argc = tokenize_argv(source, argv_slots);
if (argc <= 0) {
name_out[0] = '\0';
return argc;
}
char *first = (char *)argv_slots[0];
int i = 0;
while (first[i] != '\0') {
name_out[i] = first[i];
i += 1;
}
name_out[i] = '\0';
return argc;
}
int try_exec(char *name, int *argv) {
/* Returns:
0 — file not found; last_exec_status unchanged.
1 — file exists but is not executable; last_exec_status unchanged.
2 — exec succeeded; last_exec_status holds the wait status.
3 — exec failed with OOM; message printed, last_exec_status set. */
int rc = exec(name, argv);
if (rc >= 0) {
last_exec_status = rc;
return 2;
}
if (-rc == ERROR_NOT_EXECUTE) {
return 1;
}
if (-rc == ERROR_FAULT) {
printf("exec: out of memory\n");
last_exec_status = 1 << 8;
return 3;
}
return 0;
}
int visual_bell() {
printf("\e[48;5;4m");
sleep(50);
printf("\e[48;5;0m");
return 0;
}
int main() {
/* Ignore SIGINT — the shell prefers to keep its line editor alive when
the user types Ctrl+C at the prompt. Cooked 0x03 still arrives in
the byte stream so the line editor can choose to display ^C and
reset its input buffer; without SIG_IGN, the kernel-side default is
to kill the program (which here would mean reloading the shell). */
asm("mov ebx, SIGINT\n"
"mov ecx, SIG_IGN\n"
"mov ah, SYS_SYS_SIGNAL\n"
"int 30h\n");
/* Marker print exactly once per shell-load. Tests assert this line
appears once across N commands, verifying shell-survives-child. */
write(STDOUT, "[shell:start]\n", 14);
char *buf = input_buf;
int vga_fd = open("/dev/vga", O_WRONLY);
int kill_len = 0;
while (1) {
write(STDOUT, "$ ", 2);
int cursor = 0;
int end = 0;
while (1) {
char ch = getchar();
if (ch == '\x01') {
/* Ctrl-A: beginning of line */
if (cursor > 0) {
cursor_back(cursor);
cursor = 0;
}
} else if (ch == '\x02') {
/* Ctrl-B: cursor left */
if (cursor > 0) {
cursor_back(1);
cursor -= 1;
}
} else if (ch == '\x03') {
/* Ctrl-C: cancel line */
putchar('\n');
end = 0;
history_view = 0;
break;
} else if (ch == '\x04') {
/* Ctrl-D: shutdown (returns here only on APM failure) */
shutdown();
} else if (ch == '\x05') {
/* Ctrl-E: end of line */
write(STDOUT, buf + cursor, end - cursor);
cursor = end;
} else if (ch == '\x06') {
/* Ctrl-F: cursor right */
if (cursor < end) {
putchar(buf[cursor]);
cursor += 1;
}
} else if (ch == '\b' || ch == '\x7F') {
/* Backspace / DEL */
if (cursor > 0) {
cursor_back(1);
cursor -= 1;
end = delete_at_cursor(buf, cursor, end);
}
} else if (ch == '\x0B') {
/* Ctrl-K: kill to end of line */
if (cursor < end) {
int span = end - cursor;
if (span > MAX_INPUT) {
span = MAX_INPUT;
}
kill_len = span;
int copy_index = 0;
while (copy_index < span) {
kill_buf[copy_index] = buf[cursor + copy_index];
copy_index += 1;
}
int erase_index = 0;
while (erase_index < span) {
putchar(' ');
erase_index += 1;
}
cursor_back(span);
end = cursor;
}
} else if (ch == '\x0C') {
/* Ctrl-L: clear screen and reprompt */
video_mode(vga_fd, VIDEO_MODE_TEXT_80x25);
end = 0;
history_view = 0;
break;
} else if (ch == '\x0E') {
/* Ctrl-N: history down (alias of Down arrow). */
end = history_down(buf, cursor, end);
cursor = end;
} else if (ch == '\x10') {
/* Ctrl-P: history up (alias of Up arrow). */
end = history_up(buf, cursor, end);
cursor = end;
} else if (ch == '\n') {
/* Enter — fd_read_console normalises CR → LF on input
* (PS/2 Enter scancode and serial-terminal CR both
* land here as LF). */
putchar('\n');
break;
} else if (ch == '\x19') {
/* Ctrl-Y: yank from kill buffer */
int yank_index = 0;
while (yank_index < kill_len) {
if (end >= MAX_INPUT) {
visual_bell();
break;
}
end = insert_char(buf, cursor, end, kill_buf[yank_index]);
cursor += 1;
yank_index += 1;
}
} else if (ch == '\x1B') {
/* CSI escape — consume "[" + parameter bytes + final.
Recognised: [A (Up) and [B (Down) for history recall.
Other CSI codes (including xterm modified arrows
like [1;2A from Shift+Up on serial) are silently
discarded so the line editor stays untouched. */
char escape_next = getchar();
if (escape_next == '[') {
char final_byte = getchar();
while (final_byte >= '0' && final_byte <= '?') {
final_byte = getchar();
}
if (final_byte == 'A') {
end = history_up(buf, cursor, end);
cursor = end;
} else if (final_byte == 'B') {
end = history_down(buf, cursor, end);
cursor = end;
}
}
} else if (ch >= ' ') {
/* Printable char — insert at cursor */
if (end >= MAX_INPUT) {
visual_bell();
} else {
end = insert_char(buf, cursor, end, ch);
cursor += 1;
}
}
}
buf[end] = 0;
/* Push to history before dispatch. Skip empty lines and
consecutive duplicates (bash default). Reset history_view
for the next prompt. */
if (end > 0) {
int previous_slot = (history_count - 1) % HISTORY_SIZE;
int is_duplicate = 0;
if (history_count > 0 && strcmp(buf, history + (previous_slot * MAX_INPUT)) == 0) {
is_duplicate = 1;
}
if (is_duplicate == 0) {
int slot = history_count % HISTORY_SIZE;
char *entry = history + (slot * MAX_INPUT);
memcpy(entry, buf, end);
entry[end] = '\0';
history_count = history_count + 1;
}
}
history_view = 0;
if (end == 0) {
continue;
}
/* Tokenize the line into chained segments (`;`, `&&`, `||`),
then dispatch each in input_buf one-at-a-time. chain_buf holds
the parsed copy so per-segment $? expansion in input_buf does
not corrupt segments not yet processed. */
memcpy(chain_buf, buf, end + 1);
int n_segments = parse_chain(chain_buf);
if (n_segments < 0) {
printf("too many commands in chain\n");
continue;
}
int seg_index = 0;
while (seg_index < n_segments) {
int run = 1;
if (seg_index > 0) {
int prev_op = segment_ops[seg_index - 1];
if (prev_op == OP_AND) {
run = (last_exec_status == 0);
} else if (prev_op == OP_OR) {
run = (last_exec_status != 0);
}
}
char *segment = chain_buf + segment_offsets[seg_index];
if (run && segment[0] != '\0') {
int seg_len = strlen(segment);
memcpy(buf, segment, seg_len + 1);
/* Pipeline check: detect `cmd1 | cmd2` before touching
redirection globals. parse_chain already consumed `||`
as OP_OR, so any remaining lone `|` is a pipe operator. */
int pipe_at = find_top_level_pipe(buf);
if (pipe_at == -2) {
write(STDOUT, "shell: pipelines support only one |\n", 36);
last_exec_status = 1 << 8;
} else if (pipe_at >= 0) {
/* Copy left side into pipe_left_buf and trim trailing
spaces. */
int pi = 0;
while (pi < pipe_at) {
pipe_left_buf[pi] = buf[pi];
pi += 1;
}
while (pi > 0 && pipe_left_buf[pi - 1] == ' ') {
pi -= 1;
}
pipe_left_buf[pi] = 0;
/* Copy right side into pipe_right_buf, skip leading
spaces. */
pi = pipe_at + 1;
while (buf[pi] == ' ') {
pi += 1;
}
int rj = 0;
while (buf[pi] != '\0') {
pipe_right_buf[rj] = buf[pi];
pi += 1;
rj += 1;
}
pipe_right_buf[rj] = 0;
/* Reject redirect on either side (v1 limitation). */
if (contains_redirect_token(pipe_left_buf) != 0 ||
contains_redirect_token(pipe_right_buf) != 0) {
write(STDOUT, "shell: pipes cannot combine with < > >>\n", 40);
last_exec_status = 1 << 8;
} else {
/* Tokenise each pipeline side in place: name is
copied out for the bin/-prefixed path build,
pipe_*_argv is filled with pointers into the
NUL-split pipe_*_buf so the kernel can walk
them as char** under the shell's PD when
staging each child's user-stack argv frame.
argv[0] is always the program name. */
int la = tokenize_pipeline_side(pipe_left_buf, pipe_left_name, pipe_left_argv);
int ra = tokenize_pipeline_side(pipe_right_buf, pipe_right_name, pipe_right_argv);
if (la < 0 || ra < 0) {
write(STDOUT, "shell: too many pipeline arguments\n", 35);
last_exec_status = 1 << 8;
} else {
/* Build bin/-prefixed paths into pipe_left_path and
pipe_right_path. Mirror dispatch_buffer's
dispatch_bin assembly. */
pipe_left_path[0] = 'b';
pipe_left_path[1] = 'i';
pipe_left_path[2] = 'n';
pipe_left_path[3] = '/';
int ci = 0;
while (pipe_left_name[ci] != '\0' && ci < MAX_PATH - 5) {
pipe_left_path[4 + ci] = pipe_left_name[ci];
ci += 1;
}
pipe_left_path[4 + ci] = 0;
pipe_right_path[0] = 'b';
pipe_right_path[1] = 'i';
pipe_right_path[2] = 'n';
pipe_right_path[3] = '/';
ci = 0;
while (pipe_right_name[ci] != '\0' && ci < MAX_PATH - 5) {
pipe_right_path[4 + ci] = pipe_right_name[ci];
ci += 1;
}
pipe_right_path[4 + ci] = 0;
int rc = pipeline2(pipe_left_path, pipe_left_argv,
pipe_right_path, pipe_right_argv);
if (rc < 0) {
write(STDOUT, "shell: pipeline failed\n", 23);
last_exec_status = -rc;
} else {
last_exec_status = rc;
}
}
}
} else {
/* Strip redirections out of buf and into the redirect_*
globals BEFORE dispatch_buffer's first-space split looks
at the cmd name. Parse errors short-circuit dispatch. */
if (parse_redirections(buf) < 0) {
printf("redirection syntax error\n");
last_exec_status = 1 << 8;
} else if (apply_redirections() == 0) {
dispatch_buffer(buf);
restore_redirections();
}
/* On apply_redirections failure last_exec_status is set; nothing
to restore (apply rolls back its own saves on the failure path). */
}
}
seg_index += 1;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment