Skip to content

Commit b111fe6

Browse files
ryanbreenclaude
andcommitted
fix(telnetd): convert to proper daemon mode
- Remove MAX_ATTEMPTS limit in accept loop (was causing timeout) - Remove MAX_CYCLES limit in relay loop (was limiting session time) - Accept connections forever in daemon mode - Continue listening after connection closes Also add docs/planning/INIT_SYSTEM_PLAN.md documenting the next major milestone: proper init system with virtual consoles. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 9688735 commit b111fe6

File tree

2 files changed

+241
-22
lines changed

2 files changed

+241
-22
lines changed

docs/planning/INIT_SYSTEM_PLAN.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Init System & Virtual Consoles Implementation Plan
2+
3+
## Overview
4+
5+
Replace `init_shell` as PID 1 with a proper init system that:
6+
1. Manages system services (telnetd, etc.)
7+
2. Supports multiple interaction modes (console, telnet, serial)
8+
3. Spawns shells on virtual consoles like Linux
9+
4. Handles process supervision and cleanup
10+
11+
## Current State
12+
13+
- `init_shell` runs as PID 1 (interactive mode)
14+
- No service management
15+
- No virtual console support
16+
- telnetd exists but has test limits and must be manually started
17+
18+
## Architecture
19+
20+
### Init Process Responsibilities
21+
22+
```
23+
/sbin/init (PID 1)
24+
├── Parse /etc/inittab or built-in config
25+
├── Mount filesystems (future)
26+
├── Start services
27+
│ ├── telnetd on port 2323
28+
│ └── (future: sshd, httpd, etc.)
29+
├── Spawn getty on virtual consoles
30+
│ ├── /dev/tty1 → getty → login → shell
31+
│ ├── /dev/tty2 → getty → login → shell
32+
│ └── /dev/ttyS0 → getty (serial console)
33+
├── Reap orphaned processes (wait for zombies)
34+
└── Handle shutdown signals
35+
```
36+
37+
### Virtual Console Model (Linux-style)
38+
39+
```
40+
/dev/console - System console (kernel messages)
41+
/dev/tty0 - Current virtual console
42+
/dev/tty1-6 - Virtual text consoles (Alt+F1 through Alt+F6)
43+
/dev/ttyS0 - Serial console (QEMU -serial stdio)
44+
/dev/pts/* - Pseudo-terminals (telnet, ssh, screen)
45+
```
46+
47+
## Implementation Phases
48+
49+
### Phase 1: Basic Init Structure
50+
51+
**Goal:** Create init binary that starts services and shells
52+
53+
**Files:**
54+
- `userspace/tests/init.rs` - Main init process
55+
- `kernel/src/main.rs` - Load init instead of init_shell
56+
57+
**Behavior:**
58+
```rust
59+
// init.rs pseudocode
60+
fn main() {
61+
// We are PID 1
62+
println!("Breenix init starting...");
63+
64+
// Start telnetd in background
65+
if fork() == 0 {
66+
exec("/bin/telnetd");
67+
}
68+
69+
// Start shell on console
70+
if fork() == 0 {
71+
exec("/bin/init_shell");
72+
}
73+
74+
// Reap zombies forever
75+
loop {
76+
waitpid(-1, WNOHANG);
77+
yield();
78+
}
79+
}
80+
```
81+
82+
### Phase 2: Virtual Console Infrastructure
83+
84+
**Goal:** Implement /dev/ttyN virtual consoles
85+
86+
**Kernel Changes:**
87+
- `kernel/src/tty/vt.rs` - Virtual terminal multiplexer
88+
- `kernel/src/tty/console.rs` - Console output routing
89+
- Support Alt+Fn switching between consoles
90+
91+
**Each VT has:**
92+
- Input buffer (keyboard → VT)
93+
- Output buffer (VT → screen)
94+
- Foreground process group
95+
- Termios settings
96+
97+
### Phase 3: Getty/Login
98+
99+
**Goal:** Proper login flow on consoles
100+
101+
**Files:**
102+
- `userspace/tests/getty.rs` - Open TTY, prompt for login
103+
- `userspace/tests/login.rs` - Authenticate and exec shell (future)
104+
105+
**Flow:**
106+
```
107+
init spawns: getty /dev/tty1
108+
getty: opens /dev/tty1, sets termios, prints "login: "
109+
getty: reads username, execs login
110+
login: (future: authenticate), execs shell
111+
shell: runs as user session
112+
```
113+
114+
For now (single-user): getty → shell directly
115+
116+
### Phase 4: Service Management
117+
118+
**Goal:** Structured service start/stop
119+
120+
**Config format (simple):**
121+
```
122+
# /etc/inittab or built-in
123+
::sysinit:/bin/mount -a
124+
::respawn:/sbin/getty /dev/tty1
125+
::respawn:/sbin/getty /dev/tty2
126+
::once:/sbin/telnetd
127+
```
128+
129+
**Respawn logic:**
130+
- If service exits, restart it
131+
- Rate-limit restarts (don't spin)
132+
133+
### Phase 5: Serial Console Support
134+
135+
**Goal:** Shell accessible via QEMU serial
136+
137+
**Current:** Kernel output goes to serial
138+
**Needed:** Bidirectional serial I/O for shell
139+
140+
**Approach:**
141+
- `/dev/ttyS0` backed by UART 0x3F8
142+
- getty spawns on ttyS0
143+
- Serial becomes interactive shell
144+
145+
## File Structure
146+
147+
```
148+
userspace/tests/
149+
├── init.rs # PID 1 init process
150+
├── getty.rs # TTY login prompt
151+
├── init_shell.rs # Interactive shell (unchanged)
152+
└── telnetd.rs # Telnet server (remove test limits)
153+
154+
kernel/src/tty/
155+
├── mod.rs # TTY subsystem
156+
├── pty/ # Pseudo-terminals (done)
157+
├── vt.rs # Virtual terminal multiplexer (new)
158+
└── console.rs # Console driver (new)
159+
```
160+
161+
## Migration Path
162+
163+
### Step 1: Create init binary
164+
- Copy minimal logic from init_shell
165+
- Fork telnetd and shell
166+
- Reap zombies
167+
168+
### Step 2: Update kernel to load init
169+
- Change `kernel_main_continue()` to load `/sbin/init`
170+
- Keep init_shell as fallback
171+
172+
### Step 3: Fix telnetd
173+
- Remove MAX_ATTEMPTS/MAX_CYCLES limits
174+
- Run as proper daemon
175+
176+
### Step 4: Add virtual consoles
177+
- Implement /dev/tty1-6
178+
- Add console switching
179+
180+
### Step 5: Add getty
181+
- Simple program: open tty, print prompt, exec shell
182+
183+
## Success Criteria
184+
185+
1. **Boot sequence:**
186+
```
187+
Kernel starts
188+
init (PID 1) starts
189+
telnetd starts (PID 2)
190+
getty/shell starts on console (PID 3)
191+
```
192+
193+
2. **Telnet works:**
194+
```bash
195+
# From host
196+
telnet localhost 2323
197+
# Get shell prompt
198+
breenix>
199+
```
200+
201+
3. **Process tree:**
202+
```
203+
PID 1: init
204+
├── PID 2: telnetd
205+
│ └── PID N: init_shell (per connection)
206+
└── PID 3: init_shell (console)
207+
```
208+
209+
4. **Zombie reaping:**
210+
- Exited processes don't accumulate
211+
- init waits on children
212+
213+
## Open Questions
214+
215+
1. **Config format:** Built-in vs /etc/inittab file?
216+
- Start built-in, add file support later
217+
218+
2. **Console switching:** How to handle Alt+Fn in QEMU?
219+
- QEMU may intercept these; may need different keys
220+
221+
3. **Serial vs VGA console:** Which is primary?
222+
- Serial for now (easier in QEMU)
223+
- VGA console requires framebuffer work
224+
225+
## References
226+
227+
- Linux init(8) man page
228+
- systemd architecture (for modern comparison)
229+
- SysV init behavior
230+
- FreeBSD init/rc system

userspace/tests/telnetd.rs

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -153,16 +153,13 @@ fn handle_connection(client_fd: i32) {
153153
];
154154

155155
let mut buf = [0u8; 1024];
156-
let mut cycles = 0;
157-
const MAX_CYCLES: i32 = 100; // Limit for test - run for limited time
158-
159156
loop {
160157
// Clear revents before polling
161158
for pfd in &mut fds {
162159
pfd.revents = 0;
163160
}
164161

165-
let ready = poll(&mut fds, 100); // 100ms timeout
162+
let ready = poll(&mut fds, 1000); // 1 second timeout
166163

167164
if ready < 0 {
168165
break;
@@ -205,12 +202,6 @@ fn handle_connection(client_fd: i32) {
205202
break;
206203
}
207204
}
208-
209-
cycles += 1;
210-
if cycles >= MAX_CYCLES {
211-
io::print("TELNETD_RELAY_TIMEOUT\n");
212-
break;
213-
}
214205
}
215206

216207
io::print("TELNETD_RELAY_DONE\n");
@@ -248,30 +239,28 @@ pub extern "C" fn _start() -> ! {
248239

249240
io::print("TELNETD_LISTENING\n");
250241

251-
// Accept one connection (for test purposes)
252-
let mut attempts = 0;
253-
const MAX_ATTEMPTS: i32 = 1000;
254-
242+
// Accept connections forever (daemon mode)
255243
loop {
256244
match accept(listen_fd, None) {
257245
Ok(client_fd) => {
258246
io::print("TELNETD_CONNECTED\n");
259247
handle_connection(client_fd);
260-
break;
248+
// After connection closes, continue accepting
249+
io::print("TELNETD_LISTENING\n");
261250
}
262251
Err(_) => {
263-
attempts += 1;
264-
if attempts >= MAX_ATTEMPTS {
265-
io::print("TELNETD_TIMEOUT: no connection\n");
266-
break;
267-
}
252+
// EAGAIN or other error - just yield and retry
268253
process::yield_now();
269254
}
270255
}
271256
}
272257

273-
io::print("TELNETD_TEST_COMPLETE\n");
274-
process::exit(0);
258+
// Unreachable in daemon mode, but keep for completeness
259+
#[allow(unreachable_code)]
260+
{
261+
io::print("TELNETD_SHUTDOWN\n");
262+
process::exit(0);
263+
}
275264
}
276265

277266
#[panic_handler]

0 commit comments

Comments
 (0)