v2 / vlib / os / pipe.c.v
213 lines · 187 sloc · 5.11 KB · 2d2d13d7977947ccb7071c47aaee6a4bd8d375af
Raw
1module os
2
3fn C._dup(fd i32) i32
4fn C._dup2(fd1 i32, fd2 i32) i32
5fn C._pipe(fds &int, size u32, mode i32) i32
6fn C.dup(fd i32) i32
7
8const fd_stdout = $if windows { 1 } $else { C.STDOUT_FILENO }
9const fd_stderr = $if windows { 2 } $else { C.STDERR_FILENO }
10
11// fd_dup duplicates a file descriptor
12pub 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.
19pub 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]
25pub struct Pipe {
26pub mut:
27 read_fd int = -1
28 write_fd int = -1
29}
30
31// pipe creates a new pipe for inter-process communication
32pub 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
51pub 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
63pub 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 int(result)
69}
70
71// write writes data from the buffer to the pipe
72pub 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 int(result)
78}
79
80// write_string writes data from the string to the pipe
81pub fn (p &Pipe) write_string(s string) !int {
82 result := C.write(p.write_fd, voidptr(s.str), s.len)
83 if result == -1 {
84 return error('Write failed')
85 }
86 return int(result)
87}
88
89// slurp reads all data from the pipe until EOF
90pub fn (mut p Pipe) slurp() []string {
91 // Close write end to send EOF signal to the pipe
92 fd_close(p.write_fd)
93 p.write_fd = -1
94 result := fd_slurp(p.read_fd)
95 fd_close(p.read_fd)
96 p.read_fd = -1
97 return result
98}
99
100// IOCapture manages redirection of standard output and error streams
101@[noinit]
102pub struct IOCapture {
103pub mut:
104 stdout Pipe
105 stderr Pipe
106mut:
107 original_stdout_fd int = -1
108 original_stderr_fd int = -1
109}
110
111// stdio_capture starts capturing stdout and stderr by redirecting them to pipes
112// Example:
113// ```v
114// import os
115// unbuffer_stdout()
116// mut cap := os.stdio_capture()!
117// println('hello println')
118// eprintln('hello eprintln')
119// cap.stop()
120// sout := cap.stdout.slurp()
121// serr := cap.stderr.slurp()
122// cap.close()
123// assert sout == ['hello println\n']
124// assert serr == ['hello eprintln\n']
125// ```
126// or
127// ```v
128// import os
129// unbuffer_stdout()
130// mut cap := os.stdio_capture()!
131// println('hello println')
132// eprintln('hello eprintln')
133// eprintln('World eprintln')
134// println('World println')
135// sout, serr := cap.finish()
136// assert sout == ['hello println\nWorld println\n']
137// assert serr == ['hello eprintln\nWorld eprintln\n']
138// ```
139pub fn stdio_capture() !IOCapture {
140 mut c := IOCapture{}
141 mut pipe_stdout := pipe()!
142 mut pipe_stderr := pipe()!
143
144 flush_stdout()
145 flush_stderr()
146
147 // Save original file descriptors
148 c.original_stdout_fd = fd_dup(fd_stdout)
149 c.original_stderr_fd = fd_dup(fd_stderr)
150
151 // Redirect stdout to pipe
152 if fd_dup2(pipe_stdout.write_fd, fd_stdout) == -1 {
153 pipe_stdout.close()
154 pipe_stderr.close()
155 return error('Failed to redirect stdout')
156 }
157
158 // Redirect stderr to pipe
159 if fd_dup2(pipe_stderr.write_fd, fd_stderr) == -1 {
160 fd_dup2(c.original_stdout_fd, fd_stdout) // Restore stdout
161 pipe_stdout.close()
162 pipe_stderr.close()
163 return error('Failed to redirect stderr')
164 }
165
166 // Close original write ends (duplicated by dup2)
167 fd_close(pipe_stdout.write_fd)
168 fd_close(pipe_stderr.write_fd)
169
170 pipe_stdout.write_fd = -1
171 pipe_stderr.write_fd = -1
172
173 // Store pipes for later reading
174 c.stdout = pipe_stdout
175 c.stderr = pipe_stderr
176 return c
177}
178
179// stop restores the original stdout and stderr streams
180// This should be called to resume normal console output
181pub fn (mut c IOCapture) stop() {
182 flush_stdout()
183 flush_stderr()
184
185 // Restore original stdout
186 if c.original_stdout_fd != -1 {
187 fd_dup2(c.original_stdout_fd, fd_stdout)
188 fd_close(c.original_stdout_fd)
189 c.original_stdout_fd = -1
190 }
191
192 // Restore original stderr
193 if c.original_stderr_fd != -1 {
194 fd_dup2(c.original_stderr_fd, fd_stderr)
195 fd_close(c.original_stderr_fd)
196 c.original_stderr_fd = -1
197 }
198}
199
200// close releases all resources associated with the capture
201pub fn (mut c IOCapture) close() {
202 c.stdout.close()
203 c.stderr.close()
204}
205
206// finish stops capturing, reads all captured data, and releases resources
207pub fn (mut c IOCapture) finish() ([]string, []string) {
208 c.stop()
209 stdout_str := c.stdout.slurp()
210 stderr_str := c.stderr.slurp()
211 c.close()
212 return stdout_str, stderr_str
213}
214