]>
Commit | Line | Data |
---|---|---|
d9fcf2fb | 1 | /* UI_FILE - a generic STDIO like output stream. |
349c5d5f | 2 | |
d01e8234 | 3 | Copyright (C) 1999-2025 Free Software Foundation, Inc. |
d9fcf2fb JM |
4 | |
5 | This file is part of GDB. | |
6 | ||
7 | This program is free software; you can redistribute it and/or modify | |
8 | it under the terms of the GNU General Public License as published by | |
a9762ec7 | 9 | the Free Software Foundation; either version 3 of the License, or |
d9fcf2fb JM |
10 | (at your option) any later version. |
11 | ||
12 | This program is distributed in the hope that it will be useful, | |
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | GNU General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU General Public License | |
a9762ec7 | 18 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
d9fcf2fb | 19 | |
581e13c1 | 20 | /* Implement the ``struct ui_file'' object. */ |
d9fcf2fb | 21 | |
d9fcf2fb | 22 | #include "ui-file.h" |
bf31fd38 | 23 | #include "gdbsupport/gdb_obstack.h" |
06cc9596 | 24 | #include "gdbsupport/gdb_select.h" |
268a13a5 | 25 | #include "gdbsupport/filestuff.h" |
3cd52293 | 26 | #include "cli-out.h" |
f64eea3a | 27 | #include "cli/cli-style.h" |
3c6c449e | 28 | #include <chrono> |
d9fcf2fb | 29 | |
d7e74731 | 30 | null_file null_stream; |
d9fcf2fb | 31 | |
d7e74731 PA |
32 | ui_file::ui_file () |
33 | {} | |
d9fcf2fb | 34 | |
d7e74731 PA |
35 | ui_file::~ui_file () |
36 | {} | |
d9fcf2fb | 37 | |
d7e74731 PA |
38 | void |
39 | ui_file::printf (const char *format, ...) | |
d9fcf2fb | 40 | { |
d7e74731 | 41 | va_list args; |
d9fcf2fb | 42 | |
d7e74731 | 43 | va_start (args, format); |
19a7b8ab | 44 | vprintf (format, args); |
d7e74731 | 45 | va_end (args); |
d9fcf2fb JM |
46 | } |
47 | ||
d7e74731 PA |
48 | void |
49 | ui_file::putstr (const char *str, int quoter) | |
d9fcf2fb | 50 | { |
d53fd721 TT |
51 | while (*str) |
52 | printchar (*str++, quoter, false); | |
d9fcf2fb JM |
53 | } |
54 | ||
d7e74731 | 55 | void |
d53fd721 | 56 | ui_file::putstrn (const char *str, int n, int quoter, bool async_safe) |
d9fcf2fb | 57 | { |
d53fd721 TT |
58 | for (int i = 0; i < n; i++) |
59 | printchar (str[i], quoter, async_safe); | |
d9fcf2fb JM |
60 | } |
61 | ||
4311246b | 62 | void |
d7e74731 | 63 | ui_file::putc (int c) |
449092f6 | 64 | { |
a11ac3b3 TT |
65 | char copy = (char) c; |
66 | write (©, 1); | |
449092f6 CV |
67 | } |
68 | ||
d7e74731 PA |
69 | void |
70 | ui_file::vprintf (const char *format, va_list args) | |
d9fcf2fb | 71 | { |
3cd52293 TT |
72 | ui_out_flags flags = disallow_ui_out_field; |
73 | cli_ui_out (this, flags).vmessage (m_applied_style, format, args); | |
d9fcf2fb JM |
74 | } |
75 | ||
d53fd721 TT |
76 | /* See ui-file.h. */ |
77 | ||
c8d74a7b TT |
78 | void |
79 | ui_file::emit_style_escape (const ui_file_style &style) | |
80 | { | |
81 | if (can_emit_style_escape () && style != m_applied_style) | |
82 | { | |
83 | m_applied_style = style; | |
84 | this->puts (style.to_ansi ().c_str ()); | |
85 | } | |
86 | } | |
87 | ||
88 | /* See ui-file.h. */ | |
89 | ||
d53fd721 TT |
90 | void |
91 | ui_file::printchar (int c, int quoter, bool async_safe) | |
92 | { | |
93 | char buf[4]; | |
94 | int out = 0; | |
95 | ||
96 | c &= 0xFF; /* Avoid sign bit follies */ | |
97 | ||
98 | if (c < 0x20 /* Low control chars */ | |
99 | || (c >= 0x7F && c < 0xA0) /* DEL, High controls */ | |
100 | || (sevenbit_strings && c >= 0x80)) | |
101 | { /* high order bit set */ | |
102 | buf[out++] = '\\'; | |
103 | ||
104 | switch (c) | |
105 | { | |
106 | case '\n': | |
107 | buf[out++] = 'n'; | |
108 | break; | |
109 | case '\b': | |
110 | buf[out++] = 'b'; | |
111 | break; | |
112 | case '\t': | |
113 | buf[out++] = 't'; | |
114 | break; | |
115 | case '\f': | |
116 | buf[out++] = 'f'; | |
117 | break; | |
118 | case '\r': | |
119 | buf[out++] = 'r'; | |
120 | break; | |
121 | case '\033': | |
122 | buf[out++] = 'e'; | |
123 | break; | |
124 | case '\007': | |
125 | buf[out++] = 'a'; | |
126 | break; | |
127 | default: | |
128 | { | |
129 | buf[out++] = '0' + ((c >> 6) & 0x7); | |
130 | buf[out++] = '0' + ((c >> 3) & 0x7); | |
131 | buf[out++] = '0' + ((c >> 0) & 0x7); | |
132 | break; | |
133 | } | |
134 | } | |
135 | } | |
136 | else | |
137 | { | |
138 | if (quoter != 0 && (c == '\\' || c == quoter)) | |
139 | buf[out++] = '\\'; | |
140 | buf[out++] = c; | |
141 | } | |
142 | ||
143 | if (async_safe) | |
144 | this->write_async_safe (buf, out); | |
145 | else | |
146 | this->write (buf, out); | |
147 | } | |
148 | ||
d7e74731 | 149 | \f |
01124a23 | 150 | |
d7e74731 PA |
151 | void |
152 | null_file::write (const char *buf, long sizeof_buf) | |
d9fcf2fb | 153 | { |
d7e74731 | 154 | /* Discard the request. */ |
d9fcf2fb JM |
155 | } |
156 | ||
d7e74731 PA |
157 | void |
158 | null_file::puts (const char *) | |
2a9d5ccf | 159 | { |
d7e74731 | 160 | /* Discard the request. */ |
2a9d5ccf HZ |
161 | } |
162 | ||
d7e74731 PA |
163 | void |
164 | null_file::write_async_safe (const char *buf, long sizeof_buf) | |
d9fcf2fb | 165 | { |
d7e74731 | 166 | /* Discard the request. */ |
d9fcf2fb JM |
167 | } |
168 | ||
d7e74731 PA |
169 | \f |
170 | ||
d7e74731 PA |
171 | string_file::~string_file () |
172 | {} | |
d9fcf2fb JM |
173 | |
174 | void | |
d7e74731 | 175 | string_file::write (const char *buf, long length_buf) |
d9fcf2fb | 176 | { |
d7e74731 | 177 | m_string.append (buf, length_buf); |
d9fcf2fb JM |
178 | } |
179 | ||
8a522c6c PW |
180 | /* See ui-file.h. */ |
181 | ||
182 | bool | |
183 | string_file::term_out () | |
184 | { | |
185 | return m_term_out; | |
186 | } | |
187 | ||
188 | /* See ui-file.h. */ | |
189 | ||
190 | bool | |
191 | string_file::can_emit_style_escape () | |
192 | { | |
193 | return m_term_out && term_cli_styling (); | |
194 | } | |
195 | ||
d7e74731 | 196 | \f |
01124a23 | 197 | |
d7e74731 | 198 | stdio_file::stdio_file (FILE *file, bool close_p) |
449092f6 | 199 | { |
d7e74731 PA |
200 | set_stream (file); |
201 | m_close_p = close_p; | |
449092f6 CV |
202 | } |
203 | ||
d7e74731 PA |
204 | stdio_file::stdio_file () |
205 | : m_file (NULL), | |
206 | m_fd (-1), | |
207 | m_close_p (false) | |
208 | {} | |
d9fcf2fb | 209 | |
d7e74731 | 210 | stdio_file::~stdio_file () |
2a9d5ccf | 211 | { |
d7e74731 PA |
212 | if (m_close_p) |
213 | fclose (m_file); | |
2a9d5ccf HZ |
214 | } |
215 | ||
d9fcf2fb | 216 | void |
d7e74731 | 217 | stdio_file::set_stream (FILE *file) |
d9fcf2fb | 218 | { |
d7e74731 PA |
219 | m_file = file; |
220 | m_fd = fileno (file); | |
d9fcf2fb JM |
221 | } |
222 | ||
d7e74731 PA |
223 | bool |
224 | stdio_file::open (const char *name, const char *mode) | |
d9fcf2fb | 225 | { |
d7e74731 PA |
226 | /* Close the previous stream, if we own it. */ |
227 | if (m_close_p) | |
228 | { | |
229 | fclose (m_file); | |
230 | m_close_p = false; | |
231 | } | |
5d502164 | 232 | |
d419f42d | 233 | gdb_file_up f = gdb_fopen_cloexec (name, mode); |
d9fcf2fb | 234 | |
d7e74731 PA |
235 | if (f == NULL) |
236 | return false; | |
d9fcf2fb | 237 | |
d419f42d | 238 | set_stream (f.release ()); |
d7e74731 | 239 | m_close_p = true; |
5d502164 | 240 | |
d7e74731 | 241 | return true; |
d9fcf2fb JM |
242 | } |
243 | ||
244 | void | |
d7e74731 | 245 | stdio_file::flush () |
d9fcf2fb | 246 | { |
d7e74731 | 247 | fflush (m_file); |
d9fcf2fb JM |
248 | } |
249 | ||
d7e74731 PA |
250 | long |
251 | stdio_file::read (char *buf, long length_buf) | |
449092f6 | 252 | { |
f0881b37 PA |
253 | /* Wait until at least one byte of data is available, or we get |
254 | interrupted with Control-C. */ | |
ad960ed2 | 255 | { |
ad960ed2 | 256 | fd_set readfds; |
f0881b37 | 257 | |
ad960ed2 | 258 | FD_ZERO (&readfds); |
d7e74731 PA |
259 | FD_SET (m_fd, &readfds); |
260 | if (interruptible_select (m_fd + 1, &readfds, NULL, NULL, NULL) == -1) | |
ad960ed2 DJ |
261 | return -1; |
262 | } | |
263 | ||
d7e74731 | 264 | return ::read (m_fd, buf, length_buf); |
449092f6 CV |
265 | } |
266 | ||
d7e74731 PA |
267 | void |
268 | stdio_file::write (const char *buf, long length_buf) | |
d9fcf2fb | 269 | { |
bf1d7d9c | 270 | /* Calling error crashes when we are called from the exception framework. */ |
d7e74731 | 271 | if (fwrite (buf, length_buf, 1, m_file)) |
d4fb63e1 TT |
272 | { |
273 | /* Nothing. */ | |
274 | } | |
d9fcf2fb JM |
275 | } |
276 | ||
d7e74731 PA |
277 | void |
278 | stdio_file::write_async_safe (const char *buf, long length_buf) | |
01124a23 | 279 | { |
9f7bc587 DE |
280 | /* This is written the way it is to avoid a warning from gcc about not using the |
281 | result of write (since it can be declared with attribute warn_unused_result). | |
282 | Alas casting to void doesn't work for this. */ | |
d7e74731 | 283 | if (::write (m_fd, buf, length_buf)) |
d4fb63e1 TT |
284 | { |
285 | /* Nothing. */ | |
286 | } | |
01124a23 DE |
287 | } |
288 | ||
d7e74731 PA |
289 | void |
290 | stdio_file::puts (const char *linebuffer) | |
d9fcf2fb | 291 | { |
e4adb939 EZ |
292 | /* This host-dependent function (with implementations in |
293 | posix-hdep.c and mingw-hdep.c) is given the opportunity to | |
294 | process the output first in host-dependent way. If it does, it | |
295 | should return non-zero, to avoid calling fputs below. */ | |
296 | if (gdb_console_fputs (linebuffer, m_file)) | |
297 | return; | |
bf1d7d9c | 298 | /* Calling error crashes when we are called from the exception framework. */ |
d7e74731 | 299 | if (fputs (linebuffer, m_file)) |
d4fb63e1 TT |
300 | { |
301 | /* Nothing. */ | |
302 | } | |
d9fcf2fb JM |
303 | } |
304 | ||
d7e74731 PA |
305 | bool |
306 | stdio_file::isatty () | |
d9fcf2fb | 307 | { |
d7e74731 | 308 | return ::isatty (m_fd); |
d9fcf2fb JM |
309 | } |
310 | ||
8a522c6c PW |
311 | /* See ui-file.h. */ |
312 | ||
313 | bool | |
314 | stdio_file::can_emit_style_escape () | |
315 | { | |
3cd52293 | 316 | return (this->isatty () |
8a522c6c PW |
317 | && term_cli_styling ()); |
318 | } | |
319 | ||
d7e74731 | 320 | \f |
2a9d5ccf | 321 | |
d7e74731 | 322 | /* This is the implementation of ui_file method 'write' for stderr. |
ffa4ac95 YQ |
323 | gdb_stdout is flushed before writing to gdb_stderr. */ |
324 | ||
d7e74731 PA |
325 | void |
326 | stderr_file::write (const char *buf, long length_buf) | |
ffa4ac95 | 327 | { |
da5bd37e | 328 | gdb_stdout->flush (); |
d7e74731 | 329 | stdio_file::write (buf, length_buf); |
ffa4ac95 YQ |
330 | } |
331 | ||
d7e74731 | 332 | /* This is the implementation of ui_file method 'puts' for stderr. |
ffa4ac95 YQ |
333 | gdb_stdout is flushed before writing to gdb_stderr. */ |
334 | ||
d7e74731 PA |
335 | void |
336 | stderr_file::puts (const char *linebuffer) | |
ffa4ac95 | 337 | { |
da5bd37e | 338 | gdb_stdout->flush (); |
d7e74731 | 339 | stdio_file::puts (linebuffer); |
ffa4ac95 | 340 | } |
ffa4ac95 | 341 | |
d7e74731 PA |
342 | stderr_file::stderr_file (FILE *stream) |
343 | : stdio_file (stream) | |
344 | {} | |
d9fcf2fb | 345 | |
d7e74731 | 346 | \f |
e4c242d9 | 347 | |
2b141965 | 348 | tee_file::tee_file (ui_file *one, ui_file *two) |
d7e74731 | 349 | : m_one (one), |
2b141965 | 350 | m_two (two) |
d7e74731 | 351 | {} |
e4c242d9 | 352 | |
d7e74731 | 353 | tee_file::~tee_file () |
e4c242d9 | 354 | { |
e4c242d9 DJ |
355 | } |
356 | ||
d7e74731 PA |
357 | void |
358 | tee_file::flush () | |
e4c242d9 | 359 | { |
d7e74731 PA |
360 | m_one->flush (); |
361 | m_two->flush (); | |
e4c242d9 DJ |
362 | } |
363 | ||
d7e74731 PA |
364 | void |
365 | tee_file::write (const char *buf, long length_buf) | |
e4c242d9 | 366 | { |
d7e74731 PA |
367 | m_one->write (buf, length_buf); |
368 | m_two->write (buf, length_buf); | |
e4c242d9 DJ |
369 | } |
370 | ||
d7e74731 PA |
371 | void |
372 | tee_file::write_async_safe (const char *buf, long length_buf) | |
e4c242d9 | 373 | { |
d7e74731 PA |
374 | m_one->write_async_safe (buf, length_buf); |
375 | m_two->write_async_safe (buf, length_buf); | |
e4c242d9 DJ |
376 | } |
377 | ||
d7e74731 PA |
378 | void |
379 | tee_file::puts (const char *linebuffer) | |
e4c242d9 | 380 | { |
d7e74731 PA |
381 | m_one->puts (linebuffer); |
382 | m_two->puts (linebuffer); | |
e4c242d9 DJ |
383 | } |
384 | ||
d7e74731 PA |
385 | bool |
386 | tee_file::isatty () | |
e4c242d9 | 387 | { |
d7e74731 | 388 | return m_one->isatty (); |
e4c242d9 | 389 | } |
8a522c6c PW |
390 | |
391 | /* See ui-file.h. */ | |
392 | ||
393 | bool | |
394 | tee_file::term_out () | |
395 | { | |
396 | return m_one->term_out (); | |
397 | } | |
398 | ||
399 | /* See ui-file.h. */ | |
400 | ||
401 | bool | |
402 | tee_file::can_emit_style_escape () | |
403 | { | |
3cd52293 | 404 | return (m_one->term_out () |
8a522c6c PW |
405 | && term_cli_styling ()); |
406 | } | |
0735b091 TT |
407 | |
408 | /* See ui-file.h. */ | |
409 | ||
410 | void | |
7b18593a | 411 | escape_buffering_file::write (const char *buf, long length_buf) |
0735b091 TT |
412 | { |
413 | std::string copy (buf, length_buf); | |
414 | this->puts (copy.c_str ()); | |
415 | } | |
416 | ||
417 | /* See ui-file.h. */ | |
418 | ||
419 | void | |
7b18593a TT |
420 | escape_buffering_file::puts (const char *buf) |
421 | { | |
422 | std::string local_buffer; | |
423 | if (!m_buffer.empty ()) | |
424 | { | |
425 | gdb_assert (m_buffer[0] == '\033'); | |
426 | m_buffer += buf; | |
427 | /* If we need to keep buffering, we'll handle that below. */ | |
428 | local_buffer = std::move (m_buffer); | |
429 | buf = local_buffer.c_str (); | |
430 | } | |
431 | ||
432 | while (*buf != '\0') | |
433 | { | |
434 | const char *esc = strchr (buf, '\033'); | |
435 | if (esc == nullptr) | |
436 | break; | |
437 | ||
438 | /* First, write out any prefix. */ | |
439 | if (esc > buf) | |
440 | { | |
441 | do_write (buf, esc - buf); | |
442 | buf = esc; | |
443 | } | |
444 | ||
445 | int n_read = 0; | |
446 | ansi_escape_result seen = examine_ansi_escape (esc, &n_read); | |
447 | if (seen == ansi_escape_result::INCOMPLETE) | |
448 | { | |
449 | /* Start buffering. */ | |
450 | m_buffer = buf; | |
451 | return; | |
452 | } | |
453 | else if (seen == ansi_escape_result::NO_MATCH) | |
454 | { | |
455 | /* Just emit the ESC . */ | |
456 | n_read = 1; | |
457 | } | |
458 | else | |
459 | gdb_assert (seen == ansi_escape_result::MATCHED); | |
460 | ||
461 | do_write (esc, n_read); | |
462 | buf += n_read; | |
463 | } | |
464 | ||
465 | /* If there is any data remaining in BUF, we can flush it now. */ | |
466 | if (*buf != '\0') | |
467 | do_puts (buf); | |
468 | } | |
469 | ||
470 | /* See ui-file.h. */ | |
471 | ||
472 | void | |
473 | no_terminal_escape_file::do_puts (const char *buf) | |
0735b091 TT |
474 | { |
475 | while (*buf != '\0') | |
476 | { | |
477 | const char *esc = strchr (buf, '\033'); | |
478 | if (esc == nullptr) | |
479 | break; | |
480 | ||
481 | int n_read = 0; | |
482 | if (!skip_ansi_escape (esc, &n_read)) | |
483 | ++esc; | |
484 | ||
485 | this->stdio_file::write (buf, esc - buf); | |
486 | buf = esc + n_read; | |
487 | } | |
488 | ||
489 | if (*buf != '\0') | |
490 | this->stdio_file::write (buf, strlen (buf)); | |
491 | } | |
3c6c449e | 492 | |
7b18593a TT |
493 | void |
494 | no_terminal_escape_file::do_write (const char *buf, long len) | |
495 | { | |
496 | std::string copy (buf, len); | |
497 | do_puts (copy.c_str ()); | |
498 | } | |
499 | ||
3c6c449e TT |
500 | void |
501 | timestamped_file::write (const char *buf, long len) | |
502 | { | |
503 | if (debug_timestamp) | |
504 | { | |
505 | /* Print timestamp if previous print ended with a \n. */ | |
506 | if (m_needs_timestamp) | |
507 | { | |
508 | using namespace std::chrono; | |
509 | ||
510 | steady_clock::time_point now = steady_clock::now (); | |
511 | seconds s = duration_cast<seconds> (now.time_since_epoch ()); | |
512 | microseconds us = duration_cast<microseconds> (now.time_since_epoch () - s); | |
513 | std::string timestamp = string_printf ("%ld.%06ld ", | |
514 | (long) s.count (), | |
515 | (long) us.count ()); | |
516 | m_stream->puts (timestamp.c_str ()); | |
517 | } | |
518 | ||
519 | /* Print the message. */ | |
520 | m_stream->write (buf, len); | |
521 | ||
522 | m_needs_timestamp = (len > 0 && buf[len - 1] == '\n'); | |
523 | } | |
524 | else | |
525 | m_stream->write (buf, len); | |
526 | } |