/* SPDX-License-Identifier: LGPL-2.1+ */
/* Copyright (C) 2022 Kent Overstreet */

#ifndef _BCACHEFS_PRINTBUF_H
#define _BCACHEFS_PRINTBUF_H

/*
 * Printbufs: Simple strings for printing to, with optional heap allocation
 *
 * This code has provisions for use in userspace, to aid in making other code
 * portable between kernelspace and userspace.
 *
 * Basic example:
 *   struct printbuf buf = PRINTBUF;
 *
 *   prt_printf(&buf, "foo=");
 *   foo_to_text(&buf, foo);
 *   printk("%s", buf.buf);
 *   printbuf_exit(&buf);
 *
 * Or
 *   struct printbuf buf = PRINTBUF_EXTERN(char_buf, char_buf_size)
 *
 * We can now write pretty printers instead of writing code that dumps
 * everything to the kernel log buffer, and then those pretty-printers can be
 * used by other code that outputs to kernel log, sysfs, debugfs, etc.
 *
 * Memory allocation: Outputing to a printbuf may allocate memory. This
 * allocation is done with GFP_KERNEL, by default: use the newer
 * memalloc_*_(save|restore) functions as needed.
 *
 * Since no equivalent yet exists for GFP_ATOMIC/GFP_NOWAIT, memory allocations
 * will be done with GFP_NOWAIT if printbuf->atomic is nonzero.
 *
 * It's allowed to grab the output buffer and free it later with kfree() instead
 * of using printbuf_exit(), if the user just needs a heap allocated string at
 * the end.
 *
 * Memory allocation failures: We don't return errors directly, because on
 * memory allocation failure we usually don't want to bail out and unwind - we
 * want to print what we've got, on a best-effort basis. But code that does want
 * to return -ENOMEM may check printbuf.allocation_failure.
 *
 * Indenting, tabstops:
 *
 * To aid is writing multi-line pretty printers spread across multiple
 * functions, printbufs track the current indent level.
 *
 * printbuf_indent_push() and printbuf_indent_pop() increase and decrease the current indent
 * level, respectively.
 *
 * To use tabstops, set printbuf->tabstops[]; they are in units of spaces, from
 * start of line. Once set, prt_tab() will output spaces up to the next tabstop.
 * prt_tab_rjust() will also advance the current line of text up to the next
 * tabstop, but it does so by shifting text since the previous tabstop up to the
 * next tabstop - right justifying it.
 *
 * Make sure you use prt_newline() instead of \n in the format string for indent
 * level and tabstops to work corretly.
 *
 * Output units: printbuf->units exists to tell pretty-printers how to output
 * numbers: a raw value (e.g. directly from a superblock field), as bytes, or as
 * human readable bytes. prt_units() obeys it.
 */

#include <linux/kernel.h>
#include <linux/string.h>

enum printbuf_si {
	PRINTBUF_UNITS_2,	/* use binary powers of 2^10 */
	PRINTBUF_UNITS_10,	/* use powers of 10^3 (standard SI) */
};

#define PRINTBUF_INLINE_TABSTOPS	8

struct printbuf {
	char			*buf;
	unsigned		size;
	unsigned		pos;
	unsigned		last_newline;
	unsigned		last_field;
	unsigned		indent;
	/*
	 * If nonzero, allocations will be done with GFP_ATOMIC:
	 */
	u8			atomic;
	bool			allocation_failure:1;
	bool			heap_allocated:1;
	bool			overflow:1;
	bool			suppress:1; /* Ratelimited or already printed */
	enum printbuf_si	si_units:1;
	bool			human_readable_units:1;
	bool			has_indent_or_tabstops:1;
	bool			may_vmalloc:1;
	u8			nr_tabstops;

	/*
	 * Do not modify directly: use printbuf_tabstop_add(),
	 * printbuf_tabstop_get()
	 */
	u8			cur_tabstop;
	u8			_tabstops[PRINTBUF_INLINE_TABSTOPS];
};

struct printbuf_restore {
	unsigned		pos;
	u8			cur_tabstop;
};

static inline struct printbuf_restore printbuf_state_save(struct printbuf *buf)
{
	return (struct printbuf_restore) {
		.pos = buf->pos, .cur_tabstop = buf->cur_tabstop
	};
}

static inline void printbuf_state_restore(struct printbuf *buf,
					  struct printbuf_restore s)
{
	buf->pos		= s.pos;
	buf->cur_tabstop	= s.cur_tabstop;
}

int bch2_printbuf_make_room_gfp(struct printbuf *, unsigned, gfp_t);
int bch2_printbuf_make_room(struct printbuf *, unsigned);
__printf(2, 3) void bch2_prt_printf(struct printbuf *out, const char *fmt, ...);
__printf(2, 0) void bch2_prt_vprintf(struct printbuf *out, const char *fmt, va_list);
const char *bch2_printbuf_str(const struct printbuf *);
void bch2_printbuf_exit(struct printbuf *);

void bch2_printbuf_tabstops_reset(struct printbuf *);
void bch2_printbuf_tabstop_pop(struct printbuf *);
int bch2_printbuf_tabstop_push(struct printbuf *, unsigned);

void bch2_printbuf_indent_add(struct printbuf *, unsigned);
void bch2_printbuf_indent_add_nextline(struct printbuf *, unsigned);
void bch2_printbuf_indent_sub(struct printbuf *, unsigned);

void bch2_prt_newline(struct printbuf *);
void bch2_printbuf_strip_trailing_newline(struct printbuf *);
void bch2_prt_tab(struct printbuf *);
void bch2_prt_tab_rjust(struct printbuf *);

void bch2_prt_bytes_indented(struct printbuf *, const char *, unsigned);
void bch2_prt_human_readable_u64(struct printbuf *, u64);
void bch2_prt_human_readable_s64(struct printbuf *, s64);
void bch2_prt_units_u64(struct printbuf *, u64);
void bch2_prt_units_s64(struct printbuf *, s64);
void bch2_prt_string_option(struct printbuf *, const char * const[], size_t);
void bch2_prt_bitflags(struct printbuf *, const char * const[], u64);
void bch2_prt_bitflags_vector(struct printbuf *, const char * const[],
			      unsigned long *, unsigned);

/* Initializer for a heap allocated printbuf: */
#define PRINTBUF ((struct printbuf) { .heap_allocated = true })

/* Initializer a printbuf that points to an external buffer: */
#define PRINTBUF_EXTERN(_buf, _size)			\
((struct printbuf) {					\
	.buf	= _buf,					\
	.size	= _size,				\
})

static inline struct printbuf bch2_printbuf_init(void)
{
	return PRINTBUF;
}

DEFINE_CLASS(printbuf, struct printbuf,
	     bch2_printbuf_exit(&_T), bch2_printbuf_init(), void)

/*
 * Returns size remaining of output buffer:
 */
static inline unsigned printbuf_remaining_size(struct printbuf *out)
{
	if (WARN_ON(out->size && out->pos >= out->size))
		out->pos = out->size - 1;
	return out->size - out->pos;
}

/*
 * Returns number of characters we can print to the output buffer - i.e.
 * excluding the terminating nul:
 */
static inline unsigned printbuf_remaining(struct printbuf *out)
{
	return out->size ? printbuf_remaining_size(out) - 1 : 0;
}

static inline unsigned printbuf_written(struct printbuf *out)
{
	return out->size ? min(out->pos, out->size - 1) : 0;
}

static inline void printbuf_nul_terminate_reserved(struct printbuf *out)
{
	if (WARN_ON(out->size && out->pos >= out->size))
		out->pos = out->size - 1;
	if (out->size)
		out->buf[out->pos] = 0;
}

static inline void printbuf_nul_terminate(struct printbuf *out)
{
	bch2_printbuf_make_room(out, 1);
	printbuf_nul_terminate_reserved(out);
}

/* Doesn't call bch2_printbuf_make_room(), doesn't nul terminate: */
static inline void __prt_char_reserved(struct printbuf *out, char c)
{
	if (printbuf_remaining(out))
		out->buf[out->pos++] = c;
}

/* Doesn't nul terminate: */
static inline void __prt_char(struct printbuf *out, char c)
{
	bch2_printbuf_make_room(out, 1);
	__prt_char_reserved(out, c);
}

static inline void prt_char(struct printbuf *out, char c)
{
	bch2_printbuf_make_room(out, 2);
	__prt_char_reserved(out, c);
	printbuf_nul_terminate_reserved(out);
}

static inline void __prt_chars_reserved(struct printbuf *out, char c, unsigned n)
{
	unsigned can_print = min(n, printbuf_remaining(out));

	for (unsigned i = 0; i < can_print; i++)
		out->buf[out->pos++] = c;
}

static inline void prt_chars(struct printbuf *out, char c, unsigned n)
{
	bch2_printbuf_make_room(out, n);
	__prt_chars_reserved(out, c, n);
	printbuf_nul_terminate_reserved(out);
}

static inline void prt_bytes(struct printbuf *out, const void *b, unsigned n)
{
	bch2_printbuf_make_room(out, n);

	unsigned can_print = min(n, printbuf_remaining(out));

	for (unsigned i = 0; i < can_print; i++)
		out->buf[out->pos++] = ((char *) b)[i];

	printbuf_nul_terminate(out);
}

static inline void prt_str(struct printbuf *out, const char *str)
{
	bch2_prt_bytes_indented(out, str, strlen(str));
}

static inline void prt_hex_byte(struct printbuf *out, u8 byte)
{
	bch2_printbuf_make_room(out, 3);
	__prt_char_reserved(out, hex_asc_hi(byte));
	__prt_char_reserved(out, hex_asc_lo(byte));
	printbuf_nul_terminate_reserved(out);
}

static inline void prt_hex_byte_upper(struct printbuf *out, u8 byte)
{
	bch2_printbuf_make_room(out, 3);
	__prt_char_reserved(out, hex_asc_upper_hi(byte));
	__prt_char_reserved(out, hex_asc_upper_lo(byte));
	printbuf_nul_terminate_reserved(out);
}

static inline void printbuf_reset_keep_tabstops(struct printbuf *buf)
{
	buf->pos		= 0;
	buf->allocation_failure	= 0;
	buf->last_newline	= 0;
	buf->last_field		= 0;
	buf->indent		= 0;
	buf->cur_tabstop	= 0;

	if (buf->size)
		printbuf_nul_terminate_reserved(buf);
}

/**
 * printbuf_reset - re-use a printbuf without freeing and re-initializing it:
 */
static inline void printbuf_reset(struct printbuf *buf)
{
	printbuf_reset_keep_tabstops(buf);
	buf->nr_tabstops	= 0;
}

/**
 * printbuf_atomic_inc - mark as entering an atomic section
 */
static inline void printbuf_atomic_inc(struct printbuf *buf)
{
	buf->atomic++;
}

/**
 * printbuf_atomic_inc - mark as leaving an atomic section
 */
static inline void printbuf_atomic_dec(struct printbuf *buf)
{
	buf->atomic--;
}

DEFINE_GUARD(printbuf_atomic, struct printbuf *,
	     printbuf_atomic_inc(_T),
	     printbuf_atomic_dec(_T));

DEFINE_GUARD(printbuf_indent, struct printbuf *,
	     bch2_printbuf_indent_add(_T, 2),
	     bch2_printbuf_indent_sub(_T, 2));

DEFINE_GUARD(printbuf_indent_nextline, struct printbuf *,
	     bch2_printbuf_indent_add_nextline(_T, 2),
	     bch2_printbuf_indent_sub(_T, 2));

#endif /* _BCACHEFS_PRINTBUF_H */
