[PATCH 02/15] ceph: add BLOG deserialization support
From: Alex Markuze
Date: Wed Jun 17 2026 - 11:04:27 EST
Add blog_des.c implementing the binary record deserialization engine
used by the debugfs read paths. Supports %d, %i, %u, %o, %x, %X,
%s, %p, %c, and length modifiers (l, ll, h, hh, z).
Signed-off-by: Alex Markuze <amarkuze@xxxxxxxxxx>
---
fs/ceph/blog_des.c | 342 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 342 insertions(+)
create mode 100644 fs/ceph/blog_des.c
diff --git a/fs/ceph/blog_des.c b/fs/ceph/blog_des.c
new file mode 100644
index 000000000000..40cef157b888
--- /dev/null
+++ b/fs/ceph/blog_des.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Binary Logging Deserialization
+ */
+
+#include <linux/ceph/blog_des.h>
+#include <linux/ceph/blog.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/printk.h>
+#include <linux/align.h>
+#include <linux/unaligned.h>
+
+/**
+ * blog_des_reconstruct - Reconstructs a formatted string from serialized values
+ * @fmt: Format string containing % specifiers
+ * @buffer: Buffer containing serialized values
+ * @size: Size of the buffer in bytes
+ * @out: Buffer to store the reconstructed string
+ * @out_size: Size of the output buffer
+ *
+ * Return: Number of bytes written to out buffer, or negative error code on failure
+ */
+int blog_des_reconstruct(const char *fmt, const void *buffer,
+ size_t size, char *out, size_t out_size)
+{
+ const char *buf_start = (const char *)buffer;
+ const char *buf_ptr = buf_start;
+ const char *buf_end = buf_start + size;
+ const char *fmt_ptr = fmt;
+ char *out_ptr = out;
+ size_t remaining = out_size - 1; /* Reserve space for null terminator */
+ size_t arg_count = 0;
+ int ret;
+
+ if (!fmt || !buffer || !out || !out_size) {
+ pr_err("blog_des_reconstruct: invalid params fmt=%p buffer=%p out=%p out_size=%zu\n",
+ fmt, buffer, out, out_size);
+ return -EINVAL;
+ }
+
+ *out_ptr = '\0';
+
+ /* Process the format string */
+ while (*fmt_ptr && remaining > 0) {
+ int is_long;
+ int is_long_long;
+ int dyn_width = -1;
+ int dyn_precision = -1;
+
+ if (*fmt_ptr != '%') {
+ /* Copy literal character */
+ *out_ptr++ = *fmt_ptr++;
+ remaining--;
+ continue;
+ }
+
+ /* Skip the '%' */
+ fmt_ptr++;
+
+ /* Handle %% */
+ if (*fmt_ptr == '%') {
+ *out_ptr++ = '%';
+ fmt_ptr++;
+ remaining--;
+ continue;
+ }
+
+ /* Skip flags (-+#0 space) */
+ while (*fmt_ptr && (*fmt_ptr == '-' || *fmt_ptr == '+' || *fmt_ptr == '#' ||
+ *fmt_ptr == '0' || *fmt_ptr == ' ')) {
+ fmt_ptr++;
+ }
+
+ /* Consume field width: digits are format-only, * is serialized */
+ while (*fmt_ptr && (*fmt_ptr >= '0' && *fmt_ptr <= '9'))
+ fmt_ptr++;
+ if (*fmt_ptr == '*') {
+ if (buf_ptr + sizeof(int) > buf_end)
+ return -EFAULT;
+ dyn_width = get_unaligned((int *)buf_ptr);
+ buf_ptr += sizeof(int);
+ fmt_ptr++;
+ }
+
+ /* Consume precision: digits are format-only, * is serialized */
+ if (*fmt_ptr == '.') {
+ fmt_ptr++;
+ while (*fmt_ptr && (*fmt_ptr >= '0' && *fmt_ptr <= '9'))
+ fmt_ptr++;
+ if (*fmt_ptr == '*') {
+ if (buf_ptr + sizeof(int) > buf_end)
+ return -EFAULT;
+ dyn_precision = get_unaligned((int *)buf_ptr);
+ buf_ptr += sizeof(int);
+ fmt_ptr++;
+ }
+ }
+
+ /*
+ * dyn_width: consumed from the buffer to keep offsets
+ * aligned; output width-padding is not implemented.
+ */
+ (void)dyn_width;
+
+ /* Parse length modifiers (l, ll, h, hh, z) */
+ is_long = 0;
+ is_long_long = 0;
+
+ if (*fmt_ptr == 'l') {
+ fmt_ptr++;
+ is_long = 1;
+ if (*fmt_ptr == 'l') {
+ fmt_ptr++;
+ is_long_long = 1;
+ is_long = 0;
+ }
+ } else if (*fmt_ptr == 'h') {
+ fmt_ptr++;
+ if (*fmt_ptr == 'h')
+ fmt_ptr++;
+ } else if (*fmt_ptr == 'z') {
+ fmt_ptr++;
+ if (sizeof(size_t) == sizeof(long long))
+ is_long_long = 1;
+ else
+ is_long = 1;
+ }
+
+ /* Parse and handle format specifier */
+ switch (*fmt_ptr) {
+ case 's': {
+ const char *str;
+ size_t str_len;
+ size_t out_len;
+ size_t max_scan_len;
+
+ if (buf_ptr >= buf_end) {
+ pr_err("blog_des_reconstruct: string arg %zu overruns buffer (no space)\n",
+ arg_count);
+ return -EFAULT;
+ }
+
+ str = buf_ptr;
+ max_scan_len = buf_end - buf_ptr;
+
+ str_len = strnlen(str, max_scan_len);
+ if (str_len == max_scan_len && str[str_len - 1] != '\0') {
+ pr_err("blog_des_reconstruct: unterminated string at arg %zu (fmt=%s)\n",
+ arg_count, fmt);
+ return -EFAULT;
+ }
+
+ buf_ptr += round_up(str_len + 1, 4);
+ if (buf_ptr > buf_end) {
+ pr_err("blog_des_reconstruct: string arg %zu overruns buffer after copy (fmt=%s)\n",
+ arg_count, fmt);
+ return -EFAULT;
+ }
+
+ out_len = str_len;
+ if (dyn_precision >= 0 && (size_t)dyn_precision < out_len)
+ out_len = (size_t)dyn_precision;
+ if (out_len > remaining)
+ out_len = remaining;
+ memcpy(out_ptr, str, out_len);
+ out_ptr += out_len;
+ remaining -= out_len;
+ break;
+ }
+ case 'd':
+ case 'i': {
+ if (is_long_long) {
+ long long val;
+
+ if (buf_ptr + sizeof(long long) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((long long *)buf_ptr);
+ buf_ptr += sizeof(long long);
+ ret = snprintf(out_ptr, remaining, "%lld", val);
+ } else if (is_long) {
+ long val;
+
+ if (buf_ptr + sizeof(long) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((long *)buf_ptr);
+ buf_ptr += sizeof(long);
+ ret = snprintf(out_ptr, remaining, "%ld", val);
+ } else {
+ int val;
+
+ if (buf_ptr + sizeof(int) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((int *)buf_ptr);
+ buf_ptr += sizeof(int);
+ ret = snprintf(out_ptr, remaining, "%d", val);
+ }
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+ case 'u': {
+ if (is_long_long) {
+ unsigned long long val;
+
+ if (buf_ptr + sizeof(unsigned long long) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((unsigned long long *)buf_ptr);
+ buf_ptr += sizeof(unsigned long long);
+ ret = snprintf(out_ptr, remaining, "%llu", val);
+ } else if (is_long) {
+ unsigned long val;
+
+ if (buf_ptr + sizeof(unsigned long) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((unsigned long *)buf_ptr);
+ buf_ptr += sizeof(unsigned long);
+ ret = snprintf(out_ptr, remaining, "%lu", val);
+ } else {
+ unsigned int val;
+
+ if (buf_ptr + sizeof(unsigned int) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((unsigned int *)buf_ptr);
+ buf_ptr += sizeof(unsigned int);
+ ret = snprintf(out_ptr, remaining, "%u", val);
+ }
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+ case 'o':
+ case 'x':
+ case 'X': {
+ const char *num_fmt;
+
+ if (*fmt_ptr == 'o')
+ num_fmt = is_long_long ? "%llo" : is_long ? "%lo" : "%o";
+ else if (*fmt_ptr == 'x')
+ num_fmt = is_long_long ? "%llx" : is_long ? "%lx" : "%x";
+ else
+ num_fmt = is_long_long ? "%llX" : is_long ? "%lX" : "%X";
+
+ if (is_long_long) {
+ unsigned long long val;
+
+ if (buf_ptr + sizeof(unsigned long long) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((unsigned long long *)buf_ptr);
+ buf_ptr += sizeof(unsigned long long);
+ ret = snprintf(out_ptr, remaining, num_fmt, val);
+ } else if (is_long) {
+ unsigned long val;
+
+ if (buf_ptr + sizeof(unsigned long) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((unsigned long *)buf_ptr);
+ buf_ptr += sizeof(unsigned long);
+ ret = snprintf(out_ptr, remaining, num_fmt, val);
+ } else {
+ unsigned int val;
+
+ if (buf_ptr + sizeof(unsigned int) > buf_end)
+ return -EFAULT;
+ val = get_unaligned((unsigned int *)buf_ptr);
+ buf_ptr += sizeof(unsigned int);
+ ret = snprintf(out_ptr, remaining, num_fmt, val);
+ }
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+ case 'p': {
+ void *ptr;
+
+ if (buf_ptr + sizeof(void *) > buf_end)
+ return -EFAULT;
+
+ ptr = (void *)(unsigned long)get_unaligned((unsigned long *)buf_ptr);
+ buf_ptr += sizeof(void *);
+
+ /*
+ * Skip kernel %p sub-specifiers (U, I, d, D, etc.).
+ * bout/boutc do not support %p extensions; call sites
+ * must pre-format them with snprintf and pass %s.
+ */
+ while (fmt_ptr[1] && isalpha(fmt_ptr[1]))
+ fmt_ptr++;
+
+ ret = snprintf(out_ptr, remaining, "%p", ptr);
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+ case 'c': {
+ char val;
+
+ if (buf_ptr + sizeof(int) > buf_end)
+ return -EFAULT;
+
+ val = (char)get_unaligned((int *)buf_ptr);
+ buf_ptr += sizeof(int);
+
+ if (remaining > 0) {
+ *out_ptr++ = val;
+ remaining--;
+ }
+ break;
+ }
+ default:
+ pr_err("%s: unsupported format specifier '%%%c' at argument %zu\n",
+ __func__, *fmt_ptr, arg_count);
+ return -EINVAL;
+ }
+
+ fmt_ptr++;
+ arg_count++;
+ }
+
+ /* Null-terminate the output */
+ *out_ptr = '\0';
+
+ return out_ptr - out;
+}
--
2.34.1