1  // SPDX-License-Identifier: LGPL-2.1+
2  /* Copyright (C) 2022 Kent Overstreet */
3  
4  #include <linux/bitmap.h>
5  #include <linux/err.h>
6  #include <linux/export.h>
7  #include <linux/kernel.h>
8  #include <linux/slab.h>
9  #include <linux/string_helpers.h>
10  
11  #include "printbuf.h"
12  
__printbuf_linelen(struct printbuf * buf,unsigned pos)13  static inline unsigned __printbuf_linelen(struct printbuf *buf, unsigned pos)
14  {
15  	return pos - buf->last_newline;
16  }
17  
printbuf_linelen(struct printbuf * buf)18  static inline unsigned printbuf_linelen(struct printbuf *buf)
19  {
20  	return __printbuf_linelen(buf, buf->pos);
21  }
22  
23  /*
24   * Returns spaces from start of line, if set, or 0 if unset:
25   */
cur_tabstop(struct printbuf * buf)26  static inline unsigned cur_tabstop(struct printbuf *buf)
27  {
28  	return buf->cur_tabstop < buf->nr_tabstops
29  		? buf->_tabstops[buf->cur_tabstop]
30  		: 0;
31  }
32  
bch2_printbuf_make_room(struct printbuf * out,unsigned extra)33  int bch2_printbuf_make_room(struct printbuf *out, unsigned extra)
34  {
35  	/* Reserved space for terminating nul: */
36  	extra += 1;
37  
38  	if (out->pos + extra <= out->size)
39  		return 0;
40  
41  	if (!out->heap_allocated) {
42  		out->overflow = true;
43  		return 0;
44  	}
45  
46  	unsigned new_size = roundup_pow_of_two(out->size + extra);
47  
48  	/* Sanity check... */
49  	if (new_size > PAGE_SIZE << MAX_PAGE_ORDER) {
50  		out->allocation_failure = true;
51  		out->overflow = true;
52  		return -ENOMEM;
53  	}
54  
55  	/*
56  	 * Note: output buffer must be freeable with kfree(), it's not required
57  	 * that the user use printbuf_exit().
58  	 */
59  	char *buf = krealloc(out->buf, new_size, !out->atomic ? GFP_KERNEL : GFP_NOWAIT);
60  
61  	if (!buf) {
62  		out->allocation_failure = true;
63  		out->overflow = true;
64  		return -ENOMEM;
65  	}
66  
67  	out->buf	= buf;
68  	out->size	= new_size;
69  	return 0;
70  }
71  
printbuf_advance_pos(struct printbuf * out,unsigned len)72  static void printbuf_advance_pos(struct printbuf *out, unsigned len)
73  {
74  	out->pos += min(len, printbuf_remaining(out));
75  }
76  
printbuf_insert_spaces(struct printbuf * out,unsigned pos,unsigned nr)77  static void printbuf_insert_spaces(struct printbuf *out, unsigned pos, unsigned nr)
78  {
79  	unsigned move = out->pos - pos;
80  
81  	bch2_printbuf_make_room(out, nr);
82  
83  	if (pos + nr < out->size)
84  		memmove(out->buf + pos + nr,
85  			out->buf + pos,
86  			min(move, out->size - 1 - pos - nr));
87  
88  	if (pos < out->size)
89  		memset(out->buf + pos, ' ', min(nr, out->size - pos));
90  
91  	printbuf_advance_pos(out, nr);
92  	printbuf_nul_terminate_reserved(out);
93  }
94  
__printbuf_do_indent(struct printbuf * out,unsigned pos)95  static void __printbuf_do_indent(struct printbuf *out, unsigned pos)
96  {
97  	while (true) {
98  		int pad;
99  		unsigned len = out->pos - pos;
100  		char *p = out->buf + pos;
101  		char *n = memscan(p, '\n', len);
102  		if (cur_tabstop(out)) {
103  			n = min(n, (char *) memscan(p, '\r', len));
104  			n = min(n, (char *) memscan(p, '\t', len));
105  		}
106  
107  		pos = n - out->buf;
108  		if (pos == out->pos)
109  			break;
110  
111  		switch (*n) {
112  		case '\n':
113  			pos++;
114  			out->last_newline = pos;
115  
116  			printbuf_insert_spaces(out, pos, out->indent);
117  
118  			pos = min(pos + out->indent, out->pos);
119  			out->last_field = pos;
120  			out->cur_tabstop = 0;
121  			break;
122  		case '\r':
123  			memmove(n, n + 1, out->pos - pos);
124  			--out->pos;
125  			pad = (int) cur_tabstop(out) - (int) __printbuf_linelen(out, pos);
126  			if (pad > 0) {
127  				printbuf_insert_spaces(out, out->last_field, pad);
128  				pos += pad;
129  			}
130  
131  			out->last_field = pos;
132  			out->cur_tabstop++;
133  			break;
134  		case '\t':
135  			pad = (int) cur_tabstop(out) - (int) __printbuf_linelen(out, pos) - 1;
136  			if (pad > 0) {
137  				*n = ' ';
138  				printbuf_insert_spaces(out, pos, pad - 1);
139  				pos += pad;
140  			} else {
141  				memmove(n, n + 1, out->pos - pos);
142  				--out->pos;
143  			}
144  
145  			out->last_field = pos;
146  			out->cur_tabstop++;
147  			break;
148  		}
149  	}
150  }
151  
printbuf_do_indent(struct printbuf * out,unsigned pos)152  static inline void printbuf_do_indent(struct printbuf *out, unsigned pos)
153  {
154  	if (out->has_indent_or_tabstops && !out->suppress_indent_tabstop_handling)
155  		__printbuf_do_indent(out, pos);
156  }
157  
bch2_prt_vprintf(struct printbuf * out,const char * fmt,va_list args)158  void bch2_prt_vprintf(struct printbuf *out, const char *fmt, va_list args)
159  {
160  	int len;
161  
162  	do {
163  		va_list args2;
164  
165  		va_copy(args2, args);
166  		len = vsnprintf(out->buf + out->pos, printbuf_remaining_size(out), fmt, args2);
167  		va_end(args2);
168  	} while (len > printbuf_remaining(out) &&
169  		 !bch2_printbuf_make_room(out, len));
170  
171  	unsigned indent_pos = out->pos;
172  	printbuf_advance_pos(out, len);
173  	printbuf_do_indent(out, indent_pos);
174  }
175  
bch2_prt_printf(struct printbuf * out,const char * fmt,...)176  void bch2_prt_printf(struct printbuf *out, const char *fmt, ...)
177  {
178  	va_list args;
179  	int len;
180  
181  	do {
182  		va_start(args, fmt);
183  		len = vsnprintf(out->buf + out->pos, printbuf_remaining_size(out), fmt, args);
184  		va_end(args);
185  	} while (len > printbuf_remaining(out) &&
186  		 !bch2_printbuf_make_room(out, len));
187  
188  	unsigned indent_pos = out->pos;
189  	printbuf_advance_pos(out, len);
190  	printbuf_do_indent(out, indent_pos);
191  }
192  
193  /**
194   * bch2_printbuf_str() - returns printbuf's buf as a C string, guaranteed to be
195   * null terminated
196   * @buf:	printbuf to terminate
197   * Returns:	Printbuf contents, as a nul terminated C string
198   */
bch2_printbuf_str(const struct printbuf * buf)199  const char *bch2_printbuf_str(const struct printbuf *buf)
200  {
201  	/*
202  	 * If we've written to a printbuf then it's guaranteed to be a null
203  	 * terminated string - but if we haven't, then we might not have
204  	 * allocated a buffer at all:
205  	 */
206  	return buf->pos
207  		? buf->buf
208  		: "";
209  }
210  
211  /**
212   * bch2_printbuf_exit() - exit a printbuf, freeing memory it owns and poisoning it
213   * against accidental use.
214   * @buf:	printbuf to exit
215   */
bch2_printbuf_exit(struct printbuf * buf)216  void bch2_printbuf_exit(struct printbuf *buf)
217  {
218  	if (buf->heap_allocated) {
219  		kfree(buf->buf);
220  		buf->buf = ERR_PTR(-EINTR); /* poison value */
221  	}
222  }
223  
bch2_printbuf_tabstops_reset(struct printbuf * buf)224  void bch2_printbuf_tabstops_reset(struct printbuf *buf)
225  {
226  	buf->nr_tabstops = 0;
227  }
228  
bch2_printbuf_tabstop_pop(struct printbuf * buf)229  void bch2_printbuf_tabstop_pop(struct printbuf *buf)
230  {
231  	if (buf->nr_tabstops)
232  		--buf->nr_tabstops;
233  }
234  
235  /*
236   * bch2_printbuf_tabstop_set() - add a tabstop, n spaces from the previous tabstop
237   *
238   * @buf: printbuf to control
239   * @spaces: number of spaces from previous tabpstop
240   *
241   * In the future this function may allocate memory if setting more than
242   * PRINTBUF_INLINE_TABSTOPS or setting tabstops more than 255 spaces from start
243   * of line.
244   */
bch2_printbuf_tabstop_push(struct printbuf * buf,unsigned spaces)245  int bch2_printbuf_tabstop_push(struct printbuf *buf, unsigned spaces)
246  {
247  	unsigned prev_tabstop = buf->nr_tabstops
248  		? buf->_tabstops[buf->nr_tabstops - 1]
249  		: 0;
250  
251  	if (WARN_ON(buf->nr_tabstops >= ARRAY_SIZE(buf->_tabstops)))
252  		return -EINVAL;
253  
254  	buf->_tabstops[buf->nr_tabstops++] = prev_tabstop + spaces;
255  	buf->has_indent_or_tabstops = true;
256  	return 0;
257  }
258  
259  /**
260   * bch2_printbuf_indent_add() - add to the current indent level
261   *
262   * @buf: printbuf to control
263   * @spaces: number of spaces to add to the current indent level
264   *
265   * Subsequent lines, and the current line if the output position is at the start
266   * of the current line, will be indented by @spaces more spaces.
267   */
bch2_printbuf_indent_add(struct printbuf * buf,unsigned spaces)268  void bch2_printbuf_indent_add(struct printbuf *buf, unsigned spaces)
269  {
270  	if (WARN_ON_ONCE(buf->indent + spaces < buf->indent))
271  		spaces = 0;
272  
273  	buf->indent += spaces;
274  	prt_chars(buf, ' ', spaces);
275  
276  	buf->has_indent_or_tabstops = true;
277  }
278  
279  /**
280   * bch2_printbuf_indent_sub() - subtract from the current indent level
281   *
282   * @buf: printbuf to control
283   * @spaces: number of spaces to subtract from the current indent level
284   *
285   * Subsequent lines, and the current line if the output position is at the start
286   * of the current line, will be indented by @spaces less spaces.
287   */
bch2_printbuf_indent_sub(struct printbuf * buf,unsigned spaces)288  void bch2_printbuf_indent_sub(struct printbuf *buf, unsigned spaces)
289  {
290  	if (WARN_ON_ONCE(spaces > buf->indent))
291  		spaces = buf->indent;
292  
293  	if (buf->last_newline + buf->indent == buf->pos) {
294  		buf->pos -= spaces;
295  		printbuf_nul_terminate(buf);
296  	}
297  	buf->indent -= spaces;
298  
299  	if (!buf->indent && !buf->nr_tabstops)
300  		buf->has_indent_or_tabstops = false;
301  }
302  
bch2_prt_newline(struct printbuf * buf)303  void bch2_prt_newline(struct printbuf *buf)
304  {
305  	bch2_printbuf_make_room(buf, 1 + buf->indent);
306  
307  	__prt_char_reserved(buf, '\n');
308  
309  	buf->last_newline	= buf->pos;
310  
311  	__prt_chars_reserved(buf, ' ', buf->indent);
312  
313  	printbuf_nul_terminate_reserved(buf);
314  
315  	buf->last_field		= buf->pos;
316  	buf->cur_tabstop	= 0;
317  }
318  
bch2_printbuf_strip_trailing_newline(struct printbuf * out)319  void bch2_printbuf_strip_trailing_newline(struct printbuf *out)
320  {
321  	for (int p = out->pos - 1; p >= 0; --p) {
322  		if (out->buf[p] == '\n') {
323  			out->pos = p;
324  			break;
325  		}
326  		if (out->buf[p] != ' ')
327  			break;
328  	}
329  
330  	printbuf_nul_terminate_reserved(out);
331  }
332  
__prt_tab(struct printbuf * out)333  static void __prt_tab(struct printbuf *out)
334  {
335  	int spaces = max_t(int, 0, cur_tabstop(out) - printbuf_linelen(out));
336  
337  	prt_chars(out, ' ', spaces);
338  
339  	out->last_field = out->pos;
340  	out->cur_tabstop++;
341  }
342  
343  /**
344   * bch2_prt_tab() - Advance printbuf to the next tabstop
345   * @out:	printbuf to control
346   *
347   * Advance output to the next tabstop by printing spaces.
348   */
bch2_prt_tab(struct printbuf * out)349  void bch2_prt_tab(struct printbuf *out)
350  {
351  	if (WARN_ON(!cur_tabstop(out)))
352  		return;
353  
354  	__prt_tab(out);
355  }
356  
__prt_tab_rjust(struct printbuf * buf)357  static void __prt_tab_rjust(struct printbuf *buf)
358  {
359  	int pad = (int) cur_tabstop(buf) - (int) printbuf_linelen(buf);
360  	if (pad > 0)
361  		printbuf_insert_spaces(buf, buf->last_field, pad);
362  
363  	buf->last_field = buf->pos;
364  	buf->cur_tabstop++;
365  }
366  
367  /**
368   * bch2_prt_tab_rjust - Advance printbuf to the next tabstop, right justifying
369   * previous output
370   *
371   * @buf: printbuf to control
372   *
373   * Advance output to the next tabstop by inserting spaces immediately after the
374   * previous tabstop, right justifying previously outputted text.
375   */
bch2_prt_tab_rjust(struct printbuf * buf)376  void bch2_prt_tab_rjust(struct printbuf *buf)
377  {
378  	if (WARN_ON(!cur_tabstop(buf)))
379  		return;
380  
381  	__prt_tab_rjust(buf);
382  }
383  
384  /**
385   * bch2_prt_bytes_indented() - Print an array of chars, handling embedded control characters
386   *
387   * @out:	output printbuf
388   * @str:	string to print
389   * @count:	number of bytes to print
390   *
391   * The following contol characters are handled as so:
392   *   \n: prt_newline	newline that obeys current indent level
393   *   \t: prt_tab	advance to next tabstop
394   *   \r: prt_tab_rjust	advance to next tabstop, with right justification
395   */
bch2_prt_bytes_indented(struct printbuf * out,const char * str,unsigned count)396  void bch2_prt_bytes_indented(struct printbuf *out, const char *str, unsigned count)
397  {
398  	unsigned indent_pos = out->pos;
399  	prt_bytes(out, str, count);
400  	printbuf_do_indent(out, indent_pos);
401  }
402  
403  /**
404   * bch2_prt_human_readable_u64() - Print out a u64 in human readable units
405   * @out:	output printbuf
406   * @v:		integer to print
407   *
408   * Units of 2^10 (default) or 10^3 are controlled via @out->si_units
409   */
bch2_prt_human_readable_u64(struct printbuf * out,u64 v)410  void bch2_prt_human_readable_u64(struct printbuf *out, u64 v)
411  {
412  	bch2_printbuf_make_room(out, 10);
413  	unsigned len = string_get_size(v, 1, !out->si_units,
414  				       out->buf + out->pos,
415  				       printbuf_remaining_size(out));
416  	printbuf_advance_pos(out, len);
417  }
418  
419  /**
420   * bch2_prt_human_readable_s64() - Print out a s64 in human readable units
421   * @out:	output printbuf
422   * @v:		integer to print
423   *
424   * Units of 2^10 (default) or 10^3 are controlled via @out->si_units
425   */
bch2_prt_human_readable_s64(struct printbuf * out,s64 v)426  void bch2_prt_human_readable_s64(struct printbuf *out, s64 v)
427  {
428  	if (v < 0)
429  		prt_char(out, '-');
430  	bch2_prt_human_readable_u64(out, abs(v));
431  }
432  
433  /**
434   * bch2_prt_units_u64() - Print out a u64 according to printbuf unit options
435   * @out:	output printbuf
436   * @v:		integer to print
437   *
438   * Units are either raw (default), or human reabable units (controlled via
439   * @buf->human_readable_units)
440   */
bch2_prt_units_u64(struct printbuf * out,u64 v)441  void bch2_prt_units_u64(struct printbuf *out, u64 v)
442  {
443  	if (out->human_readable_units)
444  		bch2_prt_human_readable_u64(out, v);
445  	else
446  		bch2_prt_printf(out, "%llu", v);
447  }
448  
449  /**
450   * bch2_prt_units_s64() - Print out a s64 according to printbuf unit options
451   * @out:	output printbuf
452   * @v:		integer to print
453   *
454   * Units are either raw (default), or human reabable units (controlled via
455   * @buf->human_readable_units)
456   */
bch2_prt_units_s64(struct printbuf * out,s64 v)457  void bch2_prt_units_s64(struct printbuf *out, s64 v)
458  {
459  	if (v < 0)
460  		prt_char(out, '-');
461  	bch2_prt_units_u64(out, abs(v));
462  }
463  
bch2_prt_string_option(struct printbuf * out,const char * const list[],size_t selected)464  void bch2_prt_string_option(struct printbuf *out,
465  			    const char * const list[],
466  			    size_t selected)
467  {
468  	for (size_t i = 0; list[i]; i++)
469  		bch2_prt_printf(out, i == selected ? "[%s] " : "%s ", list[i]);
470  }
471  
bch2_prt_bitflags(struct printbuf * out,const char * const list[],u64 flags)472  void bch2_prt_bitflags(struct printbuf *out,
473  		       const char * const list[], u64 flags)
474  {
475  	unsigned bit, nr = 0;
476  	bool first = true;
477  
478  	while (list[nr])
479  		nr++;
480  
481  	while (flags && (bit = __ffs64(flags)) < nr) {
482  		if (!first)
483  			bch2_prt_printf(out, ",");
484  		first = false;
485  		bch2_prt_printf(out, "%s", list[bit]);
486  		flags ^= BIT_ULL(bit);
487  	}
488  }
489  
bch2_prt_bitflags_vector(struct printbuf * out,const char * const list[],unsigned long * v,unsigned nr)490  void bch2_prt_bitflags_vector(struct printbuf *out,
491  			      const char * const list[],
492  			      unsigned long *v, unsigned nr)
493  {
494  	bool first = true;
495  	unsigned i;
496  
497  	for (i = 0; i < nr; i++)
498  		if (!list[i]) {
499  			nr = i - 1;
500  			break;
501  		}
502  
503  	for_each_set_bit(i, v, nr) {
504  		if (!first)
505  			bch2_prt_printf(out, ",");
506  		first = false;
507  		bch2_prt_printf(out, "%s", list[i]);
508  	}
509  }
510