Shell track

Shell

How the LainDOS prompt works after boot: built-ins, current directories, AUTOEXEC.BAT, PATH search, environment variables, and the small boundary where it intentionally stops short of COMMAND.COM compatibility.

From boot to A:\>

The shell is just another DOS program, but it is the user-facing control loop for the live image. It receives the initial PSP and environment, runs optional startup commands, then keeps handing child programs to EXEC until EXIT terminates it.

01
Startup
The kernel boots SHELL.COM as the first program, then the shell shrinks its PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer. block and prints the banner.
02
AUTOEXEC
If AUTOEXEC.BAT exists in the root, it runs before the first interactive prompt.
03
Prompt
The prompt is built from the current drive plus AH=47h current-directory state.
04
Dispatch
A line is matched case-insensitively against built-ins or drive switches, then treated as COM, EXE, or BAT.
05
Return
Child programs return through AH=4Dh so the shell can recover state and print the next prompt.

Startup and dispatch

SHELL.COM owns the prompt loop after boot.

The kernel boots SHELL.COM like any other COM program. The shell immediately shrinks its allocation with AH=4Ah, prints LainDOS Shell plus the embedded build id, tries AUTOEXEC.BAT, and then enters the prompt loop.

Every interactive line is read with AH=0Ah, checked for a standalone drive switch, matched case-insensitively against the built-in command table, and finally passed to external command lookup without uppercasing arguments. Tabs count as whitespace everywhere spaces do, so tab-indented batch lines parse normally.

Before dispatch, redir_setup strips a trailing >file or >>file from the line, saves stdout with AH=45h, and force-duplicates the opened target onto handle 1 with AH=46h. All shell output goes through AH=40h writes to handle 1, so built-ins and spawned programs alike inherit the redirection; >NUL discards via the kernel NUL device. redir_restore puts the console back after the command.

programs/shell.asmNASM · 16-bit
23
24
20
29
33
 
35
36
37
38
39
 
41
44
48
49
54
56
62
67
69
70
71
76
mov bx, shell_resident_paras
mov ah, 0x4A
int 0x21
call run_command_tail
call run_autoexec
; ...
prompt:
call print_prompt
call read_line
call execute_line
jmp prompt
; ...
execute_line:
call redir_setup
mov si, line_buf
call skip_command_prefix
call change_drive_command
mov bx, command_table
call cmd_match
call [bx+2]
.external:
call prepare_command
call run_command
call redir_restore

Built-ins and current directory

Built-ins are small DOS API wrappers.

The built-ins intentionally stay narrow. DIR builds a FindFirst/FindNext pattern from the optional operand, formats entries like MS-DOS with fixed 8.3 columns or /W columns, appends human-readable used/free suffixes to the raw-byte summary, and supports /P pagination. CD, MD, RD, and their DOS aliases call the matching directory APIs; RD only removes empty directories, while DELTREE [/Y] path walks a directory tree bottom-up and removes files plus subdirectories, refusing root and current-directory references. COPY copies single files or wildcard patterns (FindFirst/FindNext expansion) with overwrite confirmation, defaulting the destination to the current directory, DEL and ERASE delete files or wildcard patterns with optional /P, REN and RENAME rename one file without moving it, and TYPE opens a file and copies it to handle 1. The large COPY/TYPE buffer is allocated only during those commands so the resident shell leaves more conventional memory for child programs.

Startup-script conveniences are minimal: PAUSE prints Press any key to continue . . . and waits on INT 16h AH=0, BREAK is accepted as a no-op (LainDOS does not implement user-mode Ctrl-Break), MODE CO80 issues INT 10h AH=00h AL=03h, and MORE < file reads the file in transient 4 KiB chunks, prints each line, and waits on the more prompt between screens. PAUSE, MODE, and MORE accept a trailing > nul (with or without space) and suppress the prompt; MORE accepts a leading < file (with space) to redirect input. Batch control includes labels, GOTO, IF [NOT] EXIST path / ERRORLEVEL n / a==b command (ERRORLEVEL compares the stored AH=4Dh exit code; unrecognized forms print Syntax error), and %1-%9/%% parameter expansion for the vendor scripts that need them.

The prompt and drive switches are DOS state, not private shell variables. The prompt asks AH=19h for the current drive and AH=47h for the current directory; C: uses AH=0Eh to select the drive and relies on the kernel to reject missing drives.

programs/shell.asmNASM · 16-bit
242
243
253
257
258
259
265
268
274
275
1260
1261
1283
1309
1330
1338
1342
1350
1363
1378
1379
1650
1685
1654
1662
 
1715
1719
1762
1813
1606
1823
1832
 
1844
1849
1851
1857
1920
1926
 
2033
2037
2041
2057
2092
2202
2209
2037
2218
2238
2246
 
2347
2351
2353
2360
2364
2374
2380
2474
2494
2397
 
2771
2775
2799
2801
2820
2824
2832
 
3745
3748
3749
3756
3757
3758
3760
3766
3767
3768
3770
3771
do_dir:
call parse_dir_args
call print_dir_header
mov dx, dir_pattern
mov cx, ATTR_DIR
mov ah, 0x4E
call print_dir_entry
call print_dir_wide_entry
call finish_dir_wide_row
call print_dir_summary
do_copy:
call parse_copy_args
mov ah, 0x4E
copy_one_file:
mov ah, 0x3D
mov ah, 0x3C
call alloc_type_buffer
mov ah, 0x3F
mov ah, 0x40
call free_type_buffer
mov dx, copy_success_msg
do_cd:
mov dx, si
mov ah, 0x3B
mov ah, 0x47
; ...
do_del:
call del_path_has_wildcard
mov ah, 0x41
confirm_del_prompt:
mov ah, 0x08
del_path_has_wildcard:
cmp al, '*'
; ...
do_ren:
call ren_paths_have_wildcard
call ren_dst_has_path
mov ah, 0x56
ren_dst_has_path:
cmp al, ':'
; ...
do_goto:
call batch_seek_label
do_if:
mov di, exist_arg
mov ax, 0x4300
mov di, line_buf
call execute_line
call batch_seek_label
batch_seek_label:
call batch_read_line
call batch_label_match
; ...
do_pause:
mov dx, pause_msg
call wait_key
do_mode:
mov ax, 0x0003
do_more:
call more_display_file
parse_more_args:
cmp al, '<'
mov di, nul_arg
; ...
change_drive_command:
mov al, [si]
mov dl, al
mov ah, 0x0E
print_prompt:
mov ah, 0x47
mov dx, prompt_end
; ...
command_table:
dw dir_cmd, do_dir
dw cd_cmd, do_cd
dw deltree_cmd, do_deltree
dw copy_cmd, do_copy
dw del_cmd, do_del
dw ren_cmd, do_ren
dw if_cmd, do_if
dw goto_cmd, do_goto
dw pause_cmd, do_pause
dw mode_cmd, do_mode
dw more_cmd, do_more

External command lookup

COM, EXE, and BAT are tried locally before PATH.

If the command has no extension, the shell first tries .COM, then .EXE, then .BAT in the current directory. Only after those fail does it walk PATH for COM, EXE, and BAT candidates. Extension checks are case-insensitive.

Arguments are copied to a PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.-compatible command tail without uppercasing. Successful child programs return through AH=4Dh so the shell consumes the exit status and restores DS before printing the next prompt. Batch files stream line-by-line from their handle and save the previous batch handle so a nested BAT can return to its caller.

programs/shell.asmNASM · 16-bit
2904
2907
2911
2925
2939
2962
2965
2967
2969
2974
 
3006
3011
3019
3027
3033
3039
3045
3049
3068
3069
3081
3090
3094
3109
3124
3125
3134
3138
3139
3150
3153
3157
3158
3160
3164
3169
3174
3182
3185
 
3523
3527
3538
3543
3551
3563
3567
3573
3574
3581
3596
3608
 
3199
3203
3206
3207
3209
3212
3216
3219
 
3237
3250
3254
3264
3282
3284
 
3301
3326
3361
3365
3379
prepare_command:
mov byte [command_has_ext], 0
mov byte [tail_has_args], 0
cmp al, '.'
mov byte [command_has_path], 1
mov al, '.'
mov al, 'C'
mov al, 'O'
mov al, 'M'
call build_cmd_tail
; ...
run_command:
call run_current_command
call run_current_command
call run_batch
call run_path_exec
call run_path_exec
call run_path_batch
call command_ext_is_bat
mov ah, 0x4D
int 0x21
command_ext_is_bat:
and al, 0xDF
and al, 0xDF
run_current_command:
mov ax, 0x4B00
int 0x21
run_autoexec:
mov dx, autoexec_name
call run_batch_named
run_batch_named:
call batch_sync_position
inc byte [batch_active]
push word [batch_handle]
mov ah, 0x3D
mov word [batch_buf_pos], 0
call batch_read_line
call execute_line
pop word [batch_handle]
dec byte [batch_active]
; ...
batch_read_line:
call batch_read_char
cmp cx, 127
call batch_read_char
xor al, al
batch_read_char:
mov bx, [batch_buf_pos]
mov ah, 0x3F
int 0x21
.have_char:
batch_sync_position:
mov ax, 0x4201
; ...
run_path_exec:
run_path_batch:
run_path_command:
cmp byte [command_has_path], 0
call find_path_value
call build_path_candidate
call run_candidate_exec
call run_batch_path
; ...
find_path_value:
mov ah, 0x62
mov ax, [es:0x2C]
mov si, path_env_name
.found:
mov [path_env_seg], es
; ...
build_path_candidate:
cmp al, ';'
mov al, '\'
mov si, command_name
clc

Default environment

COMSPEC, PATH, PROMPT, and BLASTER are kernel-provided.

The boot program gets an MCBMCBA 16-byte DOS memory header that describes the allocated or free block immediately after it.-backed environment before it starts. Default variables include COMSPEC=A:SHELL.COM, PATH=A:;A:BIN, PROMPT=$P$G, and the conventional Sound Blaster string used by game setup tools.

When the shell EXECs a child with env segment 0, the loader copies the shell's environment and appends a fresh DOS executable-path tail. The environment tests read PSPPSPThe DOS data block placed before each program, holding terminate vectors, the job file table, command tail, and environment pointer.:2Ch and verify both the variables and the tail visible to child programs.

src/kernel.asmNASM · 16-bit
1705
1714
1722
1723
1724
1731
 
1765
1771
1776
1784
1786
1788
1811
1725
 
3707
3708
3709
3710
3711
3712
init_environment:
call alloc_exec_environment
call write_environment_vars
mov ax, 1
stosw
mov si, fname_exe
; ...
write_environment_vars:
mov si, env_comspec_name
mov si, env_path_name
mov si, env_blaster
mov si, env_prompt
xor al, al
env_copy_drive_root:
mov al, [cs:dos_drive_letter]
; ...
env_comspec_name: db "COMSPEC=", 0
env_path_name: db "PATH=", 0
env_shell_name: db "SHELL.COM", 0
env_bin_dir: db "BIN", 0
env_blaster: db "BLASTER=A220 I5 D1 H5 P330 T6", 0
env_prompt: db "PROMPT=$P$G", 0

What this is not

LainDOS keeps shell behavior target-driven. The current shell is enough for test images, startup scripts, PATH-based game launchers, and manual emulator use, but it is not a full clone of COMMAND.COM.

No full COMMAND.COM compatibility: no SET command, prompt expansion, user-defined aliases, pipes, general redirection, or wildcard argument expansion.
Batch control is deliberately narrow: labels, GOTO (a missing label prints Label not found and ends the batch), IF with NOT/EXIST/ERRORLEVEL/== forms, %1-%9 parameters, and nested BAT files are supported, but there is no CALL, FOR, environment-variable expansion, or pipes.
AUTOEXEC.BAT is the only startup script. CONFIG.SYS and installable DOS device drivers remain out of scope.
Bad commands print an error and batch execution continues; this matches the current game/test needs rather than full DOS policy.