Skip to content

Commit 9c9a6d1

Browse files
committed
Fix CTRL+D behaviour
1 parent b357eac commit 9c9a6d1

File tree

3 files changed

+145
-4
lines changed

3 files changed

+145
-4
lines changed

src/cat.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,62 @@
11
package main
22

33
import (
4+
"bufio"
45
"io"
56
"log"
67
"os"
78
)
89

10+
// ctrlDReader wraps a reader and detects Ctrl+D (ASCII 4) as EOT/EOF
11+
type ctrlDReader struct {
12+
r io.Reader
13+
}
14+
15+
func (c *ctrlDReader) Read(p []byte) (n int, err error) {
16+
n, err = c.r.Read(p)
17+
18+
// Check for Ctrl+D (ASCII 4)
19+
for i := 0; i < n; i++ {
20+
if p[i] == 4 {
21+
// If we found Ctrl+D, return data up to that point and signal EOF
22+
return i, io.EOF
23+
}
24+
}
25+
26+
return n, err
27+
}
28+
929
func main() {
30+
stdout := os.Stdout
31+
1032
if len(os.Args) == 1 {
11-
_, err := io.Copy(os.Stdout, os.Stdin)
12-
if err != nil {
33+
// Create a reader that detects Ctrl+D as EOF
34+
stdin := &ctrlDReader{r: os.Stdin}
35+
reader := bufio.NewReader(stdin)
36+
37+
// Copy stdin contents to stdout
38+
_, err := io.Copy(stdout, reader)
39+
if err != nil && err != io.EOF {
1340
log.Fatal(err)
1441
}
1542
} else {
1643
for _, fname := range os.Args[1:] {
44+
// Open file in binary mode
1745
fh, err := os.Open(fname)
1846
if err != nil {
1947
log.Fatal(err)
2048
}
2149

22-
_, err = io.Copy(os.Stdout, fh)
23-
if err != nil {
50+
// Use buffered I/O for better performance
51+
reader := bufio.NewReader(fh)
52+
53+
// Copy file contents preserving all bytes
54+
_, err = io.Copy(stdout, reader)
55+
if err != nil && err != io.EOF {
2456
log.Fatal(err)
2557
}
58+
59+
fh.Close()
2660
}
2761
}
2862
}

src/tcp.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"log"
7+
"net"
8+
"strings"
9+
)
10+
11+
type Server struct {
12+
host string
13+
port string
14+
}
15+
16+
type Client struct {
17+
conn net.Conn
18+
}
19+
20+
type Config struct {
21+
Host string
22+
Port string
23+
}
24+
25+
func New(config *Config) *Server {
26+
return &Server{
27+
host: config.Host,
28+
port: config.Port,
29+
}
30+
}
31+
32+
func (server *Server) Run() {
33+
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", server.host, server.port))
34+
if err != nil {
35+
log.Fatal(err)
36+
}
37+
defer listener.Close()
38+
39+
for {
40+
conn, err := listener.Accept()
41+
if err != nil {
42+
log.Fatal(err)
43+
}
44+
45+
client := &Client{
46+
conn: conn,
47+
}
48+
go client.handleRequest()
49+
}
50+
}
51+
52+
func (client *Client) handleRequest() {
53+
reader := bufio.NewReader(client.conn)
54+
for {
55+
message, err := reader.ReadString('\n')
56+
if err != nil {
57+
client.conn.Close()
58+
return
59+
}
60+
message = strings.Trim(message, "\n\r")
61+
fmt.Sprintf("Message incoming: %s\n", message)
62+
client.conn.Write([]byte(fmt.Sprintf("Message received: %s\n", message)))
63+
if message == "exit" {
64+
fmt.Println("Exiting connection...")
65+
client.conn.Close()
66+
return
67+
}
68+
}
69+
}
70+
71+
func main() {
72+
server := New(&Config{
73+
Host: "localhost",
74+
Port: "8888",
75+
})
76+
fmt.Println("run...")
77+
server.Run()
78+
fmt.Println("...done")
79+
}

terminal_input_explanation.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Why Enter is Required After Ctrl+D
2+
3+
The requirement to press Enter after Ctrl+D is due to how terminal input is handled at the operating system level, not in our Go code.
4+
5+
## Terminal Input Modes
6+
7+
In Unix-like systems (including Alpine Linux), terminals operate in "canonical mode" (cooked mode) by default where:
8+
- Input is line-buffered by the terminal driver at the OS level
9+
- Input isn't sent to the application until a line delimiter (Enter/CR/LF) is received
10+
- Special characters like Ctrl+D are interpreted by the terminal driver
11+
12+
## The Actual Issue
13+
14+
When you press Ctrl+D in a terminal:
15+
1. The terminal driver in canonical mode doesn't immediately send this to our application
16+
2. It holds the input in its buffer until Enter is pressed
17+
3. Only after Enter is pressed does our application receive the input, including the Ctrl+D character
18+
4. Our ctrlDReader then detects the Ctrl+D and signals EOF
19+
20+
## Why bufio.Reader Doesn't Help
21+
22+
The bufio.Reader in our code provides buffering for data that has already been delivered to our application by the OS. It doesn't affect how or when the OS delivers that data to our application in the first place.
23+
24+
## Potential Solution
25+
26+
To make Ctrl+D work without requiring Enter, you would need to put the terminal in raw mode using terminal control libraries like `golang.org/x/term`. This would bypass the OS-level line buffering so every keystroke would be immediately sent to your application.
27+
28+
However, raw mode has other implications - it disables all terminal processing, including echo, so you'd need to handle that yourself if needed.

0 commit comments

Comments
 (0)