Skip to content

Commit b39a585

Browse files
authored
os: add pipe and stdio_capture support (implement #25714) (#25716)
1 parent 8123728 commit b39a585

File tree

2 files changed

+399
-0
lines changed

2 files changed

+399
-0
lines changed

vlib/os/pipe.c.v

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
module os
2+
3+
fn C._dup(fd int) int
4+
fn C._dup2(fd1 int, fd2 int) int
5+
fn C._pipe(fds &int, size u32, mode int) int
6+
fn C.dup(fd int) int
7+
8+
const fd_stdout = $if windows { 1 } $else { C.STDOUT_FILENO }
9+
const fd_stderr = $if windows { 2 } $else { C.STDERR_FILENO }
10+
11+
// fd_dup duplicates a file descriptor
12+
pub fn fd_dup(fd int) int {
13+
return $if windows { C._dup(fd) } $else { C.dup(fd) }
14+
}
15+
16+
// fd_dup2 duplicates file descriptor `fd1` to descriptor number `fd2`
17+
// If `fd2` is already open, it is closed first before being reused.
18+
// Returns the new file descriptor on success, or -1 on error.
19+
pub fn fd_dup2(fd1 int, fd2 int) int {
20+
return $if windows { C._dup2(fd1, fd2) } $else { C.dup2(fd1, fd2) }
21+
}
22+
23+
// Pipe represents a bidirectional communication channel
24+
@[noinit]
25+
pub struct Pipe {
26+
mut:
27+
read_fd int = -1
28+
write_fd int = -1
29+
}
30+
31+
// pipe creates a new pipe for inter-process communication
32+
pub fn pipe() !Pipe {
33+
mut fds := [2]int{}
34+
$if windows {
35+
if C._pipe(&fds[0], 0, 0) == -1 {
36+
return error('Failed to create pipe')
37+
}
38+
} $else {
39+
if C.pipe(&fds[0]) == -1 {
40+
return error('Failed to create pipe')
41+
}
42+
}
43+
44+
return Pipe{
45+
read_fd: fds[0]
46+
write_fd: fds[1]
47+
}
48+
}
49+
50+
// close closes the pipe and releases associated resources
51+
pub fn (mut p Pipe) close() {
52+
if p.read_fd != -1 {
53+
fd_close(p.read_fd)
54+
p.read_fd = -1
55+
}
56+
if p.write_fd != -1 {
57+
fd_close(p.write_fd)
58+
p.write_fd = -1
59+
}
60+
}
61+
62+
// read reads data from the pipe into the provided buffer
63+
pub fn (p &Pipe) read(mut buffer []u8) !int {
64+
result := C.read(p.read_fd, buffer.data, buffer.len)
65+
if result == -1 {
66+
return error('Read failed')
67+
}
68+
return result
69+
}
70+
71+
// write writes data from the buffer to the pipe
72+
pub fn (p &Pipe) write(buffer []u8) !int {
73+
result := C.write(p.write_fd, buffer.data, buffer.len)
74+
if result == -1 {
75+
return error('Write failed')
76+
}
77+
return result
78+
}
79+
80+
// slurp reads all data from the pipe until EOF
81+
pub fn (mut p Pipe) slurp() []string {
82+
// Close write end to send EOF signal to the pipe
83+
fd_close(p.write_fd)
84+
p.write_fd = -1
85+
result := fd_slurp(p.read_fd)
86+
fd_close(p.read_fd)
87+
p.read_fd = -1
88+
return result
89+
}
90+
91+
// IOCapture manages redirection of standard output and error streams
92+
@[noinit]
93+
pub struct IOCapture {
94+
pub mut:
95+
stdout &Pipe = unsafe { nil }
96+
stderr &Pipe = unsafe { nil }
97+
mut:
98+
original_stdout_fd int = -1
99+
original_stderr_fd int = -1
100+
}
101+
102+
// stdio_capture starts capturing stdout and stderr by redirecting them to pipes
103+
// example:
104+
// ```v
105+
// mut cap := os.stdio_capture()!
106+
// println('hello println')
107+
// eprintln('hello eprintln')
108+
// cap.stop()
109+
// sout := cap.stdout.slurp()
110+
// serr := cap.stderr.slurp()
111+
// cap.close()
112+
// ```
113+
// or
114+
// ```v
115+
// mut cap := os.stdio_capture()!
116+
// println('hello println')
117+
// eprintln('hello eprintln')
118+
// sout, serr := cap.finish()
119+
// ```
120+
pub fn stdio_capture() !IOCapture {
121+
mut c := IOCapture{}
122+
mut pipe_stdout := pipe()!
123+
mut pipe_stderr := pipe()!
124+
125+
// Save original file descriptors
126+
c.original_stdout_fd = fd_dup(fd_stdout)
127+
c.original_stderr_fd = fd_dup(fd_stderr)
128+
129+
// Redirect stdout to pipe
130+
if fd_dup2(pipe_stdout.write_fd, fd_stdout) == -1 {
131+
pipe_stdout.close()
132+
pipe_stderr.close()
133+
return error('Failed to redirect stdout')
134+
}
135+
136+
// Redirect stderr to pipe
137+
if fd_dup2(pipe_stderr.write_fd, fd_stderr) == -1 {
138+
fd_dup2(c.original_stdout_fd, fd_stdout) // Restore stdout
139+
pipe_stdout.close()
140+
pipe_stderr.close()
141+
return error('Failed to redirect stderr')
142+
}
143+
144+
// Close original write ends (duplicated by dup2)
145+
fd_close(pipe_stdout.write_fd)
146+
fd_close(pipe_stderr.write_fd)
147+
148+
pipe_stdout.write_fd = -1
149+
pipe_stderr.write_fd = -1
150+
151+
// Store pipes for later reading
152+
c.stdout = &pipe_stdout
153+
c.stderr = &pipe_stderr
154+
return c
155+
}
156+
157+
// stop restores the original stdout and stderr streams
158+
// This should be called to resume normal console output
159+
pub fn (mut c IOCapture) stop() {
160+
// Restore original stdout
161+
if c.original_stdout_fd != -1 {
162+
fd_dup2(c.original_stdout_fd, fd_stdout)
163+
fd_close(c.original_stdout_fd)
164+
c.original_stdout_fd = -1
165+
}
166+
167+
// Restore original stderr
168+
if c.original_stderr_fd != -1 {
169+
fd_dup2(c.original_stderr_fd, fd_stderr)
170+
fd_close(c.original_stderr_fd)
171+
c.original_stderr_fd = -1
172+
}
173+
}
174+
175+
// close releases all resources associated with the capture
176+
pub fn (mut c IOCapture) close() {
177+
c.stdout.close()
178+
c.stderr.close()
179+
}
180+
181+
// finish stops capturing, reads all captured data, and releases resources
182+
pub fn (mut c IOCapture) finish() ([]string, []string) {
183+
c.stop()
184+
stdout_str := c.stdout.slurp()
185+
stderr_str := c.stderr.slurp()
186+
c.close()
187+
return stdout_str, stderr_str
188+
}

0 commit comments

Comments
 (0)