1/*-
2 * SPDX-License-Identifier: MIT-CMU
3 *
4 * Mach Operating System
5 * Copyright (c) 1991,1990 Carnegie Mellon University
6 * All Rights Reserved.
7 *
8 * Permission to use, copy, modify and distribute this software and its
9 * documentation is hereby granted, provided that both the copyright
10 * notice and this permission notice appear in all copies of the
11 * software, derivative works or modified versions, and any portions
12 * thereof, and that both notices appear in supporting documentation.
13 *
14 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS
15 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
16 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
17 *
18 * Carnegie Mellon requests users of this software to return to
19 *
20 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
21 *  School of Computer Science
22 *  Carnegie Mellon University
23 *  Pittsburgh PA 15213-3890
24 *
25 * any improvements or extensions that they make and grant Carnegie the
26 * rights to redistribute these changes.
27 */
28/*
29 * 	Author: David B. Golub, Carnegie Mellon University
30 *	Date:	7/90
31 */
32
33/*
34 * Printf and character output for debugger.
35 */
36
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD$");
39
40#include "opt_ddb.h"
41
42#include <sys/param.h>
43#include <sys/systm.h>
44#include <sys/cons.h>
45#include <sys/kdb.h>
46#include <sys/kernel.h>
47#include <sys/sysctl.h>
48
49#include <machine/stdarg.h>
50
51#include <ddb/ddb.h>
52#include <ddb/db_output.h>
53
54struct dbputchar_arg {
55	size_t	da_nbufr;
56	size_t	da_remain;
57	char	*da_pbufr;
58	char	*da_pnext;
59};
60
61/*
62 *	Character output - tracks position in line.
63 *	To do this correctly, we should know how wide
64 *	the output device is - then we could zero
65 *	the line position when the output device wraps
66 *	around to the start of the next line.
67 *
68 *	Instead, we count the number of spaces printed
69 *	since the last printing character so that we
70 *	don't print trailing spaces.  This avoids most
71 *	of the wraparounds.
72 */
73static int	db_output_position = 0;		/* output column */
74static int	db_last_non_space = 0;		/* last non-space character */
75db_expr_t	db_tab_stop_width = 8;		/* how wide are tab stops? */
76#define	NEXT_TAB(i) rounddown((i) + db_tab_stop_width, db_tab_stop_width)
77db_expr_t	db_max_width = 79;		/* output line width */
78db_expr_t	db_lines_per_page = 20;		/* lines per page */
79volatile int	db_pager_quit;			/* user requested quit */
80static int	db_newlines;			/* # lines this page */
81static int	db_maxlines;			/* max lines/page when paging */
82static int	ddb_use_printf = 0;
83SYSCTL_INT(_debug, OID_AUTO, ddb_use_printf, CTLFLAG_RW, &ddb_use_printf, 0,
84    "use printf for all ddb output");
85
86static void	db_putc(int c);
87static void	db_puts(const char *str);
88static void	db_putchar(int c, void *arg);
89static void	db_pager(void);
90
91/*
92 * Force pending whitespace.
93 */
94void
95db_force_whitespace(void)
96{
97	int last_print, next_tab;
98
99	last_print = db_last_non_space;
100	while (last_print < db_output_position) {
101	    next_tab = NEXT_TAB(last_print);
102	    if (next_tab <= db_output_position) {
103		while (last_print < next_tab) { /* DON'T send a tab!!! */
104			cnputc(' ');
105			db_capture_writech(' ');
106			last_print++;
107		}
108	    }
109	    else {
110		cnputc(' ');
111		db_capture_writech(' ');
112		last_print++;
113	    }
114	}
115	db_last_non_space = db_output_position;
116}
117
118/*
119 * Output character.  Buffer whitespace.
120 */
121static void
122db_putchar(int c, void *arg)
123{
124	struct dbputchar_arg *dap = arg;
125
126	if (dap->da_pbufr == NULL) {
127		 /* No bufferized output is provided. */
128		db_putc(c);
129	} else {
130		*dap->da_pnext++ = c;
131		dap->da_remain--;
132
133		/* Leave always the buffer 0 terminated. */
134		*dap->da_pnext = '\0';
135
136		/* Check if the buffer needs to be flushed. */
137		if (dap->da_remain < 2 || c == '\n') {
138			db_puts(dap->da_pbufr);
139			dap->da_pnext = dap->da_pbufr;
140			dap->da_remain = dap->da_nbufr;
141			*dap->da_pnext = '\0';
142		}
143	}
144}
145
146static void
147db_putc(int c)
148{
149
150	/*
151	 * If not in the debugger or the user requests it, output data to
152	 * both the console and the message buffer.
153	 */
154	if (!kdb_active || ddb_use_printf) {
155		printf("%c", c);
156		if (!kdb_active)
157			return;
158		if (c == '\r' || c == '\n')
159			db_check_interrupt();
160		if (c == '\n' && db_maxlines > 0) {
161			db_newlines++;
162			if (db_newlines >= db_maxlines)
163				db_pager();
164		}
165		return;
166	}
167
168	/* Otherwise, output data directly to the console. */
169	if (c > ' ' && c <= '~') {
170	    /*
171	     * Printing character.
172	     * If we have spaces to print, print them first.
173	     * Use tabs if possible.
174	     */
175	    db_force_whitespace();
176	    cnputc(c);
177	    db_capture_writech(c);
178	    db_output_position++;
179	    db_last_non_space = db_output_position;
180	}
181	else if (c == '\n') {
182	    /* Newline */
183	    cnputc(c);
184	    db_capture_writech(c);
185	    db_output_position = 0;
186	    db_last_non_space = 0;
187	    db_check_interrupt();
188	    if (db_maxlines > 0) {
189		    db_newlines++;
190		    if (db_newlines >= db_maxlines)
191			    db_pager();
192	    }
193	}
194	else if (c == '\r') {
195	    /* Return */
196	    cnputc(c);
197	    db_capture_writech(c);
198	    db_output_position = 0;
199	    db_last_non_space = 0;
200	    db_check_interrupt();
201	}
202	else if (c == '\t') {
203	    /* assume tabs every 8 positions */
204	    db_output_position = NEXT_TAB(db_output_position);
205	}
206	else if (c == ' ') {
207	    /* space */
208	    db_output_position++;
209	}
210	else if (c == '\007') {
211	    /* bell */
212	    cnputc(c);
213	    /* No need to beep in a log: db_capture_writech(c); */
214	}
215	/* other characters are assumed non-printing */
216}
217
218static void
219db_puts(const char *str)
220{
221	int i;
222
223	for (i = 0; str[i] != '\0'; i++)
224		db_putc(str[i]);
225}
226
227/*
228 * Turn on the pager.
229 */
230void
231db_enable_pager(void)
232{
233	if (db_maxlines == 0) {
234		db_maxlines = db_lines_per_page;
235		db_newlines = 0;
236		db_pager_quit = 0;
237	}
238}
239
240/*
241 * Turn off the pager.
242 */
243void
244db_disable_pager(void)
245{
246	db_maxlines = 0;
247}
248
249/*
250 * A simple paging callout function.  It supports several simple more(1)-like
251 * commands as well as a quit command that sets db_pager_quit which db
252 * commands can poll to see if they should terminate early.
253 */
254void
255db_pager(void)
256{
257	int c, done;
258
259	db_capture_enterpager();
260	db_printf("--More--\r");
261	done = 0;
262	while (!done) {
263		c = cngetc();
264		switch (c) {
265		case 'e':
266		case 'j':
267		case '\n':
268			/* Just one more line. */
269			db_maxlines = 1;
270			done++;
271			break;
272		case 'd':
273			/* Half a page. */
274			db_maxlines = db_lines_per_page / 2;
275			done++;
276			break;
277		case 'f':
278		case ' ':
279			/* Another page. */
280			db_maxlines = db_lines_per_page;
281			done++;
282			break;
283		case 'q':
284		case 'Q':
285		case 'x':
286		case 'X':
287			/* Quit */
288			db_maxlines = 0;
289			db_pager_quit = 1;
290			done++;
291			break;
292#if 0
293			/* FALLTHROUGH */
294		default:
295			cnputc('\007');
296#endif
297		}
298	}
299	db_printf("        ");
300	db_force_whitespace();
301	db_printf("\r");
302	db_newlines = 0;
303	db_capture_exitpager();
304}
305
306/*
307 * Return output position
308 */
309int
310db_print_position(void)
311{
312	return (db_output_position);
313}
314
315/*
316 * Printing
317 */
318int
319db_printf(const char *fmt, ...)
320{
321#ifdef DDB_BUFR_SIZE
322	char bufr[DDB_BUFR_SIZE];
323#endif
324	struct dbputchar_arg dca;
325	va_list	listp;
326	int retval;
327
328#ifdef DDB_BUFR_SIZE
329	dca.da_pbufr = bufr;
330	dca.da_pnext = dca.da_pbufr;
331	dca.da_nbufr = sizeof(bufr);
332	dca.da_remain = sizeof(bufr);
333	*dca.da_pnext = '\0';
334#else
335	dca.da_pbufr = NULL;
336#endif
337
338	va_start(listp, fmt);
339	retval = kvprintf (fmt, db_putchar, &dca, db_radix, listp);
340	va_end(listp);
341
342#ifdef DDB_BUFR_SIZE
343	if (*dca.da_pbufr != '\0')
344		db_puts(dca.da_pbufr);
345#endif
346	return (retval);
347}
348
349int db_indent;
350
351void
352db_iprintf(const char *fmt,...)
353{
354#ifdef DDB_BUFR_SIZE
355	char bufr[DDB_BUFR_SIZE];
356#endif
357	struct dbputchar_arg dca;
358	int i;
359	va_list listp;
360
361	for (i = db_indent; i >= 8; i -= 8)
362		db_printf("\t");
363	while (--i >= 0)
364		db_printf(" ");
365
366#ifdef DDB_BUFR_SIZE
367	dca.da_pbufr = bufr;
368	dca.da_pnext = dca.da_pbufr;
369	dca.da_nbufr = sizeof(bufr);
370	dca.da_remain = sizeof(bufr);
371	*dca.da_pnext = '\0';
372#else
373	dca.da_pbufr = NULL;
374#endif
375
376	va_start(listp, fmt);
377	kvprintf (fmt, db_putchar, &dca, db_radix, listp);
378	va_end(listp);
379
380#ifdef DDB_BUFR_SIZE
381	if (*dca.da_pbufr != '\0')
382		db_puts(dca.da_pbufr);
383#endif
384}
385
386/*
387 * End line if too long.
388 */
389void
390db_end_line(int field_width)
391{
392	if (db_output_position + field_width > db_max_width)
393	    db_printf("\n");
394}
395