1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! Stdout-redirect dance for the MCP transport.
//!
//! ## Why this exists
//!
//! The MCP wire format on stdio is JSON-RPC 2.0, one message per line
//! on stdout. ANY other write to stdout corrupts the protocol — a
//! lone `println!` from somewhere in the dep tree, a debug print left
//! in by accident, a banner from a third-party crate. The MCP client
//! sees garbage where it expected JSON and disconnects.
//!
//! ## Engine-side fix + this defense-in-depth backstop
//!
//! As of the engine-stdout-pollution cleanup, the SQLRite engine's
//! `process_command` no longer writes to stdout. The historical REPL-
//! convenience prints (CREATE TABLE schema dump, INSERT row dump,
//! SELECT result table) all moved out: the rendered SELECT table now
//! comes back inside [`sqlrite::CommandOutput::rendered`] for the REPL
//! to print itself, and the CREATE / INSERT prints were dropped
//! entirely (the latter was a spammy bug-feature anyway). So in normal
//! operation this redirect catches nothing.
//!
//! We keep the redirect anyway as **defense in depth.** Three concrete
//! futures it protects against: (a) a future engine PR accidentally
//! reintroducing a `println!` that the test suite doesn't catch
//! (engine tests don't assert on stdout); (b) a transitive dep adding
//! a startup banner; (c) a debug print left in mid-development. Any
//! one of those would silently break the MCP server without this
//! safety net. Cost: ~140 LOC + a libc dep that's already a
//! transitive of clap.
//!
//! ## What this module does
//!
//! At process startup, before opening the database:
//!
//! 1. Duplicate fd 1 (stdout) to get a private handle pointing at
//! the original destination — that's where MCP responses go.
//! 2. `dup2(2, 1)` — overwrite fd 1 with stderr, so any subsequent
//! `print!` / `println!` from anywhere in the process ends up on
//! stderr (visible in the MCP client's "server log" pane).
//! 3. Hand back a `File` wrapping the saved-off fd; the transport
//! runner writes JSON-RPC responses to it.
//!
//! ## Cross-platform notes
//!
//! `libc::dup` / `libc::dup2` exist on both Unix and Windows (Windows
//! targets resolve them to `_dup` / `_dup2` in the MSVCRT). The
//! divergence is in turning the resulting C runtime fd into a Rust
//! `File`:
//!
//! - **Unix**: `std::os::fd::FromRawFd::from_raw_fd(fd) -> File`
//! directly. The fd IS the kernel handle.
//! - **Windows**: C runtime fds aren't kernel handles. We need
//! `_get_osfhandle(fd) -> HANDLE`, then
//! `std::os::windows::io::FromRawHandle::from_raw_handle(handle) -> File`.
//!
//! Two `#[cfg]` arms below.
use File;
use io;
/// Replace process fd 1 (stdout) with a duplicate of fd 2 (stderr),
/// returning a `File` that still writes to the original stdout. Call
/// this exactly once, very early in `main`, before anything else can
/// emit stdout.
///
/// Returns an error if either `dup` or `dup2` fails — both extremely
/// rare (out of file descriptors, somebody closed fd 1 already, etc.)
/// and worth aborting on.