#include <linux/seq_file.h>
#include <linux/math64.h>
#include <linux/ktime.h>
+#include <linux/jiffies.h>
+#include <linux/timekeeping.h>
+#include <linux/rtc.h>
+#include <linux/printk.h>
+#include <linux/time.h>
+#include <linux/time_types.h>
#include <linux/ceph/libceph.h>
+#include <linux/ceph/ceph_san_logger.h>
#include <linux/ceph/mon_client.h>
#include <linux/ceph/auth.h>
#include <linux/ceph/debugfs.h>
return 0;
}
+/* @buffer: The buffer to store the formatted date and time string.
+ * @buffer_len: The length of the buffer.
+ *
+ * Returns: The number of characters written to the buffer, or a negative error code.
+ */
+static int jiffies_to_formatted_time(unsigned long jiffies_value, char *buffer, size_t buffer_len)
+{
+ unsigned long seconds;
+ unsigned long subsec_jiffies;
+ unsigned long microseconds;
+ struct tm tm_time;
+ time64_t timestamp;
+
+ // Convert jiffies to seconds since boot
+ seconds = jiffies_value / HZ;
+
+ // Calculate remaining jiffies for subsecond precision
+ subsec_jiffies = jiffies_value % HZ;
+ microseconds = (subsec_jiffies * 1000) / HZ;
+
+ // Get current time and calculate absolute timestamp
+ // Using boottime as reference to convert relative jiffies to absolute time
+ timestamp = ktime_get_real_seconds() - (jiffies - jiffies_value) / HZ;
+
+ // Convert to broken-down time
+ time64_to_tm(timestamp, 0, &tm_time);
+
+ // Format the time into the buffer with millisecond precision
+ return snprintf(buffer, buffer_len, "%04ld-%02d-%02d %02d:%02d:%02d.%03lu",
+ tm_time.tm_year + 1900, tm_time.tm_mon + 1, tm_time.tm_mday,
+ tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec, microseconds);
+}
+
static int status_show(struct seq_file *s, void *p)
{
struct ceph_fs_client *fsc = s->private;
return 0;
}
+static int ceph_san_tls_show_internal(struct seq_file *s, void *p)
+{
+ struct ceph_san_tls_ctx *ctx;
+ struct ceph_san_log_iter iter;
+ struct ceph_san_log_entry *entry;
+ const struct ceph_san_source_info *source;
+ int total_entries = 0;
+ int total_contexts = 0;
+
+ /* Lock the logger to safely traverse the contexts list */
+ spin_lock(&g_logger.lock);
+
+ list_for_each_entry(ctx, &g_logger.contexts, list) {
+ /* Initialize iterator for this context's pagefrag */
+ ceph_san_log_iter_init(&iter, &ctx->pf);
+ int pid = ctx->pid;
+ char *comm = ctx->comm;
+ int ctx_entries = 0;
+
+ total_contexts++;
+
+ /* Lock the pagefrag before accessing entries */
+ spin_lock_bh(&ctx->pf.lock);
+
+ /* Iterate through log entries in this context */
+ while ((entry = ceph_san_log_iter_next(&iter)) != NULL) {
+ char datetime_str[32];
+ char reconstructed_msg[256];
+
+ /* Validate entry before processing */
+ if (!entry || !is_valid_kernel_addr(entry)) {
+ seq_printf(s, "[%d][%s]: Invalid entry pointer %p\n", pid, comm, entry);
+ break;
+ }
+
+#if CEPH_SAN_DEBUG_POISON
+ if (entry->debug_poison != CEPH_SAN_LOG_ENTRY_POISON) {
+ seq_printf(s, "[%d][%s]: Corrupted log entry detected\n", pid, comm);
+ continue;
+ }
+#endif
+
+ /* Calculate absolute timestamp from delta */
+ unsigned long abs_jiffies = ctx->base_jiffies + entry->ts_delta;
+ jiffies_to_formatted_time(abs_jiffies, datetime_str, sizeof(datetime_str));
+
+ /* Get source information for this ID */
+ source = ceph_san_get_source_info(entry->source_id);
+ if (!source) {
+ seq_printf(s, "[%d][%s][%s]:[Unknown Source ID %u]: %s\n",
+ pid, comm, datetime_str, entry->source_id, entry->buffer);
+ total_entries++;
+ ctx_entries++;
+ continue;
+ }
+
+ seq_printf(s, "[%d][%s][%s]:%s:%s:%u: ",
+ pid, comm, datetime_str,
+ source->file, source->func, source->line);
+
+ int ret = ceph_san_log_reconstruct(entry, reconstructed_msg, sizeof(reconstructed_msg));
+ if (ret >= 0)
+ seq_puts(s, reconstructed_msg);
+ else
+ seq_printf(s, "<error reconstructing message: %d>", ret);
+ seq_putc(s, '\n');
+
+ total_entries++;
+ ctx_entries++;
+ }
+
+ /* Unlock the pagefrag after we're done with this context */
+ spin_unlock_bh(&ctx->pf.lock);
+ }
+
+ spin_unlock(&g_logger.lock);
+
+ /* Add summary information */
+ seq_printf(s, "\n=== Summary ===\n");
+ seq_printf(s, "Total contexts: %d\n", total_contexts);
+ seq_printf(s, "Total entries: %d\n", total_entries);
+
+ return 0;
+}
+
+static int ceph_san_tls_show(struct seq_file *s, void *p)
+{
+ return ceph_san_tls_show_internal(s, p);
+}
+
+
+
+static int ceph_san_contexts_show(struct seq_file *s, void *p)
+{
+ struct ceph_san_tls_ctx *ctx;
+ unsigned long flags;
+
+ /* Lock the logger to safely traverse the contexts list */
+ spin_lock_irqsave(&g_logger.lock, flags);
+ seq_puts(s, "Active TLS Contexts:\n");
+ seq_puts(s, "PID Command State Context ID Base Jiffies\n");
+ seq_puts(s, "------------------------------------------------------------\n");
+ list_for_each_entry(ctx, &g_logger.contexts, list) {
+ char task_state = ctx->task ? task_state_to_char(ctx->task) : 'N';
+
+ seq_printf(s, "%-8d %-15s [%c] %-12llu %-12lu\n",
+ ctx->pid,
+ ctx->comm,
+ task_state,
+ ctx->id,
+ ctx->base_jiffies);
+ }
+ spin_unlock_irqrestore(&g_logger.lock, flags);
+ return 0;
+}
+
DEFINE_SHOW_ATTRIBUTE(mdsmap);
DEFINE_SHOW_ATTRIBUTE(mdsc);
DEFINE_SHOW_ATTRIBUTE(caps);
DEFINE_SHOW_ATTRIBUTE(metrics_latency);
DEFINE_SHOW_ATTRIBUTE(metrics_size);
DEFINE_SHOW_ATTRIBUTE(metrics_caps);
+DEFINE_SHOW_ATTRIBUTE(ceph_san_tls);
+DEFINE_SHOW_ATTRIBUTE(ceph_san_contexts);
/*
void ceph_fs_debugfs_cleanup(struct ceph_fs_client *fsc)
{
- boutc(fsc->client, "begin\n");
+ doutc(fsc->client, "begin\n");
debugfs_remove(fsc->debugfs_bdi);
debugfs_remove(fsc->debugfs_congestion_kb);
debugfs_remove(fsc->debugfs_mdsmap);
debugfs_remove(fsc->debugfs_caps);
debugfs_remove(fsc->debugfs_status);
debugfs_remove(fsc->debugfs_mdsc);
+ debugfs_remove(fsc->debugfs_cephsan_tls);
+ debugfs_remove(fsc->debugfs_cephsan_contexts);
debugfs_remove_recursive(fsc->debugfs_metrics_dir);
- boutc(fsc->client, "done\n");
+ doutc(fsc->client, "done\n");
}
void ceph_fs_debugfs_init(struct ceph_fs_client *fsc)
{
char name[NAME_MAX];
- boutc(fsc->client, "begin\n");
+ doutc(fsc->client, "begin\n");
fsc->debugfs_congestion_kb =
debugfs_create_file("writeback_congestion_kb",
0600,
fsc,
&status_fops);
+
+ fsc->debugfs_cephsan_tls = debugfs_create_file("cephsan_tls",
+ 0444,
+ fsc->client->debugfs_dir,
+ fsc,
+ &ceph_san_tls_fops);
+
+ /* Add the new contexts-only view */
+ fsc->debugfs_cephsan_contexts = debugfs_create_file("cephsan_contexts",
+ 0444,
+ fsc->client->debugfs_dir,
+ fsc,
+ &ceph_san_contexts_fops);
+
fsc->debugfs_metrics_dir = debugfs_create_dir("metrics",
fsc->client->debugfs_dir);
boutc(fsc->client, "done\n");
}
-
#else /* CONFIG_DEBUG_FS */
void ceph_fs_debugfs_init(struct ceph_fs_client *fsc)
{
}
-#endif /* CONFIG_DEBUG_FS */
+#endif /* CONFIG_DEBUG_FS */
struct dentry *debugfs_status;
struct dentry *debugfs_mds_sessions;
struct dentry *debugfs_metrics_dir;
+ struct dentry *debugfs_cephsan_tls;
+ struct dentry *debugfs_cephsan_contexts;
#endif
#ifdef CONFIG_CEPH_FSCACHE
#include <linux/types.h> /* For size_t */
+/* Forward declaration */
+struct ceph_san_log_entry;
+
/**
* Reconstructs a formatted string from a buffer containing serialized values.
* The function uses the format string to determine the types and number of values
* @param size Size of the buffer in bytes
* @param out Buffer to store the reconstructed string
* @param out_size Size of the output buffer
- * @return Number of bytes written to out buffer, or -1 on error
+ * @return Number of bytes written to out buffer, or negative error code on failure
*/
int ceph_san_des_reconstruct(const char *fmt, const void *buffer, size_t nr_args,
size_t size, char *out, size_t out_size);
+/**
+ * Reconstructs a formatted string from a log entry.
+ * This is a wrapper around ceph_san_des_reconstruct that handles log entry parsing.
+ *
+ * @param entry Log entry containing serialized data
+ * @param output Buffer to write the formatted string to
+ * @param output_size Size of the output buffer
+ * @return Length of formatted string, or negative error code on failure
+ */
+int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size);
+
#endif /* CEPH_SAN_DES_H */
/* Get next log entry, returns NULL when no more entries */
struct ceph_san_log_entry *ceph_san_log_iter_next(struct ceph_san_log_iter *iter);
-/* Reconstruct a formatted string from a log entry */
-int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size);
/* Initialize the logging system */
int ceph_san_logger_init(void);
16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define ceph_san_narg(...) ___ceph_san_narg(__VA_ARGS__)
-#define STR_MAX_SIZE 64
+#define STR_MAX_SIZE 256
#define __sizeof(x) \
(IS_STR(x) ? STR_MAX_SIZE : \
(sizeof(x) < 4) ? 4 : sizeof(x))
}
dst[count] = '\0';
- pr_err("strscpy_n: string truncated, exceeded max size %d\n", STR_MAX_SIZE);
+ pr_warn("strscpy_n: string truncated, exceeded max size %d\n", STR_MAX_SIZE);
out:
return count + 1;
}
static inline void* strscpy_n_update(char *dst, const char *src, const char *file, int line)
{
ssize_t ret = __strscpy(dst, src);
- if (!(unlikely(ret > 0 && ret < STR_MAX_SIZE))) {
- panic("strscpy_n_update: ret = %zd at %s:%d :: %s < - %s\n", ret, file, line, dst, src);
+ if (unlikely(ret <= 0 || ret >= STR_MAX_SIZE)) {
+ pr_err("strscpy_n_update: string handling error ret=%zd at %s:%d :: dst='%s' src='%s'\n",
+ ret, file, line, dst, src ? src : "(null)");
+ /* Return safely instead of panicking - truncate and continue */
+ if (ret >= STR_MAX_SIZE) {
+ dst[STR_MAX_SIZE - 1] = '\0';
+ ret = STR_MAX_SIZE;
+ } else {
+ /* Handle null or empty string case */
+ dst[0] = '\0';
+ ret = 1;
+ }
}
return dst + round_up(ret, 4);
}
#include <linux/ceph/ceph_san_des.h>
+#include <linux/ceph/ceph_san_logger.h> /* For log entry struct and source functions */
#include <linux/string.h> /* For strchr, strlen */
#include <linux/ctype.h> /* For isdigit */
#include <linux/types.h> /* For size_t */
#include <linux/kernel.h> /* For snprintf */
+#include <linux/printk.h> /* For pr_err */
+#include <linux/align.h> /* For round_up */
-static int parse_format_specifier(const char **fmt, char *spec) {
- const char *p = *fmt;
- char *s = spec;
- /* Skip the '%' */
- if (*p != '%') return -1;
- *s++ = *p++;
+int ceph_san_des_reconstruct(const char *fmt, const void *buffer, size_t nr_args,
+ 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;
- /* Skip flags */
- while (*p && (*p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '0')) {
- *s++ = *p++;
+ if (!fmt || !buffer || !out || !out_size) {
+ pr_err("ceph_san_des_reconstruct: invalid parameters\n");
+ return -EINVAL;
}
- /* Skip field width */
- while (*p && isdigit(*p)) {
- *s++ = *p++;
- }
+ *out_ptr = '\0';
- /* Skip precision */
- if (*p == '.') {
- *s++ = *p++;
- while (*p && isdigit(*p)) {
- *s++ = *p++;
+ // Process the format string
+ while (*fmt_ptr && remaining > 0) {
+ if (*fmt_ptr != '%') {
+ // Copy literal character
+ *out_ptr++ = *fmt_ptr++;
+ remaining--;
+ continue;
}
- }
- /* Get length modifier */
- if (*p == 'h' || *p == 'l' || *p == 'L' || *p == 'j' || *p == 'z' || *p == 't') {
- *s++ = *p++;
- if ((*p == 'h' || *p == 'l') && *(p-1) == *p) {
- *s++ = *p++;
+ // Skip the '%'
+ fmt_ptr++;
+
+ // Handle %%
+ if (*fmt_ptr == '%') {
+ *out_ptr++ = '%';
+ fmt_ptr++;
+ remaining--;
+ continue;
}
- }
- /* Get conversion specifier */
- if (*p && strchr("diouxXeEfFgGaAcspn%", *p)) {
- *s++ = *p++;
- } else {
- return -1;
- }
+ // Skip flags (-+#0 space)
+ while (*fmt_ptr && (*fmt_ptr == '-' || *fmt_ptr == '+' || *fmt_ptr == '#' ||
+ *fmt_ptr == '0' || *fmt_ptr == ' ')) {
+ fmt_ptr++;
+ }
- *s = '\0';
- *fmt = p;
- return 0;
-}
+ // Skip field width (digits or *)
+ while (*fmt_ptr && (*fmt_ptr >= '0' && *fmt_ptr <= '9')) {
+ fmt_ptr++;
+ }
+ if (*fmt_ptr == '*') {
+ fmt_ptr++;
+ }
-int ceph_san_des_reconstruct(const char *fmt, const void *buffer, size_t nr_args,
- size_t size, char *out, size_t out_size) {
- const unsigned char *buf = buffer;
- const char *p = fmt;
- char spec[32];
- size_t offset = 0;
- size_t out_offset = 0;
- size_t arg_count = 0;
+ // Skip precision (.digits or .*)
+ if (*fmt_ptr == '.') {
+ fmt_ptr++;
+ while (*fmt_ptr && (*fmt_ptr >= '0' && *fmt_ptr <= '9')) {
+ fmt_ptr++;
+ }
+ if (*fmt_ptr == '*') {
+ fmt_ptr++;
+ }
+ }
- if (!fmt || !buffer || !out || !out_size) {
- return -1;
- }
- //printf("Starting reconstruction with buffer at %p, size %zu, nr_args %zu, out_size %zu\n",
- // buffer, size, nr_args, out_size);
- while (*p && out_offset < out_size - 1) {
- if (*p != '%') {
- out[out_offset++] = *p++;
- continue;
+ // Check argument count limit
+ if (arg_count >= nr_args) {
+ pr_err("ceph_san_des_reconstruct (%zu): too many format specifiers, expected %zu args. Format: '%.100s'\n",
+ arg_count, nr_args, fmt);
+ return -EINVAL;
}
- if (parse_format_specifier(&p, spec) < 0) {
- return -1;
+ // Parse and handle format specifier
+ switch (*fmt_ptr) {
+ case 's': { // String (inline)
+ const char *str;
+ size_t str_len;
+ size_t max_scan_len;
+
+ // Validate we have enough buffer space for at least a null terminator
+ if (buf_ptr >= buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun at string argument\n", arg_count);
+ return -EFAULT;
+ }
+
+ // String is stored inline in buffer
+ str = buf_ptr;
+
+ // Calculate maximum safe length to scan for null terminator
+ max_scan_len = buf_end - buf_ptr;
+
+ // Find string length with bounds checking
+ str_len = strnlen(str, max_scan_len);
+ if (str_len == max_scan_len && str[str_len - 1] != '\0') {
+ pr_err("ceph_san_des_reconstruct (%zu): unterminated string in buffer\n", arg_count);
+ return -EFAULT;
+ }
+
+ // Advance buffer pointer with proper alignment
+ buf_ptr += round_up(str_len + 1, 4);
+
+ // Check if buffer advance exceeds entry bounds
+ if (buf_ptr > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): string extends beyond buffer bounds\n", arg_count);
+ return -EFAULT;
+ }
+
+ // Copy string to output with bounds checking
+ if (str_len > remaining)
+ str_len = remaining;
+ memcpy(out_ptr, str, str_len);
+ out_ptr += str_len;
+ remaining -= str_len;
+ break;
}
- if (arg_count >= nr_args) {
- return -1;
+ case 'd': case 'i': { // Integer
+ int val;
+
+ // Check buffer bounds before reading
+ if (buf_ptr + sizeof(int) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading int\n", arg_count);
+ return -EFAULT;
+ }
+
+ val = *(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;
}
- /* Check buffer overflow */
- if (offset >= size) {
- return -1;
+ case 'u': { // Unsigned integer
+ unsigned int val;
+
+ // Check buffer bounds before reading
+ if (buf_ptr + sizeof(unsigned int) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count);
+ return -EFAULT;
+ }
+
+ val = *(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;
}
- //printf("Processing specifier '%s' at offset %zu\n", spec, offset);
+ case 'x': { // Hex integer (lowercase)
+ unsigned int val;
- /* Handle different format specifiers */
- switch (spec[strlen(spec)-1]) {
- case 'd':
- case 'i':
- case 'o':
- case 'u':
- case 'x':
- case 'X': {
- long long val;
- const void *ptr = buf + offset;
- if (strchr(spec, 'l')) {
- val = *(const long long*)ptr;
- offset += sizeof(long long);
- } else {
- val = *(const int*)ptr;
- offset += sizeof(int);
- }
- //printf("Read integer value: %lld at address %p (offset %zu)\n", val, ptr, offset);
- out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val);
- break;
+ // Check buffer bounds before reading
+ if (buf_ptr + sizeof(unsigned int) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count);
+ return -EFAULT;
}
- case 'f':
- case 'e':
- case 'E':
- case 'g':
- case 'G':
- case 'a':
- case 'A': {
- double val = *(const double*)(buf + offset);
- offset += sizeof(double);
- //printf("Read double value: %f at offset %zu\n", val, offset - sizeof(double));
- out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val);
- break;
+ val = *(unsigned int *)buf_ptr;
+ buf_ptr += sizeof(unsigned int);
+
+ ret = snprintf(out_ptr, remaining, "%x", val);
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
}
+ break;
+ }
- case 'c': {
- char val = *(const char*)(buf + offset);
- offset += sizeof(char);
- //printf("Read char value: %c at offset %zu\n", val, offset - sizeof(char));
- out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val);
- break;
+ case 'X': { // Hex integer (uppercase)
+ unsigned int val;
+
+ // Check buffer bounds before reading
+ if (buf_ptr + sizeof(unsigned int) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count);
+ return -EFAULT;
}
- case 's': {
- const char *val = *(const char**)(buf + offset);
- offset += sizeof(const char*);
- //printf("Read string pointer: %p at offset %zu\n", val, offset - sizeof(const char*));
- out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val);
- break;
+ val = *(unsigned int *)buf_ptr;
+ buf_ptr += sizeof(unsigned int);
+
+ ret = snprintf(out_ptr, remaining, "%X", val);
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
}
+ break;
+ }
+
+ case 'o': { // Octal integer
+ unsigned int val;
- case 'p': {
- const void *val = *(const void**)(buf + offset);
- offset += sizeof(const void*);
- //printf("Read pointer value: %p at offset %zu\n", val, offset - sizeof(const void*));
- out_offset += snprintf(out + out_offset, out_size - out_offset, spec, val);
- break;
+ // Check buffer bounds before reading
+ if (buf_ptr + sizeof(unsigned int) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned int\n", arg_count);
+ return -EFAULT;
}
- case '%': {
- out[out_offset++] = '%';
- break;
+ val = *(unsigned int *)buf_ptr;
+ buf_ptr += sizeof(unsigned int);
+
+ ret = snprintf(out_ptr, remaining, "%o", val);
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+
+ case 'p': { // Pointer
+ void *val;
+
+ // Check buffer bounds before reading
+ if (buf_ptr + sizeof(void *) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading pointer\n", arg_count);
+ return -EFAULT;
+ }
+
+ val = *(void **)buf_ptr;
+ buf_ptr += sizeof(void *);
+
+ ret = snprintf(out_ptr, remaining, "%p", val);
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+
+ case 'l': { // Long types
+ fmt_ptr++; // Skip 'l'
+ if (*fmt_ptr == 'l') { // Long long
+ fmt_ptr++; // Skip second 'l'
+ if (*fmt_ptr == 'd' || *fmt_ptr == 'i') {
+ long long val;
+ if (buf_ptr + sizeof(long long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading long long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(long long *)buf_ptr;
+ buf_ptr += sizeof(long long);
+ ret = snprintf(out_ptr, remaining, "%lld", val);
+ } else if (*fmt_ptr == 'u') {
+ unsigned long long val;
+ if (buf_ptr + sizeof(unsigned long long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long long);
+ ret = snprintf(out_ptr, remaining, "%llu", val);
+ } else if (*fmt_ptr == 'x') {
+ unsigned long long val;
+ if (buf_ptr + sizeof(unsigned long long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long long);
+ ret = snprintf(out_ptr, remaining, "%llx", val);
+ } else if (*fmt_ptr == 'X') {
+ unsigned long long val;
+ if (buf_ptr + sizeof(unsigned long long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long long);
+ ret = snprintf(out_ptr, remaining, "%llX", val);
+ } else if (*fmt_ptr == 'o') {
+ unsigned long long val;
+ if (buf_ptr + sizeof(unsigned long long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long long);
+ ret = snprintf(out_ptr, remaining, "%llo", val);
+ } else {
+ pr_err("ceph_san_des_reconstruct: invalid long long format specifier '%%ll%c'\n", *fmt_ptr);
+ return -EINVAL;
+ }
+ } else { // Long
+ if (*fmt_ptr == 'd' || *fmt_ptr == 'i') {
+ long val;
+ if (buf_ptr + sizeof(long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(long *)buf_ptr;
+ buf_ptr += sizeof(long);
+ ret = snprintf(out_ptr, remaining, "%ld", val);
+ } else if (*fmt_ptr == 'u') {
+ unsigned long val;
+ if (buf_ptr + sizeof(unsigned long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long);
+ ret = snprintf(out_ptr, remaining, "%lu", val);
+ } else if (*fmt_ptr == 'x') {
+ unsigned long val;
+ if (buf_ptr + sizeof(unsigned long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long);
+ ret = snprintf(out_ptr, remaining, "%lx", val);
+ } else if (*fmt_ptr == 'X') {
+ unsigned long val;
+ if (buf_ptr + sizeof(unsigned long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long);
+ ret = snprintf(out_ptr, remaining, "%lX", val);
+ } else if (*fmt_ptr == 'o') {
+ unsigned long val;
+ if (buf_ptr + sizeof(unsigned long) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading unsigned long\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(unsigned long *)buf_ptr;
+ buf_ptr += sizeof(unsigned long);
+ ret = snprintf(out_ptr, remaining, "%lo", val);
+ } else {
+ pr_err("ceph_san_des_reconstruct: invalid long format specifier '%%l%c'\n", *fmt_ptr);
+ return -EINVAL;
+ }
}
- default:
- return -1;
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ break;
+ }
+
+ case 'z': { // size_t
+ fmt_ptr++; // Skip 'z'
+ if (*fmt_ptr == 'u') {
+ size_t val;
+ if (buf_ptr + sizeof(size_t) > buf_end) {
+ pr_err("ceph_san_des_reconstruct (%zu): buffer overrun reading size_t\n", arg_count);
+ return -EFAULT;
+ }
+ val = *(size_t *)buf_ptr;
+ buf_ptr += sizeof(size_t);
+ ret = snprintf(out_ptr, remaining, "%zu", val);
+ if (ret > 0) {
+ if (ret > remaining)
+ ret = remaining;
+ out_ptr += ret;
+ remaining -= ret;
+ }
+ } else {
+ pr_err("ceph_san_des_reconstruct: invalid size_t format specifier '%%z%c'\n", *fmt_ptr);
+ return -EINVAL;
+ }
+ break;
+ }
+
+ default:
+ // Unknown format specifier
+ pr_err("ceph_san_des_reconstruct: unknown format specifier '%%%c'\n", *fmt_ptr);
+ return -EINVAL;
}
+ fmt_ptr++;
arg_count++;
}
- out[out_offset] = '\0';
- return out_offset;
+ // Remove trailing newline if present
+ if (out_ptr > out && *(out_ptr - 1) == '\n') {
+ *(out_ptr - 1) = '\0';
+ } else {
+ *out_ptr = '\0';
+ }
+
+ return out_size - remaining - 1;
+}
+
+/**
+ * ceph_san_log_reconstruct - Reconstruct a formatted string from a log entry
+ * @entry: Log entry containing serialized data
+ * @output: Buffer to write the formatted string to
+ * @output_size: Size of the output buffer
+ *
+ * This is a wrapper around ceph_san_des_reconstruct that handles log entry parsing.
+ * It extracts the format string from the source info and calls the core reconstruction function.
+ *
+ * Returns length of formatted string, or negative error code on failure
+ */
+int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size)
+{
+ const struct ceph_san_source_info *info;
+ const char *fmt;
+
+ if (!entry || !output || output_size == 0) {
+ pr_err("ceph_san_log_reconstruct: invalid parameters\n");
+ return -EINVAL;
+ }
+
+ /* Verify entry is a valid kernel address */
+ if (!is_valid_kernel_addr(entry)) {
+ pr_err("ceph_san_log_reconstruct: invalid entry pointer %p\n", entry);
+ return -EFAULT;
+ }
+
+ /* Verify entry buffer is a valid kernel address */
+ if (!is_valid_kernel_addr(entry->buffer)) {
+ pr_err("ceph_san_log_reconstruct: invalid buffer pointer %p for entry %p\n",
+ entry->buffer, entry);
+ return -EFAULT;
+ }
+
+#if CEPH_SAN_DEBUG_POISON
+ if (entry->debug_poison != CEPH_SAN_LOG_ENTRY_POISON) {
+ pr_err("ceph_san_log_reconstruct: corrupted log entry detected\n");
+ return -EFAULT;
+ }
+#endif
+
+ // Get format string from source info
+ info = ceph_san_get_source_info(entry->source_id);
+ if (!info) {
+ pr_err("ceph_san_log_reconstruct: source info not found for ID %u\n", entry->source_id);
+ return -EINVAL;
+ }
+
+ fmt = info->fmt;
+ if (!fmt) {
+ pr_err("ceph_san_log_reconstruct: format string not found in source info for ID %u\n", entry->source_id);
+ return -EINVAL;
+ }
+
+ // Count arguments in format string to pass to des_reconstruct
+ // This must match the parsing logic in ceph_san_des_reconstruct exactly
+ size_t nr_args = 0;
+ const char *p = fmt;
+ while (*p) {
+ if (*p == '%') {
+ p++; // Skip '%'
+
+ // Handle %%
+ if (*p == '%') {
+ p++;
+ continue;
+ }
+
+ // Skip flags (-+#0 space)
+ while (*p && (*p == '-' || *p == '+' || *p == '#' ||
+ *p == '0' || *p == ' ')) {
+ p++;
+ }
+
+ // Skip field width (digits or *)
+ while (*p && (*p >= '0' && *p <= '9')) {
+ p++;
+ }
+ if (*p == '*') {
+ p++;
+ }
+
+ // Skip precision (.digits or .*)
+ if (*p == '.') {
+ p++;
+ while (*p && (*p >= '0' && *p <= '9')) {
+ p++;
+ }
+ if (*p == '*') {
+ p++;
+ }
+ }
+
+ // Check if we have a valid conversion specifier
+ if (*p == 's' || *p == 'd' || *p == 'i' || *p == 'u' || *p == 'p' ||
+ *p == 'x' || *p == 'X' || *p == 'o' || *p == 'z') {
+ nr_args++;
+ } else if (*p == 'l') {
+ // Handle long types
+ p++; // Skip 'l'
+ if (*p == 'l') {
+ p++; // Skip second 'l' for long long
+ }
+ // Now check the conversion specifier
+ if (*p == 'd' || *p == 'i' || *p == 'u' || *p == 'x' ||
+ *p == 'X' || *p == 'o') {
+ nr_args++;
+ }
+ }
+ }
+ if (*p) p++;
+ }
+
+ // Call the core reconstruction function
+ return ceph_san_des_reconstruct(fmt, entry->buffer, nr_args, entry->len, output, output_size);
}
+EXPORT_SYMBOL(ceph_san_log_reconstruct);
}
EXPORT_SYMBOL(ceph_san_log_iter_next);
-/**
- * ceph_san_log_reconstruct - Reconstruct a formatted string from a log entry
- * @entry: Log entry containing serialized data
- * @output: Buffer to write the formatted string to
- * @output_size: Size of the output buffer
- *
- * Returns length of formatted string, or negative error code on failure
- */
-int ceph_san_log_reconstruct(const struct ceph_san_log_entry *entry, char *output, size_t output_size)
-{
- const struct ceph_san_source_info *info;
- const char *fmt;
- char *in_buffer, *out_ptr;
- int ret;
- int arg_count = 0;
- size_t remaining = output_size - 1; // Reserve space for null terminator
-
- if (!entry || !output || output_size == 0) {
- pr_err("ceph_san_log_reconstruct: invalid parameters\n");
- return -EINVAL;
- }
-
- /* Verify entry is a valid kernel address */
- if (!is_valid_kernel_addr(entry)) {
- pr_err("ceph_san_log_reconstruct: invalid entry pointer %p\n", entry);
- return -EFAULT;
- }
- /* Dump entry buffer pointer and validate */
- pr_debug("ceph_san_log_reconstruct: entry buffer pointer %llx (len %u) is %s\n",
- (unsigned long long)entry->buffer, entry->len,
- is_valid_kernel_addr(entry->buffer) ? "valid" : "invalid");
- /* Verify entry buffer is a valid kernel address */
- if (!is_valid_kernel_addr(entry->buffer)) {
- pr_err("ceph_san_log_reconstruct: invalid buffer pointer %p for entry %p\n",
- entry->buffer, entry);
- return -EFAULT;
- }
-
- // Get format string from source info
- info = ceph_san_get_source_info(entry->source_id);
- if (!info) {
- pr_err("ceph_san_log_reconstruct: source info not found for ID %u\n", entry->source_id);
- return -EINVAL;
- }
-
- fmt = info->fmt;
- if (!fmt) {
- pr_err("ceph_san_log_reconstruct: format string not found in source info for ID %u\n", entry->source_id);
- return -EINVAL;
- }
-
- in_buffer = (char *)(entry->buffer);
- out_ptr = output;
- *out_ptr = '\0';
-
- // Process the format string
- while (*fmt && remaining > 0) {
- if (*fmt != '%') {
- // Copy regular characters
- *out_ptr++ = *fmt++;
- remaining--;
- continue;
- }
- arg_count++;
- fmt++; // Skip the '%'
- // Skip width specifiers
- while (*fmt >= '0' && *fmt <= '9')
- fmt++;
-
- // Handle format specifiers
- switch (*fmt) {
- case '%': // Literal %
- *out_ptr++ = '%';
- remaining--;
- break;
- case 's': { // String
- const char *str;
- int type = 0;
- // Inline string
- str = in_buffer;
- if (!is_valid_kernel_addr(str)) {
- pr_err("ceph_san_log_reconstruct (%d): invalid inline string pointer %llx\n", arg_count, (unsigned long long)str);
- return -EFAULT;
- }
- in_buffer += round_up(strlen(str) + 1, 4);
- size_t len = strlen(str);
- if (len > remaining)
- len = remaining;
- pr_debug("reconstruct: writing %s string '%s' (len=%zu) at out_offset=%ld\n",
- type ? "pointer" : "inline", str, strlen(str), in_buffer - entry->buffer);
- memcpy(out_ptr, str, len);
- out_ptr += len;
- remaining -= len;
- break;
- }
-
- case 'd': case 'i': { // Integer
- int val = *(int *)in_buffer;
- in_buffer += sizeof(int);
- pr_debug("reconstruct: reading int %d at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%d", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- break;
- }
-
- case 'u': { // Unsigned integer
- unsigned int val = *(unsigned int *)in_buffer;
- in_buffer += sizeof(unsigned int);
- pr_debug("reconstruct: reading int %u at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%u", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- break;
- }
-
- case 'x': case 'X': { // Hex
- unsigned int val = *(unsigned int *)in_buffer;
- in_buffer += sizeof(unsigned int);
- pr_debug("reconstruct: reading int %u at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, (*fmt == 'x') ? "%x" : "%X", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- break;
- }
-
- case 'p': { // Pointer
- void *val = *(void **)in_buffer;
- in_buffer += sizeof(void *);
- pr_debug("reconstruct: reading pointer %llx at in_offset=%ld\n",
- (unsigned long long)val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%p", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- break;
- }
-
- case 'o': { // Octal
- unsigned int val = *(unsigned int *)in_buffer;
- in_buffer += sizeof(unsigned int);
- pr_debug("reconstruct: reading int %u (octal: %o) at in_offset=%ld\n",
- val, val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%o", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- break;
- }
-
- case 'l': { // Long or long long
- fmt++;
- if (*fmt == 'l') { // Long long
- fmt++;
- if (*fmt == 'd' || *fmt == 'i') {
- long long val = *(long long *)in_buffer;
- in_buffer += sizeof(long long);
- pr_debug("reconstruct: reading long long %lld at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%lld", val);
- } else if (*fmt == 'u') {
- unsigned long long val = *(unsigned long long *)in_buffer;
- in_buffer += sizeof(unsigned long long);
- pr_debug("reconstruct: reading long long %llu at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%llu", val);
- } else if (*fmt == 'x') {
- unsigned long long val = *(unsigned long long *)in_buffer;
- in_buffer += sizeof(unsigned long long);
- pr_debug("reconstruct: reading long long %llu at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%llx", val);
- } else if (*fmt == 'X') {
- unsigned long long val = *(unsigned long long *)in_buffer;
- in_buffer += sizeof(unsigned long long);
- pr_debug("reconstruct: reading long long %llu at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%llX", val);
- } else if (*fmt == 'o') {
- unsigned long long val = *(unsigned long long *)in_buffer;
- in_buffer += sizeof(unsigned long long);
- pr_debug("reconstruct: reading long long %llu (octal: %llo) at in_offset=%ld\n",
- val, val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%llo", val);
- } else {
- pr_err("ceph_san_log_reconstruct: invalid long long format specifier '%%%c%c%c'\n", 'l', 'l', *fmt);
- return -EINVAL;
- }
- } else { // Long
- if (*fmt == 'd' || *fmt == 'i') {
- long val = *(long *)in_buffer;
- in_buffer += sizeof(long);
- pr_debug("reconstruct: reading long %ld at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%ld", val);
- } else if (*fmt == 'u') {
- unsigned long val = *(unsigned long *)in_buffer;
- in_buffer += sizeof(unsigned long);
- pr_debug("reconstruct: reading long %lu at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%lu", val);
- } else if (*fmt == 'x') {
- unsigned long val = *(unsigned long *)in_buffer;
- in_buffer += sizeof(unsigned long);
- pr_debug("reconstruct: reading long %lx at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%lx", val);
- } else if (*fmt == 'X') {
- unsigned long val = *(unsigned long *)in_buffer;
- in_buffer += sizeof(unsigned long);
- pr_debug("reconstruct: reading long %lx at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%lX", val);
- } else if (*fmt == 'o') {
- unsigned long val = *(unsigned long *)in_buffer;
- in_buffer += sizeof(unsigned long);
- pr_debug("reconstruct: reading long %lu (octal: %lo) at in_offset=%ld\n",
- val, val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%lo", val);
- } else {
- pr_err("ceph_san_log_reconstruct: invalid long format specifier '%%l%c'\n", *fmt);
- return -EINVAL;
- }
- }
-
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- break;
- }
-
- case 'z': {
- fmt++; // Look at next character
- if (*fmt == 'u' || *fmt == 'd') {
- size_t val = *(size_t *)in_buffer;
- in_buffer += sizeof(size_t);
- pr_debug("reconstruct: reading size_t %zu at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, (*fmt == 'u') ? "%zu" : "%zd", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- } else if (*fmt == 'x' || *fmt == 'X') {
- size_t val = *(size_t *)in_buffer;
- in_buffer += sizeof(size_t);
- pr_debug("reconstruct: reading size_t %zx at in_offset=%ld\n",
- val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, (*fmt == 'x') ? "%zx" : "%zX", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- } else if (*fmt == 'o') {
- size_t val = *(size_t *)in_buffer;
- in_buffer += sizeof(size_t);
- pr_debug("reconstruct: reading size_t %zu (octal: %zo) at in_offset=%ld\n",
- val, val, in_buffer - entry->buffer);
- ret = snprintf(out_ptr, remaining, "%zo", val);
- if (ret > 0) {
- if (ret > remaining)
- ret = remaining;
- out_ptr += ret;
- remaining -= ret;
- }
- } else {
- pr_err("ceph_san_log_reconstruct: invalid size_t format specifier '%%z%c'\n", *fmt);
- return -EINVAL;
- }
- break;
- }
-
- default:
- // Unknown format specifier
- pr_debug("ceph_san_log_reconstruct: unknown format specifier '%%%c' in fmt string\n", *fmt);
- return -EINVAL;
- }
-
- fmt++;
- }
- if (out_ptr > output && *(out_ptr - 1) == '\n') {
- *(out_ptr - 1) = '\0';
- } else {
- *out_ptr = '\0';
- }
-
- return output_size - remaining - 1;
-}
-EXPORT_SYMBOL(ceph_san_log_reconstruct);
/**
* ceph_san_log_trim - Trim the current context's pagefrag by n bytes