]>
Commit | Line | Data |
---|---|---|
d9fcf2fb | 1 | /* UI_FILE - a generic STDIO like output stream. |
349c5d5f | 2 | |
1d506c26 | 3 | Copyright (C) 1999-2024 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 | ||
90 | void | |
91 | ui_file::reset_style () | |
92 | { | |
93 | if (can_emit_style_escape ()) | |
94 | { | |
95 | m_applied_style = ui_file_style (); | |
96 | this->puts (m_applied_style.to_ansi ().c_str ()); | |
97 | } | |
98 | } | |
99 | ||
100 | /* See ui-file.h. */ | |
101 | ||
d53fd721 TT |
102 | void |
103 | ui_file::printchar (int c, int quoter, bool async_safe) | |
104 | { | |
105 | char buf[4]; | |
106 | int out = 0; | |
107 | ||
108 | c &= 0xFF; /* Avoid sign bit follies */ | |
109 | ||
110 | if (c < 0x20 /* Low control chars */ | |
111 | || (c >= 0x7F && c < 0xA0) /* DEL, High controls */ | |
112 | || (sevenbit_strings && c >= 0x80)) | |
113 | { /* high order bit set */ | |
114 | buf[out++] = '\\'; | |
115 | ||
116 | switch (c) | |
117 | { | |
118 | case '\n': | |
119 | buf[out++] = 'n'; | |
120 | break; | |
121 | case '\b': | |
122 | buf[out++] = 'b'; | |
123 | break; | |
124 | case '\t': | |
125 | buf[out++] = 't'; | |
126 | break; | |
127 | case '\f': | |
128 | buf[out++] = 'f'; | |
129 | break; | |
130 | case '\r': | |
131 | buf[out++] = 'r'; | |
132 | break; | |
133 | case '\033': | |
134 | buf[out++] = 'e'; | |
135 | break; | |
136 | case '\007': | |
137 | buf[out++] = 'a'; | |
138 | break; | |
139 | default: | |
140 | { | |
141 | buf[out++] = '0' + ((c >> 6) & 0x7); | |
142 | buf[out++] = '0' + ((c >> 3) & 0x7); | |
143 | buf[out++] = '0' + ((c >> 0) & 0x7); | |
144 | break; | |
145 | } | |
146 | } | |
147 | } | |
148 | else | |
149 | { | |
150 | if (quoter != 0 && (c == '\\' || c == quoter)) | |
151 | buf[out++] = '\\'; | |
152 | buf[out++] = c; | |
153 | } | |
154 | ||
155 | if (async_safe) | |
156 | this->write_async_safe (buf, out); | |
157 | else | |
158 | this->write (buf, out); | |
159 | } | |
160 | ||
d7e74731 | 161 | \f |
01124a23 | 162 | |
d7e74731 PA |
163 | void |
164 | null_file::write (const char *buf, long sizeof_buf) | |
d9fcf2fb | 165 | { |
d7e74731 | 166 | /* Discard the request. */ |
d9fcf2fb JM |
167 | } |
168 | ||
d7e74731 PA |
169 | void |
170 | null_file::puts (const char *) | |
2a9d5ccf | 171 | { |
d7e74731 | 172 | /* Discard the request. */ |
2a9d5ccf HZ |
173 | } |
174 | ||
d7e74731 PA |
175 | void |
176 | null_file::write_async_safe (const char *buf, long sizeof_buf) | |
d9fcf2fb | 177 | { |
d7e74731 | 178 | /* Discard the request. */ |
d9fcf2fb JM |
179 | } |
180 | ||
d7e74731 PA |
181 | \f |
182 | ||
8a522c6c PW |
183 | /* true if the gdb terminal supports styling, and styling is enabled. */ |
184 | ||
185 | static bool | |
186 | term_cli_styling () | |
187 | { | |
8a522c6c PW |
188 | if (!cli_styling) |
189 | return false; | |
190 | ||
191 | const char *term = getenv ("TERM"); | |
192 | /* Windows doesn't by default define $TERM, but can support styles | |
193 | regardless. */ | |
194 | #ifndef _WIN32 | |
195 | if (term == nullptr || !strcmp (term, "dumb")) | |
196 | return false; | |
197 | #else | |
198 | /* But if they do define $TERM, let us behave the same as on Posix | |
199 | platforms, for the benefit of programs which invoke GDB as their | |
200 | back-end. */ | |
201 | if (term && !strcmp (term, "dumb")) | |
202 | return false; | |
203 | #endif | |
204 | return true; | |
205 | } | |
206 | ||
d7e74731 | 207 | \f |
d9fcf2fb | 208 | |
d7e74731 PA |
209 | string_file::~string_file () |
210 | {} | |
d9fcf2fb JM |
211 | |
212 | void | |
d7e74731 | 213 | string_file::write (const char *buf, long length_buf) |
d9fcf2fb | 214 | { |
d7e74731 | 215 | m_string.append (buf, length_buf); |
d9fcf2fb JM |
216 | } |
217 | ||
8a522c6c PW |
218 | /* See ui-file.h. */ |
219 | ||
220 | bool | |
221 | string_file::term_out () | |
222 | { | |
223 | return m_term_out; | |
224 | } | |
225 | ||
226 | /* See ui-file.h. */ | |
227 | ||
228 | bool | |
229 | string_file::can_emit_style_escape () | |
230 | { | |
231 | return m_term_out && term_cli_styling (); | |
232 | } | |
233 | ||
d7e74731 | 234 | \f |
01124a23 | 235 | |
d7e74731 | 236 | stdio_file::stdio_file (FILE *file, bool close_p) |
449092f6 | 237 | { |
d7e74731 PA |
238 | set_stream (file); |
239 | m_close_p = close_p; | |
449092f6 CV |
240 | } |
241 | ||
d7e74731 PA |
242 | stdio_file::stdio_file () |
243 | : m_file (NULL), | |
244 | m_fd (-1), | |
245 | m_close_p (false) | |
246 | {} | |
d9fcf2fb | 247 | |
d7e74731 | 248 | stdio_file::~stdio_file () |
2a9d5ccf | 249 | { |
d7e74731 PA |
250 | if (m_close_p) |
251 | fclose (m_file); | |
2a9d5ccf HZ |
252 | } |
253 | ||
d9fcf2fb | 254 | void |
d7e74731 | 255 | stdio_file::set_stream (FILE *file) |
d9fcf2fb | 256 | { |
d7e74731 PA |
257 | m_file = file; |
258 | m_fd = fileno (file); | |
d9fcf2fb JM |
259 | } |
260 | ||
d7e74731 PA |
261 | bool |
262 | stdio_file::open (const char *name, const char *mode) | |
d9fcf2fb | 263 | { |
d7e74731 PA |
264 | /* Close the previous stream, if we own it. */ |
265 | if (m_close_p) | |
266 | { | |
267 | fclose (m_file); | |
268 | m_close_p = false; | |
269 | } | |
5d502164 | 270 | |
d419f42d | 271 | gdb_file_up f = gdb_fopen_cloexec (name, mode); |
d9fcf2fb | 272 | |
d7e74731 PA |
273 | if (f == NULL) |
274 | return false; | |
d9fcf2fb | 275 | |
d419f42d | 276 | set_stream (f.release ()); |
d7e74731 | 277 | m_close_p = true; |
5d502164 | 278 | |
d7e74731 | 279 | return true; |
d9fcf2fb JM |
280 | } |
281 | ||
282 | void | |
d7e74731 | 283 | stdio_file::flush () |
d9fcf2fb | 284 | { |
d7e74731 | 285 | fflush (m_file); |
d9fcf2fb JM |
286 | } |
287 | ||
d7e74731 PA |
288 | long |
289 | stdio_file::read (char *buf, long length_buf) | |
449092f6 | 290 | { |
f0881b37 PA |
291 | /* Wait until at least one byte of data is available, or we get |
292 | interrupted with Control-C. */ | |
ad960ed2 | 293 | { |
ad960ed2 | 294 | fd_set readfds; |
f0881b37 | 295 | |
ad960ed2 | 296 | FD_ZERO (&readfds); |
d7e74731 PA |
297 | FD_SET (m_fd, &readfds); |
298 | if (interruptible_select (m_fd + 1, &readfds, NULL, NULL, NULL) == -1) | |
ad960ed2 DJ |
299 | return -1; |
300 | } | |
301 | ||
d7e74731 | 302 | return ::read (m_fd, buf, length_buf); |
449092f6 CV |
303 | } |
304 | ||
d7e74731 PA |
305 | void |
306 | stdio_file::write (const char *buf, long length_buf) | |
d9fcf2fb | 307 | { |
bf1d7d9c | 308 | /* Calling error crashes when we are called from the exception framework. */ |
d7e74731 | 309 | if (fwrite (buf, length_buf, 1, m_file)) |
d4fb63e1 TT |
310 | { |
311 | /* Nothing. */ | |
312 | } | |
d9fcf2fb JM |
313 | } |
314 | ||
d7e74731 PA |
315 | void |
316 | stdio_file::write_async_safe (const char *buf, long length_buf) | |
01124a23 | 317 | { |
9f7bc587 DE |
318 | /* This is written the way it is to avoid a warning from gcc about not using the |
319 | result of write (since it can be declared with attribute warn_unused_result). | |
320 | Alas casting to void doesn't work for this. */ | |
d7e74731 | 321 | if (::write (m_fd, buf, length_buf)) |
d4fb63e1 TT |
322 | { |
323 | /* Nothing. */ | |
324 | } | |
01124a23 DE |
325 | } |
326 | ||
d7e74731 PA |
327 | void |
328 | stdio_file::puts (const char *linebuffer) | |
d9fcf2fb | 329 | { |
e4adb939 EZ |
330 | /* This host-dependent function (with implementations in |
331 | posix-hdep.c and mingw-hdep.c) is given the opportunity to | |
332 | process the output first in host-dependent way. If it does, it | |
333 | should return non-zero, to avoid calling fputs below. */ | |
334 | if (gdb_console_fputs (linebuffer, m_file)) | |
335 | return; | |
bf1d7d9c | 336 | /* Calling error crashes when we are called from the exception framework. */ |
d7e74731 | 337 | if (fputs (linebuffer, m_file)) |
d4fb63e1 TT |
338 | { |
339 | /* Nothing. */ | |
340 | } | |
d9fcf2fb JM |
341 | } |
342 | ||
d7e74731 PA |
343 | bool |
344 | stdio_file::isatty () | |
d9fcf2fb | 345 | { |
d7e74731 | 346 | return ::isatty (m_fd); |
d9fcf2fb JM |
347 | } |
348 | ||
8a522c6c PW |
349 | /* See ui-file.h. */ |
350 | ||
351 | bool | |
352 | stdio_file::can_emit_style_escape () | |
353 | { | |
3cd52293 | 354 | return (this->isatty () |
8a522c6c PW |
355 | && term_cli_styling ()); |
356 | } | |
357 | ||
d7e74731 | 358 | \f |
2a9d5ccf | 359 | |
d7e74731 | 360 | /* This is the implementation of ui_file method 'write' for stderr. |
ffa4ac95 YQ |
361 | gdb_stdout is flushed before writing to gdb_stderr. */ |
362 | ||
d7e74731 PA |
363 | void |
364 | stderr_file::write (const char *buf, long length_buf) | |
ffa4ac95 | 365 | { |
da5bd37e | 366 | gdb_stdout->flush (); |
d7e74731 | 367 | stdio_file::write (buf, length_buf); |
ffa4ac95 YQ |
368 | } |
369 | ||
d7e74731 | 370 | /* This is the implementation of ui_file method 'puts' for stderr. |
ffa4ac95 YQ |
371 | gdb_stdout is flushed before writing to gdb_stderr. */ |
372 | ||
d7e74731 PA |
373 | void |
374 | stderr_file::puts (const char *linebuffer) | |
ffa4ac95 | 375 | { |
da5bd37e | 376 | gdb_stdout->flush (); |
d7e74731 | 377 | stdio_file::puts (linebuffer); |
ffa4ac95 | 378 | } |
ffa4ac95 | 379 | |
d7e74731 PA |
380 | stderr_file::stderr_file (FILE *stream) |
381 | : stdio_file (stream) | |
382 | {} | |
d9fcf2fb | 383 | |
d7e74731 | 384 | \f |
e4c242d9 | 385 | |
2b141965 | 386 | tee_file::tee_file (ui_file *one, ui_file *two) |
d7e74731 | 387 | : m_one (one), |
2b141965 | 388 | m_two (two) |
d7e74731 | 389 | {} |
e4c242d9 | 390 | |
d7e74731 | 391 | tee_file::~tee_file () |
e4c242d9 | 392 | { |
e4c242d9 DJ |
393 | } |
394 | ||
d7e74731 PA |
395 | void |
396 | tee_file::flush () | |
e4c242d9 | 397 | { |
d7e74731 PA |
398 | m_one->flush (); |
399 | m_two->flush (); | |
e4c242d9 DJ |
400 | } |
401 | ||
d7e74731 PA |
402 | void |
403 | tee_file::write (const char *buf, long length_buf) | |
e4c242d9 | 404 | { |
d7e74731 PA |
405 | m_one->write (buf, length_buf); |
406 | m_two->write (buf, length_buf); | |
e4c242d9 DJ |
407 | } |
408 | ||
d7e74731 PA |
409 | void |
410 | tee_file::write_async_safe (const char *buf, long length_buf) | |
e4c242d9 | 411 | { |
d7e74731 PA |
412 | m_one->write_async_safe (buf, length_buf); |
413 | m_two->write_async_safe (buf, length_buf); | |
e4c242d9 DJ |
414 | } |
415 | ||
d7e74731 PA |
416 | void |
417 | tee_file::puts (const char *linebuffer) | |
e4c242d9 | 418 | { |
d7e74731 PA |
419 | m_one->puts (linebuffer); |
420 | m_two->puts (linebuffer); | |
e4c242d9 DJ |
421 | } |
422 | ||
d7e74731 PA |
423 | bool |
424 | tee_file::isatty () | |
e4c242d9 | 425 | { |
d7e74731 | 426 | return m_one->isatty (); |
e4c242d9 | 427 | } |
8a522c6c PW |
428 | |
429 | /* See ui-file.h. */ | |
430 | ||
431 | bool | |
432 | tee_file::term_out () | |
433 | { | |
434 | return m_one->term_out (); | |
435 | } | |
436 | ||
437 | /* See ui-file.h. */ | |
438 | ||
439 | bool | |
440 | tee_file::can_emit_style_escape () | |
441 | { | |
3cd52293 | 442 | return (m_one->term_out () |
8a522c6c PW |
443 | && term_cli_styling ()); |
444 | } | |
0735b091 TT |
445 | |
446 | /* See ui-file.h. */ | |
447 | ||
448 | void | |
449 | no_terminal_escape_file::write (const char *buf, long length_buf) | |
450 | { | |
451 | std::string copy (buf, length_buf); | |
452 | this->puts (copy.c_str ()); | |
453 | } | |
454 | ||
455 | /* See ui-file.h. */ | |
456 | ||
457 | void | |
458 | no_terminal_escape_file::puts (const char *buf) | |
459 | { | |
460 | while (*buf != '\0') | |
461 | { | |
462 | const char *esc = strchr (buf, '\033'); | |
463 | if (esc == nullptr) | |
464 | break; | |
465 | ||
466 | int n_read = 0; | |
467 | if (!skip_ansi_escape (esc, &n_read)) | |
468 | ++esc; | |
469 | ||
470 | this->stdio_file::write (buf, esc - buf); | |
471 | buf = esc + n_read; | |
472 | } | |
473 | ||
474 | if (*buf != '\0') | |
475 | this->stdio_file::write (buf, strlen (buf)); | |
476 | } | |
3c6c449e TT |
477 | |
478 | void | |
479 | timestamped_file::write (const char *buf, long len) | |
480 | { | |
481 | if (debug_timestamp) | |
482 | { | |
483 | /* Print timestamp if previous print ended with a \n. */ | |
484 | if (m_needs_timestamp) | |
485 | { | |
486 | using namespace std::chrono; | |
487 | ||
488 | steady_clock::time_point now = steady_clock::now (); | |
489 | seconds s = duration_cast<seconds> (now.time_since_epoch ()); | |
490 | microseconds us = duration_cast<microseconds> (now.time_since_epoch () - s); | |
491 | std::string timestamp = string_printf ("%ld.%06ld ", | |
492 | (long) s.count (), | |
493 | (long) us.count ()); | |
494 | m_stream->puts (timestamp.c_str ()); | |
495 | } | |
496 | ||
497 | /* Print the message. */ | |
498 | m_stream->write (buf, len); | |
499 | ||
500 | m_needs_timestamp = (len > 0 && buf[len - 1] == '\n'); | |
501 | } | |
502 | else | |
503 | m_stream->write (buf, len); | |
504 | } |