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