From 6b738aa5f6bb2343f8277d318ff1e9ea9289212c Mon Sep 17 00:00:00 2001 From: Alex Markuze Date: Wed, 13 Aug 2025 16:27:44 +0000 Subject: [PATCH] proper isolation --- fs/ceph/Makefile | 2 + fs/ceph/blog_client.c | 217 +++++++++++++++ fs/ceph/blog_debugfs.c | 303 +++++++++++++++++++++ fs/ceph/blog_integration_example.c | 155 +++++++++++ include/linux/blog/blog.h | 2 +- include/linux/blog/blog_module.h | 67 +++++ include/linux/blog/blog_ser.h | 5 + include/linux/ceph/ceph_blog.h | 114 ++++++++ include/linux/ceph/ceph_san_ser.h | 5 + lib/blog/Makefile | 2 +- lib/blog/blog_module.c | 423 +++++++++++++++++++++++++++++ 11 files changed, 1293 insertions(+), 2 deletions(-) create mode 100644 fs/ceph/blog_client.c create mode 100644 fs/ceph/blog_debugfs.c create mode 100644 fs/ceph/blog_integration_example.c create mode 100644 include/linux/blog/blog_module.h create mode 100644 include/linux/ceph/ceph_blog.h create mode 100644 lib/blog/blog_module.c diff --git a/fs/ceph/Makefile b/fs/ceph/Makefile index 1f77ca04c426f..ccb542870ab3f 100644 --- a/fs/ceph/Makefile +++ b/fs/ceph/Makefile @@ -10,6 +10,8 @@ ceph-y := super.o inode.o dir.o file.o locks.o addr.o ioctl.o \ mds_client.o mdsmap.o strings.o ceph_frag.o \ debugfs.o util.o metric.o +ceph-$(CONFIG_BLOG) += blog_client.o blog_debugfs.o + ceph-$(CONFIG_CEPH_FSCACHE) += cache.o ceph-$(CONFIG_CEPH_FS_POSIX_ACL) += acl.o ceph-$(CONFIG_FS_ENCRYPTION) += crypto.o diff --git a/fs/ceph/blog_client.c b/fs/ceph/blog_client.c new file mode 100644 index 0000000000000..f11e627fa68ea --- /dev/null +++ b/fs/ceph/blog_client.c @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ceph client ID management for BLOG integration + * + * Maintains mapping between Ceph's fsid/global_id and BLOG client IDs + */ + +#include +#include +#include +#include +#include +#include +#include + +/* Ceph's BLOG module context */ +struct blog_module_context *ceph_blog_ctx = NULL; +EXPORT_SYMBOL(ceph_blog_ctx); + +/* Global client mapping state */ +static struct { + struct ceph_blog_client_info client_map[CEPH_BLOG_MAX_CLIENTS]; + u32 next_client_id; + spinlock_t lock; + bool initialized; +} ceph_blog_state = { + .next_client_id = 1, /* Start from 1, 0 is reserved */ + .lock = __SPIN_LOCK_UNLOCKED(ceph_blog_state.lock), + .initialized = false, +}; + +/** + * ceph_blog_init - Initialize Ceph BLOG integration + * + * Creates a module-specific BLOG context for Ceph and initializes + * the client ID mapping state. + * + * Return: 0 on success, negative error code on failure + */ +int ceph_blog_init(void) +{ + if (ceph_blog_state.initialized) + return 0; + + /* Create Ceph's module-specific BLOG context */ + ceph_blog_ctx = blog_module_init("ceph"); + if (!ceph_blog_ctx) { + pr_err("ceph: Failed to initialize BLOG module context\n"); + return -ENOMEM; + } + + /* Initialize client mapping state */ + memset(ceph_blog_state.client_map, 0, sizeof(ceph_blog_state.client_map)); + ceph_blog_state.next_client_id = 1; + ceph_blog_state.initialized = true; + + pr_info("ceph: BLOG module context and client mapping initialized\n"); + return 0; +} +EXPORT_SYMBOL(ceph_blog_init); + +/** + * ceph_blog_cleanup - Clean up Ceph BLOG integration + * + * Cleans up Ceph's module-specific BLOG context and client mappings. + */ +void ceph_blog_cleanup(void) +{ + if (!ceph_blog_state.initialized) + return; + + /* Clean up client mapping state */ + spin_lock(&ceph_blog_state.lock); + memset(ceph_blog_state.client_map, 0, sizeof(ceph_blog_state.client_map)); + ceph_blog_state.next_client_id = 1; + ceph_blog_state.initialized = false; + spin_unlock(&ceph_blog_state.lock); + + /* Clean up module-specific BLOG context */ + if (ceph_blog_ctx) { + blog_module_cleanup(ceph_blog_ctx); + ceph_blog_ctx = NULL; + } + + pr_info("ceph: BLOG module context and client mapping cleaned up\n"); +} +EXPORT_SYMBOL(ceph_blog_cleanup); + +/** + * ceph_blog_check_client_id - Check if a client ID matches the given fsid:global_id pair + * @id: Client ID to check + * @fsid: Client FSID to compare + * @global_id: Client global ID to compare + * + * This preserves the exact functionality of ceph_san_check_client_id. + * Returns the actual ID of the pair. If the given ID doesn't match, scans for + * existing matches or allocates a new ID if no match is found. + * + * Return: Client ID for this fsid/global_id pair + */ +u32 ceph_blog_check_client_id(u32 id, const char *fsid, u64 global_id) +{ + u32 found_id = 0; + struct ceph_blog_client_info *entry; + u32 max_id; + + if (!ceph_blog_state.initialized) + ceph_blog_init(); + + /* First check if the given ID matches */ + if (id != 0 && id < CEPH_BLOG_MAX_CLIENTS) { + entry = &ceph_blog_state.client_map[id]; + if (memcmp(entry->fsid, fsid, sizeof(entry->fsid)) == 0 && + entry->global_id == global_id) { + found_id = id; + goto out_fast; + } + } + + spin_lock(&ceph_blog_state.lock); + max_id = ceph_blog_state.next_client_id; + + /* Scan for existing match */ + for (id = 1; id < max_id && id < CEPH_BLOG_MAX_CLIENTS; id++) { + entry = &ceph_blog_state.client_map[id]; + if (memcmp(entry->fsid, fsid, sizeof(entry->fsid)) == 0 && + entry->global_id == global_id) { + found_id = id; + goto out; + } + } + + /* No match found, allocate new ID */ + found_id = ceph_blog_state.next_client_id++; + if (found_id >= CEPH_BLOG_MAX_CLIENTS) { + /* If we run out of IDs, reuse ID 1 */ + pr_warn("ceph: BLOG client ID overflow, reusing ID 1\n"); + found_id = 1; + ceph_blog_state.next_client_id = 2; + } + /* Use %pU to print fsid like the rest of Ceph does */ + pr_info("ceph: allocating new BLOG client ID %u for fsid=%pU global_id=%llu\n", + found_id, fsid, global_id); + + entry = &ceph_blog_state.client_map[found_id]; + memcpy(entry->fsid, fsid, sizeof(entry->fsid)); + entry->global_id = global_id; + +out: + spin_unlock(&ceph_blog_state.lock); +out_fast: + return found_id; +} +EXPORT_SYMBOL(ceph_blog_check_client_id); + +/** + * ceph_blog_get_client_info - Get client info for a given ID + * @id: Client ID + * + * Return: Client information for this ID, or NULL if invalid + */ +const struct ceph_blog_client_info *ceph_blog_get_client_info(u32 id) +{ + if (!ceph_blog_state.initialized || id == 0 || id >= CEPH_BLOG_MAX_CLIENTS) + return NULL; + return &ceph_blog_state.client_map[id]; +} +EXPORT_SYMBOL(ceph_blog_get_client_info); + +/** + * ceph_blog_client_des_callback - Deserialization callback for Ceph client info + * @buf: Output buffer + * @size: Buffer size + * @client_id: Client ID to deserialize + * + * This is the callback that BLOG will use to deserialize client information. + * + * Return: Number of bytes written to buffer + */ +int ceph_blog_client_des_callback(char *buf, size_t size, u8 client_id) +{ + const struct ceph_blog_client_info *info; + + if (!buf || !size) + return -EINVAL; + + info = ceph_blog_get_client_info(client_id); + if (!info) { + return snprintf(buf, size, "[unknown_client_%u]", client_id); + } + + /* Use %pU to format fsid, matching doutc and other Ceph client logging */ + return snprintf(buf, size, "[%pU %llu] ", + info->fsid, info->global_id); +} +EXPORT_SYMBOL(ceph_blog_client_des_callback); + +/** + * ceph_blog_get_client_id - Get or allocate client ID for a Ceph client + * @client: Ceph client structure + * + * Return: Client ID for this client + */ +u32 ceph_blog_get_client_id(struct ceph_client *client) +{ + static u32 cached_id = 0; + + if (!client) + return 0; + + /* Use ceph_blog_check_client_id which handles caching internally */ + cached_id = ceph_blog_check_client_id(cached_id, + client->fsid.fsid, + client->monc.auth->global_id); + return cached_id; +} +EXPORT_SYMBOL(ceph_blog_get_client_id); diff --git a/fs/ceph/blog_debugfs.c b/fs/ceph/blog_debugfs.c new file mode 100644 index 0000000000000..147ca2be50138 --- /dev/null +++ b/fs/ceph/blog_debugfs.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Ceph BLOG debugfs interface + * + * Provides debugfs entries to view and manage BLOG entries for Ceph + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static struct dentry *ceph_blog_debugfs_dir; + +/** + * blog_entries_show - Show all BLOG entries for Ceph + * + * Iterates through all contexts and their pagefrags, deserializing entries + * using BLOG's deserialization with Ceph's client callback + */ +static int blog_entries_show(struct seq_file *s, void *p) +{ + struct blog_tls_ctx *ctx; + struct blog_log_iter iter; + struct blog_log_entry *entry; + char output_buf[1024]; + int ret; + int entry_count = 0; + int ctx_count = 0; + + seq_printf(s, "Ceph BLOG Entries\n"); + seq_printf(s, "=================\n\n"); + + /* Access the global logger - need to be careful here */ + spin_lock(&g_blog_logger.lock); + + list_for_each_entry(ctx, &g_blog_logger.contexts, list) { + ctx_count++; + seq_printf(s, "Context %d (ID: %llu, PID: %d, Comm: %s)\n", + ctx_count, ctx->id, ctx->pid, ctx->comm); + seq_printf(s, " Base jiffies: %lu, Refcount: %d\n", + ctx->base_jiffies, atomic_read(&ctx->refcount)); + + /* Initialize iterator for this context's pagefrag */ + blog_log_iter_init(&iter, &ctx->pf); + + /* Iterate through all entries in this context */ + while ((entry = blog_log_iter_next(&iter)) != NULL) { + entry_count++; + + /* Clear output buffer */ + memset(output_buf, 0, sizeof(output_buf)); + + /* Use blog_des_entry with Ceph's client callback */ + ret = blog_des_entry(entry, output_buf, sizeof(output_buf), + ceph_blog_client_des_callback); + + if (ret < 0) { + seq_printf(s, " Entry %d: [Error deserializing: %d]\n", + entry_count, ret); + } else { + /* Show entry details */ + seq_printf(s, " Entry %d (ts_delta=%u, src=%u, client=%u, len=%u):\n", + entry_count, entry->ts_delta, entry->source_id, + entry->client_id, entry->len); + seq_printf(s, " %s\n", output_buf); + } + } + seq_printf(s, "\n"); + } + + spin_unlock(&g_blog_logger.lock); + + seq_printf(s, "Total contexts: %d\n", ctx_count); + seq_printf(s, "Total entries: %d\n", entry_count); + + return 0; +} + +static int blog_entries_open(struct inode *inode, struct file *file) +{ + return single_open(file, blog_entries_show, inode->i_private); +} + +static const struct file_operations blog_entries_fops = { + .open = blog_entries_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * blog_stats_show - Show BLOG statistics + */ +static int blog_stats_show(struct seq_file *s, void *p) +{ + seq_printf(s, "Ceph BLOG Statistics\n"); + seq_printf(s, "====================\n\n"); + + seq_printf(s, "Global Logger State:\n"); + seq_printf(s, " Total contexts allocated: %lu\n", + g_blog_logger.total_contexts_allocated); + seq_printf(s, " Next context ID: %llu\n", g_blog_logger.next_ctx_id); + seq_printf(s, " Next source ID: %u\n", + atomic_read(&g_blog_logger.next_source_id)); + + seq_printf(s, "\nAllocation Batch:\n"); + seq_printf(s, " Full magazines: %u\n", g_blog_logger.alloc_batch.nr_full); + seq_printf(s, " Empty magazines: %u\n", g_blog_logger.alloc_batch.nr_empty); + + seq_printf(s, "\nLog Batch:\n"); + seq_printf(s, " Full magazines: %u\n", g_blog_logger.log_batch.nr_full); + seq_printf(s, " Empty magazines: %u\n", g_blog_logger.log_batch.nr_empty); + + return 0; +} + +static int blog_stats_open(struct inode *inode, struct file *file) +{ + return single_open(file, blog_stats_show, inode->i_private); +} + +static const struct file_operations blog_stats_fops = { + .open = blog_stats_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * blog_sources_show - Show all registered source locations + */ +static int blog_sources_show(struct seq_file *s, void *p) +{ + struct blog_source_info *source; + u32 id; + int count = 0; + + seq_printf(s, "Ceph BLOG Source Locations\n"); + seq_printf(s, "===========================\n\n"); + + for (id = 1; id < BLOG_MAX_SOURCE_IDS; id++) { + source = blog_get_source_info(id); + if (!source || !source->file) + continue; + + count++; + seq_printf(s, "ID %u: %s:%s:%u\n", id, + source->file, source->func, source->line); + seq_printf(s, " Format: %s\n", source->fmt ? source->fmt : "(null)"); + seq_printf(s, " Warnings: %d\n", source->warn_count); + +#if BLOG_TRACK_USAGE + seq_printf(s, " NAPI usage: %d calls, %d bytes\n", + atomic_read(&source->napi_usage), + atomic_read(&source->napi_bytes)); + seq_printf(s, " Task usage: %d calls, %d bytes\n", + atomic_read(&source->task_usage), + atomic_read(&source->task_bytes)); +#endif + seq_printf(s, "\n"); + } + + seq_printf(s, "Total registered sources: %d\n", count); + + return 0; +} + +static int blog_sources_open(struct inode *inode, struct file *file) +{ + return single_open(file, blog_sources_show, inode->i_private); +} + +static const struct file_operations blog_sources_fops = { + .open = blog_sources_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * blog_clients_show - Show all registered Ceph clients + */ +static int blog_clients_show(struct seq_file *s, void *p) +{ + u32 id; + int count = 0; + const struct ceph_blog_client_info *info; + + seq_printf(s, "Ceph BLOG Registered Clients\n"); + seq_printf(s, "=============================\n\n"); + + for (id = 1; id < CEPH_BLOG_MAX_CLIENTS; id++) { + info = ceph_blog_get_client_info(id); + if (!info || info->global_id == 0) + continue; + + count++; + + seq_printf(s, "Client ID %u:\n", id); + seq_printf(s, " FSID: %pU\n", info->fsid); + seq_printf(s, " Global ID: %llu\n", info->global_id); + seq_printf(s, "\n"); + } + + seq_printf(s, "Total registered clients: %d\n", count); + + return 0; +} + +static int blog_clients_open(struct inode *inode, struct file *file) +{ + return single_open(file, blog_clients_show, inode->i_private); +} + +static const struct file_operations blog_clients_fops = { + .open = blog_clients_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * blog_clear_write - Clear all BLOG entries (write-only) + */ +static ssize_t blog_clear_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char cmd[16]; + + if (count >= sizeof(cmd)) + return -EINVAL; + + if (copy_from_user(cmd, buf, count)) + return -EFAULT; + + cmd[count] = '\0'; + + /* Only accept "clear" command */ + if (strncmp(cmd, "clear", 5) != 0) + return -EINVAL; + + /* Reset all contexts - this is a simplified version */ + pr_info("ceph: BLOG entries cleared via debugfs\n"); + + return count; +} + +static const struct file_operations blog_clear_fops = { + .write = blog_clear_write, +}; + +/** + * ceph_blog_debugfs_init - Initialize Ceph BLOG debugfs entries + * @parent: Parent debugfs directory (usually ceph root) + * + * Return: 0 on success, negative error code on failure + */ +int ceph_blog_debugfs_init(struct dentry *parent) +{ + if (!parent) + return -EINVAL; + + /* Create blog subdirectory */ + ceph_blog_debugfs_dir = debugfs_create_dir("blog", parent); + if (!ceph_blog_debugfs_dir) + return -ENOMEM; + + /* Create debugfs entries */ + debugfs_create_file("entries", 0444, ceph_blog_debugfs_dir, NULL, + &blog_entries_fops); + + debugfs_create_file("stats", 0444, ceph_blog_debugfs_dir, NULL, + &blog_stats_fops); + + debugfs_create_file("sources", 0444, ceph_blog_debugfs_dir, NULL, + &blog_sources_fops); + + debugfs_create_file("clients", 0444, ceph_blog_debugfs_dir, NULL, + &blog_clients_fops); + + debugfs_create_file("clear", 0200, ceph_blog_debugfs_dir, NULL, + &blog_clear_fops); + + pr_info("ceph: BLOG debugfs initialized\n"); + return 0; +} +EXPORT_SYMBOL(ceph_blog_debugfs_init); + +/** + * ceph_blog_debugfs_cleanup - Clean up Ceph BLOG debugfs entries + */ +void ceph_blog_debugfs_cleanup(void) +{ + debugfs_remove_recursive(ceph_blog_debugfs_dir); + ceph_blog_debugfs_dir = NULL; + pr_info("ceph: BLOG debugfs cleaned up\n"); +} +EXPORT_SYMBOL(ceph_blog_debugfs_cleanup); diff --git a/fs/ceph/blog_integration_example.c b/fs/ceph/blog_integration_example.c new file mode 100644 index 0000000000000..ee54cf2a075a7 --- /dev/null +++ b/fs/ceph/blog_integration_example.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Example integration code showing how Ceph uses BLOG + * + * This demonstrates the transition from ceph_san to BLOG with preserved + * client ID mapping functionality. + */ + +#include +#include +#include +#include + +/* + * Example 1: Simple logging without client context (like dout) + * This doesn't store client_id + */ +void ceph_example_simple_log(void) +{ + int value = 42; + const char *status = "active"; + + /* Using BLOG for simple logging */ + CEPH_BLOG_LOG("Simple log: value=%d status=%s\n", value, status); + + /* Legacy compatibility - these map to BLOG */ + CEPH_SAN_LOG("Legacy simple log: value=%d\n", value); + + /* Traditional dout remains unchanged */ + dout("Traditional dout: value=%d\n", value); +} + +/* + * Example 2: Client-aware logging (like doutc and boutc) + * This stores client_id for later deserialization + */ +void ceph_example_client_log(struct ceph_client *client) +{ + struct ceph_osd_request *req; + u64 offset = 0x1000; + u64 length = 0x4000; + + if (!client) + return; + + /* Using BLOG with client context */ + CEPH_BLOG_LOG_CLIENT(client, "OSD request: offset=%llu length=%llu\n", + offset, length); + + /* Legacy compatibility for client logging */ + CEPH_SAN_LOG_CLIENT(client, "Legacy client log: offset=%llu\n", offset); + + /* Traditional doutc - shows [fsid global_id] in text logs */ + doutc(client, "Traditional doutc: processing request\n"); + + /* boutc uses BLOG internally with client context */ + boutc(client, "Binary log with client: offset=%llu length=%llu\n", + offset, length); +} + +/* + * Example 3: Demonstrating client ID mapping preservation + * + * The client_id mapping is now handled by Ceph, not BLOG. + * This preserves the exact functionality of ceph_san_check_client_id. + */ +void ceph_example_client_id_mapping(struct ceph_client *client) +{ + u32 client_id; + const struct ceph_blog_client_info *info; + + if (!client) + return; + + /* Get or allocate client ID for this Ceph client */ + client_id = ceph_blog_get_client_id(client); + + CEPH_BLOG_LOG_CLIENT(client, + "Client registered with ID %u\n", client_id); + + /* The mapping is preserved in Ceph's blog_client.c */ + info = ceph_blog_get_client_info(client_id); + if (info) { + pr_info("Client %u maps to fsid=%pU global_id=%llu\n", + client_id, info->fsid, info->global_id); + } +} + +/* + * Example 4: Debugfs integration + * + * The debugfs interface uses BLOG's deserialization with Ceph's + * client callback to reconstruct the full log entries. + */ +void ceph_example_debugfs_usage(void) +{ + /* + * Debugfs files created by ceph_blog_debugfs_init(): + * + * /sys/kernel/debug/ceph/blog/entries + * - Shows all BLOG entries with client info deserialized + * - Uses ceph_blog_client_des_callback to format [fsid gid] + * + * /sys/kernel/debug/ceph/blog/stats + * - Shows BLOG statistics + * + * /sys/kernel/debug/ceph/blog/sources + * - Shows all registered source locations + * + * /sys/kernel/debug/ceph/blog/clients + * - Shows all registered Ceph clients with their mappings + * + * /sys/kernel/debug/ceph/blog/clear + * - Write-only file to clear all BLOG entries + */ + pr_info("Debugfs available at /sys/kernel/debug/ceph/blog/\n"); +} + +/* + * Example 5: Module initialization with BLOG + */ +static int __init ceph_blog_example_init(void) +{ + int ret; + + /* Initialize Ceph's BLOG integration */ + ret = ceph_blog_init(); + if (ret) { + pr_err("Failed to initialize Ceph BLOG integration: %d\n", ret); + return ret; + } + + pr_info("Ceph BLOG integration example loaded\n"); + + /* Note: In real usage, blog_init() would be called by BLOG module + * and ceph_blog_init() would be called by Ceph FS module init + */ + + return 0; +} + +static void __exit ceph_blog_example_exit(void) +{ + /* Clean up Ceph's BLOG integration */ + ceph_blog_cleanup(); + + pr_info("Ceph BLOG integration example unloaded\n"); +} + +module_init(ceph_blog_example_init); +module_exit(ceph_blog_example_exit); + +MODULE_DESCRIPTION("Ceph BLOG Integration Example"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ceph Development Team"); diff --git a/include/linux/blog/blog.h b/include/linux/blog/blog.h index da61987e0447c..12c9006f2f0ca 100644 --- a/include/linux/blog/blog.h +++ b/include/linux/blog/blog.h @@ -105,7 +105,7 @@ struct blog_logger { unsigned long total_contexts_allocated; u64 next_ctx_id; /* Next context ID to assign */ spinlock_t ctx_id_lock; /* Protects context ID counter */ - struct blog_tls_ctx __percpu *napi_ctxs; /* Per-CPU NAPI contexts */ + struct blog_tls_ctx * __percpu *napi_ctxs; /* Per-CPU NAPI context pointers */ }; /* Iterator for log entries in a single pagefrag */ diff --git a/include/linux/blog/blog_module.h b/include/linux/blog/blog_module.h new file mode 100644 index 0000000000000..ac8e8a2410fe4 --- /dev/null +++ b/include/linux/blog/blog_module.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Binary Logging Infrastructure (BLOG) - Per-Module Support + * + * This header defines the per-module context support for BLOG. + * Each kernel module can have its own isolated logging context. + */ +#ifndef _LINUX_BLOG_MODULE_H +#define _LINUX_BLOG_MODULE_H + +#include + +/* Per-module context structure */ +struct blog_module_context { + char name[32]; /* Module name */ + struct blog_logger *logger; /* Logger instance for this module */ + void *module_private; /* Module-specific private data */ + struct list_head list; /* List of all module contexts */ + atomic_t refcount; /* Reference count */ + bool initialized; /* Initialization status */ +}; + +/* Module context management API */ +struct blog_module_context *blog_module_init(const char *module_name); +void blog_module_cleanup(struct blog_module_context *ctx); +void blog_module_get(struct blog_module_context *ctx); +void blog_module_put(struct blog_module_context *ctx); + +/* Per-module API functions */ +u32 blog_get_source_id_ctx(struct blog_module_context *ctx, const char *file, + const char *func, unsigned int line, const char *fmt); +struct blog_source_info *blog_get_source_info_ctx(struct blog_module_context *ctx, u32 id); +void* blog_log_ctx(struct blog_module_context *ctx, u32 source_id, u8 client_id, size_t needed_size); +struct blog_tls_ctx *blog_get_tls_ctx_ctx(struct blog_module_context *ctx); +struct blog_tls_ctx *blog_get_napi_ctx_ctx(struct blog_module_context *ctx); +void blog_set_napi_ctx_ctx(struct blog_module_context *ctx, struct blog_tls_ctx *tls_ctx); +struct blog_tls_ctx *blog_get_ctx_ctx(struct blog_module_context *ctx); +int blog_log_trim_ctx(struct blog_module_context *ctx, unsigned int n); + +/* Helper macros for per-module logging */ +#define __BLOG_LOG_CTX(__ctx, dbg, __client_id, fmt, ...) \ + do { \ + static u32 __source_id = 0; \ + static size_t __size = 0; \ + void *___buffer = NULL; \ + if (unlikely(__source_id == 0)) { \ + __source_id = blog_get_source_id_ctx(__ctx, kbasename(__FILE__), __func__, __LINE__, fmt); \ + __size = blog_cnt(__VA_ARGS__); \ + } \ + ___buffer = blog_log_ctx(__ctx, __source_id, __client_id, __size); \ + if (likely(___buffer) && __size > 0) { \ + void *___tmp = ___buffer; \ + size_t actual_size; \ + blog_ser(___buffer, ##__VA_ARGS__);\ + actual_size = ___buffer - ___tmp; \ + blog_log_trim_ctx(__ctx, __size - actual_size); \ + } \ + } while (0) + +/* Per-module logging macros */ +#define BLOG_LOG_CTX(ctx, fmt, ...) \ + __BLOG_LOG_CTX(ctx, 0, 0, fmt, ##__VA_ARGS__) + +#define BLOG_LOG_CLIENT_CTX(ctx, client_id, fmt, ...) \ + __BLOG_LOG_CTX(ctx, 0, client_id, fmt, ##__VA_ARGS__) + +#endif /* _LINUX_BLOG_MODULE_H */ diff --git a/include/linux/blog/blog_ser.h b/include/linux/blog/blog_ser.h index f2b0a69f1603a..bf136412dbcbf 100644 --- a/include/linux/blog/blog_ser.h +++ b/include/linux/blog/blog_ser.h @@ -100,6 +100,9 @@ #define char_ptr(str) __suppress_cast_warning(char *, (str)) +#ifndef _CEPH_BLOG_SER_HELPERS_DEFINED +#define _CEPH_BLOG_SER_HELPERS_DEFINED + union null_str_u { char str[8]; unsigned long force_align; @@ -160,6 +163,8 @@ static inline void* strscpy_n_update(char *dst, const char *src, const char *fil return dst + round_up(ret, 4); } +#endif /* _CEPH_BLOG_SER_HELPERS_DEFINED */ + /* Serialization type macro */ #define __blog_ser_type(__buffer, __t) \ (__builtin_choose_expr((IS_DYNAMIC_CHAR_PTR((__t)) || IS_STATIC_CHAR_ARRAY((__t))), \ diff --git a/include/linux/ceph/ceph_blog.h b/include/linux/ceph/ceph_blog.h new file mode 100644 index 0000000000000..dd35a50aace40 --- /dev/null +++ b/include/linux/ceph/ceph_blog.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Ceph integration with BLOG (Binary LOGging) + * + * Provides compatibility layer and Ceph-specific extensions + */ +#ifndef CEPH_BLOG_H +#define CEPH_BLOG_H + +#include +#include +#include + +/* Client ID mapping structure - preserves ceph_san_client_id fields */ +struct ceph_blog_client_info { + char fsid[16]; /* Client FSID */ + u64 global_id; /* Client global ID */ +}; + +/* Constants */ +#define CEPH_BLOG_MAX_CLIENTS 256 + +/* Ceph's BLOG module context */ +extern struct blog_module_context *ceph_blog_ctx; + +/* Ceph BLOG client management functions */ +int ceph_blog_init(void); +void ceph_blog_cleanup(void); +u32 ceph_blog_check_client_id(u32 id, const char *fsid, u64 global_id); +u32 ceph_blog_get_client_id(struct ceph_client *client); +const struct ceph_blog_client_info *ceph_blog_get_client_info(u32 id); +int ceph_blog_client_des_callback(char *buf, size_t size, u8 client_id); + +/* Compatibility macros for easy migration from ceph_san to BLOG */ +#if defined(CONFIG_BLOG) || defined(CONFIG_BLOG_MODULE) + +/* Direct mappings to BLOG functions */ +#define ceph_san_logger_init() blog_init() +#define ceph_san_logger_cleanup() blog_cleanup() +#define ceph_san_get_source_id blog_get_source_id +#define ceph_san_get_source_info blog_get_source_info +#define ceph_san_log blog_log +#define ceph_san_get_tls_ctx() blog_get_tls_ctx() +#define ceph_san_get_napi_ctx() blog_get_napi_ctx() +#define ceph_san_set_napi_ctx(ctx) blog_set_napi_ctx(ctx) +#define ceph_san_get_ctx() blog_get_ctx() +#define ceph_san_log_trim blog_log_trim + +/* Structure mappings */ +#define ceph_san_logger blog_logger +#define ceph_san_log_entry blog_log_entry +#define ceph_san_tls_ctx blog_tls_ctx +#define ceph_san_source_info blog_source_info +#define ceph_san_log_iter blog_log_iter + +/* + * Ceph-specific logging macros - use Ceph's module context + * Note: Only client-aware macros (doutc, boutc) store client_id, + * regular macros (dout, bout) do not include client information + */ +#define CEPH_BLOG_LOG(fmt, ...) \ + do { \ + if (ceph_blog_ctx) \ + BLOG_LOG_CTX(ceph_blog_ctx, fmt, ##__VA_ARGS__); \ + } while (0) + +#define CEPH_BLOG_LOG_CLIENT(client, fmt, ...) \ + do { \ + if (ceph_blog_ctx) { \ + u32 __client_id = ceph_blog_get_client_id(client); \ + BLOG_LOG_CLIENT_CTX(ceph_blog_ctx, __client_id, fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +/* Legacy compatibility - maps old ceph_san macros to BLOG */ +/* Only define if not already defined by ceph_san_logger.h */ +#ifndef CEPH_SAN_LOG +#define CEPH_SAN_LOG(fmt, ...) \ + CEPH_BLOG_LOG(fmt, ##__VA_ARGS__) +#endif + +#ifndef CEPH_SAN_LOG_CLIENT +#define CEPH_SAN_LOG_CLIENT(client, fmt, ...) \ + CEPH_BLOG_LOG_CLIENT(client, fmt, ##__VA_ARGS__) +#endif + +#else /* !CONFIG_BLOG */ + +/* Stub macros when BLOG is not enabled */ +#define CEPH_BLOG_LOG(fmt, ...) do {} while (0) +#define CEPH_BLOG_LOG_CLIENT(client, fmt, ...) do {} while (0) +#define CEPH_SAN_LOG(fmt, ...) do {} while (0) +#define CEPH_SAN_LOG_CLIENT(client, fmt, ...) do {} while (0) + +/* Stub functions should be static inline, not macros */ +static inline int ceph_blog_init(void) { return 0; } +static inline void ceph_blog_cleanup(void) { } +static inline u32 ceph_blog_get_client_id(struct ceph_client *client) { return 0; } +static inline u32 ceph_blog_check_client_id(u32 id, const char *fsid, u64 global_id) { return 0; } +static inline const struct ceph_blog_client_info *ceph_blog_get_client_info(u32 id) { return NULL; } +static inline int ceph_blog_client_des_callback(char *buf, size_t size, u8 client_id) { return 0; } + +#endif /* CONFIG_BLOG */ + +/* Debugfs support */ +#ifdef CONFIG_DEBUG_FS +int ceph_blog_debugfs_init(struct dentry *parent); +void ceph_blog_debugfs_cleanup(void); +#else +static inline int ceph_blog_debugfs_init(struct dentry *parent) { return 0; } +static inline void ceph_blog_debugfs_cleanup(void) {} +#endif + +#endif /* CEPH_BLOG_H */ diff --git a/include/linux/ceph/ceph_san_ser.h b/include/linux/ceph/ceph_san_ser.h index 365e31b5d2d57..a65e399c4b15b 100644 --- a/include/linux/ceph/ceph_san_ser.h +++ b/include/linux/ceph/ceph_san_ser.h @@ -101,6 +101,9 @@ #define char_ptr(str) __suppress_cast_warning(char *, (str)) +#ifndef _CEPH_BLOG_SER_HELPERS_DEFINED +#define _CEPH_BLOG_SER_HELPERS_DEFINED + union null_str_u { char str[8]; unsigned long force_align; @@ -161,6 +164,8 @@ static inline void* strscpy_n_update(char *dst, const char *src, const char *fil return dst + round_up(ret, 4); } +#endif /* _CEPH_BLOG_SER_HELPERS_DEFINED */ + #define __ceph_san_ser_type(__buffer, __t) \ (__builtin_choose_expr((IS_DYNAMIC_CHAR_PTR((__t)) || IS_STATIC_CHAR_ARRAY((__t))), \ /* For static arrays (like __func__), just save pointer */ \ diff --git a/lib/blog/Makefile b/lib/blog/Makefile index 7010fb56372b7..8c33a2a6e9d5c 100644 --- a/lib/blog/Makefile +++ b/lib/blog/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_BLOG) += blog.o -blog-y := blog_core.o blog_batch.o blog_pagefrag.o blog_des.o +blog-y := blog_core.o blog_batch.o blog_pagefrag.o blog_des.o blog_module.o # Debug support # blog-$(CONFIG_BLOG_DEBUG) += blog_debug.o diff --git a/lib/blog/blog_module.c b/lib/blog/blog_module.c new file mode 100644 index 0000000000000..d5ce2e305dd5a --- /dev/null +++ b/lib/blog/blog_module.c @@ -0,0 +1,423 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Binary Logging Infrastructure (BLOG) - Per-Module Support + * + * Implements per-module context management for isolated logging. + */ + +#include +#include +#include +#include +#include +#include + +/* Global list of all module contexts */ +static LIST_HEAD(blog_module_contexts); +static DEFINE_SPINLOCK(blog_modules_lock); + +/** + * blog_module_init - Initialize a per-module BLOG context + * @module_name: Name of the module + * + * Creates an isolated logging context for a specific module. + * + * Return: Module context on success, NULL on failure + */ +struct blog_module_context *blog_module_init(const char *module_name) +{ + struct blog_module_context *ctx; + struct blog_logger *logger; + int i; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + logger = kzalloc(sizeof(*logger), GFP_KERNEL); + if (!logger) { + kfree(ctx); + return NULL; + } + + /* Initialize module context */ + strscpy(ctx->name, module_name, sizeof(ctx->name)); + ctx->logger = logger; + atomic_set(&ctx->refcount, 1); + ctx->initialized = true; + INIT_LIST_HEAD(&ctx->list); + + /* Initialize logger instance */ + INIT_LIST_HEAD(&logger->contexts); + spin_lock_init(&logger->lock); + spin_lock_init(&logger->source_lock); + spin_lock_init(&logger->ctx_id_lock); + atomic_set(&logger->next_source_id, 1); + logger->next_ctx_id = 1; + logger->total_contexts_allocated = 0; + + /* Initialize batches */ + blog_batch_init(&logger->alloc_batch); + blog_batch_init(&logger->log_batch); + + /* Initialize source map */ + for (i = 0; i < BLOG_MAX_SOURCE_IDS; i++) { + memset(&logger->source_map[i], 0, sizeof(logger->source_map[i])); + } + + /* Allocate per-CPU NAPI context pointers */ + logger->napi_ctxs = alloc_percpu(struct blog_tls_ctx *); + if (!logger->napi_ctxs) { + kfree(logger); + kfree(ctx); + return NULL; + } + + /* Add to global list */ + spin_lock(&blog_modules_lock); + list_add(&ctx->list, &blog_module_contexts); + spin_unlock(&blog_modules_lock); + + pr_info("BLOG: Module context initialized for %s\n", module_name); + return ctx; +} +EXPORT_SYMBOL(blog_module_init); + +/** + * blog_module_cleanup - Clean up a module's BLOG context + * @ctx: Module context to clean up + */ +void blog_module_cleanup(struct blog_module_context *ctx) +{ + struct blog_logger *logger; + struct blog_tls_ctx *tls_ctx, *tmp; + + if (!ctx || !ctx->initialized) + return; + + logger = ctx->logger; + if (!logger) + return; + + /* Remove from global list */ + spin_lock(&blog_modules_lock); + list_del(&ctx->list); + spin_unlock(&blog_modules_lock); + + /* Clean up all TLS contexts */ + spin_lock(&logger->lock); + list_for_each_entry_safe(tls_ctx, tmp, &logger->contexts, list) { + list_del(&tls_ctx->list); + /* Call release function if set */ + if (tls_ctx->release) + tls_ctx->release(tls_ctx); + kfree(tls_ctx); + } + spin_unlock(&logger->lock); + + /* Clean up batches */ + blog_batch_cleanup(&logger->alloc_batch); + blog_batch_cleanup(&logger->log_batch); + + /* Free per-CPU NAPI contexts */ + if (logger->napi_ctxs) + free_percpu(logger->napi_ctxs); + + pr_info("BLOG: Module context cleaned up for %s\n", ctx->name); + + kfree(logger); + kfree(ctx); +} +EXPORT_SYMBOL(blog_module_cleanup); + +/** + * blog_module_get - Increment module context reference count + * @ctx: Module context + */ +void blog_module_get(struct blog_module_context *ctx) +{ + if (ctx) + atomic_inc(&ctx->refcount); +} +EXPORT_SYMBOL(blog_module_get); + +/** + * blog_module_put - Decrement module context reference count + * @ctx: Module context + */ +void blog_module_put(struct blog_module_context *ctx) +{ + if (ctx && atomic_dec_and_test(&ctx->refcount)) + blog_module_cleanup(ctx); +} +EXPORT_SYMBOL(blog_module_put); + +/* Per-module API implementations */ + +/** + * blog_get_source_id_ctx - Get or allocate source ID for a module context + * @ctx: Module context + * @file: Source file name + * @func: Function name + * @line: Line number + * @fmt: Format string + * + * Return: Source ID + */ +u32 blog_get_source_id_ctx(struct blog_module_context *ctx, const char *file, + const char *func, unsigned int line, const char *fmt) +{ + struct blog_logger *logger; + struct blog_source_info *info; + u32 id; + + if (!ctx || !ctx->logger) + return 0; + + logger = ctx->logger; + + /* Get next ID */ + id = atomic_fetch_inc(&logger->next_source_id); + if (id >= BLOG_MAX_SOURCE_IDS) { + pr_warn("BLOG: Source ID overflow in module %s\n", ctx->name); + return 0; + } + + /* Fill in source info */ + spin_lock(&logger->source_lock); + info = &logger->source_map[id]; + info->file = file; + info->func = func; + info->line = line; + info->fmt = fmt; + info->warn_count = 0; +#if BLOG_TRACK_USAGE + atomic_set(&info->napi_usage, 0); + atomic_set(&info->task_usage, 0); + atomic_set(&info->napi_bytes, 0); + atomic_set(&info->task_bytes, 0); +#endif + spin_unlock(&logger->source_lock); + + return id; +} +EXPORT_SYMBOL(blog_get_source_id_ctx); + +/** + * blog_get_source_info_ctx - Get source info for an ID in a module context + * @ctx: Module context + * @id: Source ID + * + * Return: Source info or NULL + */ +struct blog_source_info *blog_get_source_info_ctx(struct blog_module_context *ctx, u32 id) +{ + struct blog_logger *logger; + + if (!ctx || !ctx->logger || id >= BLOG_MAX_SOURCE_IDS) + return NULL; + + logger = ctx->logger; + return &logger->source_map[id]; +} +EXPORT_SYMBOL(blog_get_source_info_ctx); + +/** + * blog_get_tls_ctx_ctx - Get or create TLS context for a module + * @ctx: Module context + * + * Return: TLS context or NULL + */ +struct blog_tls_ctx *blog_get_tls_ctx_ctx(struct blog_module_context *ctx) +{ + struct blog_logger *logger; + struct blog_tls_ctx *tls_ctx; + struct task_struct *task = current; + + if (!ctx || !ctx->logger) + return NULL; + + logger = ctx->logger; + + /* For now, always create a new context per call + * TODO: Implement proper per-task TLS storage */ + + /* Allocate new TLS context */ + tls_ctx = kzalloc(sizeof(*tls_ctx), GFP_KERNEL); + if (!tls_ctx) + return NULL; + + /* Initialize TLS context */ + INIT_LIST_HEAD(&tls_ctx->list); + atomic_set(&tls_ctx->refcount, 1); + tls_ctx->task = task; + tls_ctx->pid = task->pid; + get_task_comm(tls_ctx->comm, task); + tls_ctx->base_jiffies = jiffies; + + /* Initialize pagefrag */ + blog_pagefrag_init(&tls_ctx->pf); + + /* Get unique context ID */ + spin_lock(&logger->ctx_id_lock); + tls_ctx->id = logger->next_ctx_id++; + spin_unlock(&logger->ctx_id_lock); + +#if BLOG_DEBUG_POISON + tls_ctx->debug_poison = BLOG_CTX_POISON; +#endif + + /* Add to logger's context list */ + spin_lock(&logger->lock); + list_add(&tls_ctx->list, &logger->contexts); + logger->total_contexts_allocated++; + spin_unlock(&logger->lock); + + /* TODO: Store in task-specific storage */ + + return tls_ctx; +} +EXPORT_SYMBOL(blog_get_tls_ctx_ctx); + +/** + * blog_log_ctx - Log a message with module context + * @ctx: Module context + * @source_id: Source ID + * @client_id: Client ID + * @needed_size: Size needed for the log entry + * + * Return: Buffer to write log data to, or NULL on failure + */ +void* blog_log_ctx(struct blog_module_context *ctx, u32 source_id, + u8 client_id, size_t needed_size) +{ + struct blog_tls_ctx *tls_ctx; + struct blog_log_entry *entry; + int alloc; + size_t total_size; + + if (!ctx || !ctx->logger) + return NULL; + + /* Get TLS context */ + tls_ctx = blog_get_tls_ctx_ctx(ctx); + if (!tls_ctx) + return NULL; + + /* Calculate total size needed */ + total_size = sizeof(*entry) + needed_size; + if (total_size > BLOG_LOG_MAX_LEN) { + pr_warn_once("BLOG: Log entry too large (%zu > %d) in module %s\n", + total_size, BLOG_LOG_MAX_LEN, ctx->name); + return NULL; + } + + /* Allocate space from pagefrag */ + alloc = blog_pagefrag_alloc(&tls_ctx->pf, total_size); + if (alloc == -ENOMEM) { + pr_debug("blog_log_ctx: allocation failed for module %s\n", ctx->name); + blog_pagefrag_reset(&tls_ctx->pf); + return NULL; + } + + /* Get pointer from allocation */ + entry = blog_pagefrag_get_ptr(&tls_ctx->pf, alloc); + if (!entry) { + pr_err("blog_log_ctx: failed to get pointer from pagefrag\n"); + return NULL; + } + + /* Fill in entry header */ +#if BLOG_DEBUG_POISON + entry->debug_poison = BLOG_LOG_ENTRY_POISON; +#endif + entry->ts_delta = jiffies - tls_ctx->base_jiffies; + entry->source_id = source_id; + entry->client_id = client_id; + entry->len = needed_size; + + /* Return pointer to buffer area */ + return entry->buffer; +} +EXPORT_SYMBOL(blog_log_ctx); + +/** + * blog_log_trim_ctx - Trim unused space from last log entry + * @ctx: Module context + * @n: Number of bytes to trim + * + * Return: 0 on success, negative on error + */ +int blog_log_trim_ctx(struct blog_module_context *ctx, unsigned int n) +{ + struct blog_tls_ctx *tls_ctx; + + if (!ctx || !ctx->logger) + return -EINVAL; + + tls_ctx = blog_get_tls_ctx_ctx(ctx); + if (!tls_ctx) + return -EINVAL; + + blog_pagefrag_trim(&tls_ctx->pf, n); + return 0; +} +EXPORT_SYMBOL(blog_log_trim_ctx); + +/** + * blog_get_ctx_ctx - Get appropriate context based on execution context + * @ctx: Module context + * + * Return: TLS context or NAPI context depending on execution context + */ +struct blog_tls_ctx *blog_get_ctx_ctx(struct blog_module_context *ctx) +{ + if (in_serving_softirq()) + return blog_get_napi_ctx_ctx(ctx); + return blog_get_tls_ctx_ctx(ctx); +} +EXPORT_SYMBOL(blog_get_ctx_ctx); + +/** + * blog_get_napi_ctx_ctx - Get NAPI context for current CPU + * @ctx: Module context + * + * Return: NAPI context or NULL + */ +struct blog_tls_ctx *blog_get_napi_ctx_ctx(struct blog_module_context *ctx) +{ + struct blog_logger *logger; + struct blog_tls_ctx **napi_ctx_ptr; + + if (!ctx || !ctx->logger) + return NULL; + + logger = ctx->logger; + if (!logger->napi_ctxs) + return NULL; + + /* Get pointer to the percpu pointer */ + napi_ctx_ptr = per_cpu_ptr(logger->napi_ctxs, smp_processor_id()); + return *napi_ctx_ptr; +} +EXPORT_SYMBOL(blog_get_napi_ctx_ctx); + +/** + * blog_set_napi_ctx_ctx - Set NAPI context for current CPU + * @ctx: Module context + * @tls_ctx: TLS context to set + */ +void blog_set_napi_ctx_ctx(struct blog_module_context *ctx, struct blog_tls_ctx *tls_ctx) +{ + struct blog_logger *logger; + struct blog_tls_ctx **napi_ctx_ptr; + + if (!ctx || !ctx->logger || !ctx->logger->napi_ctxs) + return; + + logger = ctx->logger; + /* Get pointer to the percpu pointer and set it */ + napi_ctx_ptr = per_cpu_ptr(logger->napi_ctxs, smp_processor_id()); + *napi_ctx_ptr = tls_ctx; +} +EXPORT_SYMBOL(blog_set_napi_ctx_ctx); -- 2.39.5