]>
Commit | Line | Data |
---|---|---|
1 | /* UI_FILE - a generic STDIO like output stream. | |
2 | ||
3 | Copyright (C) 1999-2025 Free Software Foundation, Inc. | |
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 | |
9 | the Free Software Foundation; either version 3 of the License, or | |
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 | |
18 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ | |
19 | ||
20 | /* Implement the ``struct ui_file'' object. */ | |
21 | ||
22 | #include "ui-file.h" | |
23 | #include "gdbsupport/gdb_obstack.h" | |
24 | #include "gdbsupport/gdb_select.h" | |
25 | #include "gdbsupport/filestuff.h" | |
26 | #include "cli-out.h" | |
27 | #include "cli/cli-style.h" | |
28 | #include <chrono> | |
29 | ||
30 | null_file null_stream; | |
31 | ||
32 | ui_file::ui_file () | |
33 | {} | |
34 | ||
35 | ui_file::~ui_file () | |
36 | {} | |
37 | ||
38 | void | |
39 | ui_file::printf (const char *format, ...) | |
40 | { | |
41 | va_list args; | |
42 | ||
43 | va_start (args, format); | |
44 | vprintf (format, args); | |
45 | va_end (args); | |
46 | } | |
47 | ||
48 | void | |
49 | ui_file::putstr (const char *str, int quoter) | |
50 | { | |
51 | while (*str) | |
52 | printchar (*str++, quoter, false); | |
53 | } | |
54 | ||
55 | void | |
56 | ui_file::putstrn (const char *str, int n, int quoter, bool async_safe) | |
57 | { | |
58 | for (int i = 0; i < n; i++) | |
59 | printchar (str[i], quoter, async_safe); | |
60 | } | |
61 | ||
62 | void | |
63 | ui_file::putc (int c) | |
64 | { | |
65 | char copy = (char) c; | |
66 | write (©, 1); | |
67 | } | |
68 | ||
69 | void | |
70 | ui_file::vprintf (const char *format, va_list args) | |
71 | { | |
72 | ui_out_flags flags = disallow_ui_out_field; | |
73 | cli_ui_out (this, flags).vmessage (m_applied_style, format, args); | |
74 | } | |
75 | ||
76 | /* See ui-file.h. */ | |
77 | ||
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::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 | ||
149 | \f | |
150 | ||
151 | void | |
152 | null_file::write (const char *buf, long sizeof_buf) | |
153 | { | |
154 | /* Discard the request. */ | |
155 | } | |
156 | ||
157 | void | |
158 | null_file::puts (const char *) | |
159 | { | |
160 | /* Discard the request. */ | |
161 | } | |
162 | ||
163 | void | |
164 | null_file::write_async_safe (const char *buf, long sizeof_buf) | |
165 | { | |
166 | /* Discard the request. */ | |
167 | } | |
168 | ||
169 | \f | |
170 | ||
171 | string_file::~string_file () | |
172 | {} | |
173 | ||
174 | void | |
175 | string_file::write (const char *buf, long length_buf) | |
176 | { | |
177 | m_string.append (buf, length_buf); | |
178 | } | |
179 | ||
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 | ||
196 | \f | |
197 | ||
198 | stdio_file::stdio_file (FILE *file, bool close_p) | |
199 | { | |
200 | set_stream (file); | |
201 | m_close_p = close_p; | |
202 | } | |
203 | ||
204 | stdio_file::stdio_file () | |
205 | : m_file (NULL), | |
206 | m_fd (-1), | |
207 | m_close_p (false) | |
208 | {} | |
209 | ||
210 | stdio_file::~stdio_file () | |
211 | { | |
212 | if (m_close_p) | |
213 | fclose (m_file); | |
214 | } | |
215 | ||
216 | void | |
217 | stdio_file::set_stream (FILE *file) | |
218 | { | |
219 | m_file = file; | |
220 | m_fd = fileno (file); | |
221 | } | |
222 | ||
223 | bool | |
224 | stdio_file::open (const char *name, const char *mode) | |
225 | { | |
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 | } | |
232 | ||
233 | gdb_file_up f = gdb_fopen_cloexec (name, mode); | |
234 | ||
235 | if (f == NULL) | |
236 | return false; | |
237 | ||
238 | set_stream (f.release ()); | |
239 | m_close_p = true; | |
240 | ||
241 | return true; | |
242 | } | |
243 | ||
244 | void | |
245 | stdio_file::flush () | |
246 | { | |
247 | fflush (m_file); | |
248 | } | |
249 | ||
250 | long | |
251 | stdio_file::read (char *buf, long length_buf) | |
252 | { | |
253 | /* Wait until at least one byte of data is available, or we get | |
254 | interrupted with Control-C. */ | |
255 | { | |
256 | fd_set readfds; | |
257 | ||
258 | FD_ZERO (&readfds); | |
259 | FD_SET (m_fd, &readfds); | |
260 | if (interruptible_select (m_fd + 1, &readfds, NULL, NULL, NULL) == -1) | |
261 | return -1; | |
262 | } | |
263 | ||
264 | return ::read (m_fd, buf, length_buf); | |
265 | } | |
266 | ||
267 | void | |
268 | stdio_file::write (const char *buf, long length_buf) | |
269 | { | |
270 | /* Calling error crashes when we are called from the exception framework. */ | |
271 | if (fwrite (buf, length_buf, 1, m_file)) | |
272 | { | |
273 | /* Nothing. */ | |
274 | } | |
275 | } | |
276 | ||
277 | void | |
278 | stdio_file::write_async_safe (const char *buf, long length_buf) | |
279 | { | |
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. */ | |
283 | if (::write (m_fd, buf, length_buf)) | |
284 | { | |
285 | /* Nothing. */ | |
286 | } | |
287 | } | |
288 | ||
289 | void | |
290 | stdio_file::puts (const char *linebuffer) | |
291 | { | |
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; | |
298 | /* Calling error crashes when we are called from the exception framework. */ | |
299 | if (fputs (linebuffer, m_file)) | |
300 | { | |
301 | /* Nothing. */ | |
302 | } | |
303 | } | |
304 | ||
305 | bool | |
306 | stdio_file::isatty () | |
307 | { | |
308 | return ::isatty (m_fd); | |
309 | } | |
310 | ||
311 | /* See ui-file.h. */ | |
312 | ||
313 | bool | |
314 | stdio_file::can_emit_style_escape () | |
315 | { | |
316 | return (this->isatty () | |
317 | && term_cli_styling ()); | |
318 | } | |
319 | ||
320 | \f | |
321 | ||
322 | /* This is the implementation of ui_file method 'write' for stderr. | |
323 | gdb_stdout is flushed before writing to gdb_stderr. */ | |
324 | ||
325 | void | |
326 | stderr_file::write (const char *buf, long length_buf) | |
327 | { | |
328 | gdb_stdout->flush (); | |
329 | stdio_file::write (buf, length_buf); | |
330 | } | |
331 | ||
332 | /* This is the implementation of ui_file method 'puts' for stderr. | |
333 | gdb_stdout is flushed before writing to gdb_stderr. */ | |
334 | ||
335 | void | |
336 | stderr_file::puts (const char *linebuffer) | |
337 | { | |
338 | gdb_stdout->flush (); | |
339 | stdio_file::puts (linebuffer); | |
340 | } | |
341 | ||
342 | stderr_file::stderr_file (FILE *stream) | |
343 | : stdio_file (stream) | |
344 | {} | |
345 | ||
346 | \f | |
347 | ||
348 | tee_file::tee_file (ui_file *one, ui_file *two) | |
349 | : m_one (one), | |
350 | m_two (two) | |
351 | {} | |
352 | ||
353 | tee_file::~tee_file () | |
354 | { | |
355 | } | |
356 | ||
357 | void | |
358 | tee_file::flush () | |
359 | { | |
360 | m_one->flush (); | |
361 | m_two->flush (); | |
362 | } | |
363 | ||
364 | void | |
365 | tee_file::write (const char *buf, long length_buf) | |
366 | { | |
367 | m_one->write (buf, length_buf); | |
368 | m_two->write (buf, length_buf); | |
369 | } | |
370 | ||
371 | void | |
372 | tee_file::write_async_safe (const char *buf, long length_buf) | |
373 | { | |
374 | m_one->write_async_safe (buf, length_buf); | |
375 | m_two->write_async_safe (buf, length_buf); | |
376 | } | |
377 | ||
378 | void | |
379 | tee_file::puts (const char *linebuffer) | |
380 | { | |
381 | m_one->puts (linebuffer); | |
382 | m_two->puts (linebuffer); | |
383 | } | |
384 | ||
385 | bool | |
386 | tee_file::isatty () | |
387 | { | |
388 | return m_one->isatty (); | |
389 | } | |
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 | { | |
404 | return (m_one->term_out () | |
405 | && term_cli_styling ()); | |
406 | } | |
407 | ||
408 | /* See ui-file.h. */ | |
409 | ||
410 | void | |
411 | escape_buffering_file::write (const char *buf, long length_buf) | |
412 | { | |
413 | std::string copy (buf, length_buf); | |
414 | this->puts (copy.c_str ()); | |
415 | } | |
416 | ||
417 | /* See ui-file.h. */ | |
418 | ||
419 | void | |
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) | |
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 | } | |
492 | ||
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 | ||
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 | } |