From c0e31d2eb7b6d446c87cab8b18a8b8257b4ba3e8 Mon Sep 17 00:00:00 2001 From: Mathieu Desnoyers Date: Mon, 29 Nov 2010 09:49:09 -0500 Subject: [PATCH] ABI update Signed-off-by: Mathieu Desnoyers --- ltt-debugfs-abi.c | 102 +++++++++++++++++---------------- ltt-events.c | 104 ++++++++++++++++++++++++++++++---- ltt-events.h | 24 ++++++++ ltt-ring-buffer-client.h | 119 ++------------------------------------- ltt-tracer.h | 69 +---------------------- 5 files changed, 180 insertions(+), 238 deletions(-) diff --git a/ltt-debugfs-abi.c b/ltt-debugfs-abi.c index 4717b81e..e76c7169 100644 --- a/ltt-debugfs-abi.c +++ b/ltt-debugfs-abi.c @@ -57,7 +57,7 @@ static int lttng_abi_create_session(void) { struct ltt_session *session; - struct file *session_filp; + struct file *session_file; int session_fd; session = ltt_session_create(); @@ -68,15 +68,15 @@ int lttng_abi_create_session(void) ret = session_fd; goto fd_error; } - session_filp = anon_inode_getfile("[lttng_session]", + session_file = anon_inode_getfile("[lttng_session]", <tng_session_fops, session, O_RDWR); - if (IS_ERR(session_filp)) { - ret = PTR_ERR(session_filp); + if (IS_ERR(session_file)) { + ret = PTR_ERR(session_file); goto file_error; } - session->file = session_filp; - fd_install(session_fd, session_filp); + session->file = session_file; + fd_install(session_fd, session_file); return session_fd; file_error: @@ -89,7 +89,7 @@ fd_error: /** * lttng_ioctl - lttng syscall through ioctl * - * @filp: the file + * @file: the file * @cmd: the command * @arg: command arg * @@ -100,7 +100,7 @@ fd_error: * The returned session will be deleted when its file descriptor is closed. */ static -long lttng_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +long lttng_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case LTTNG_SESSION: @@ -117,12 +117,12 @@ static const struct file_operations lttng_fops = { #endif } -int lttng_abi_create_channel(struct file *session_filp, +int lttng_abi_create_channel(struct file *session_file, struct lttng_channel __user *uchan_param) { - struct ltt_session *session = session_filp->private_data; + struct ltt_session *session = session_file->private_data; struct ltt_channel *chan; - struct file *chan_filp; + struct file *chan_file; struct lttng_channel chan_param; int chan_fd; int ret = 0; @@ -134,11 +134,11 @@ int lttng_abi_create_channel(struct file *session_filp, ret = chan_fd; goto fd_error; } - chan_filp = anon_inode_getfile("[lttng_channel]", + chan_file = anon_inode_getfile("[lttng_channel]", <tng_channel_fops, NULL, O_RDWR); - if (IS_ERR(chan_filp)) { - ret = PTR_ERR(chan_filp); + if (IS_ERR(chan_file)) { + ret = PTR_ERR(chan_file); goto file_error; } /* @@ -154,16 +154,16 @@ int lttng_abi_create_channel(struct file *session_filp, ret = -ENOMEM; goto chan_error; } - channel->file = chan_filp; - chan_filp->private_data = chan; - fd_install(chan_fd, chan_filp); + channel->file = chan_file; + chan_file->private_data = chan; + fd_install(chan_fd, chan_file); /* The channel created holds a reference on the session */ - atomic_inc(&session_filp->f_count); + atomic_inc(&session_file->f_count); return chan_fd; chan_error: - fput(chan_filp); + fput(chan_file); file_error: put_unused_fd(chan_fd); fd_error: @@ -173,7 +173,7 @@ fd_error: /** * lttng_session_ioctl - lttng session fd ioctl * - * @filp: the file + * @file: the file * @cmd: the command * @arg: command arg * @@ -184,11 +184,19 @@ fd_error: * The returned channel will be deleted when its file descriptor is closed. */ static -long lttng_session_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +long lttng_session_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct ltt_session *session = file->private_data; + switch (cmd) { case LTTNG_CHANNEL: - return lttng_abi_create_channel(filp, (struct lttng_channel __user *)arg); + return lttng_abi_create_channel(file, (struct lttng_channel __user *)arg); + case LTTNG_SESSION_START: + return ltt_session_start(session); + case LTTNG_SESSION_STOP: + return ltt_session_stop(session); + case LTTNG_SESSION_FINALIZE: + return ltt_session_finalize(session); default: return -ENOIOCTLCMD; } @@ -218,9 +226,9 @@ static const struct file_operations lttng_session_fops = { } static -int lttng_abi_open_stream(struct file *channel_filp) +int lttng_abi_open_stream(struct file *channel_file) { - struct ltt_channel *channel = channel_filp->private_data; + struct ltt_channel *channel = channel_file->private_data; struct lib_ring_buffer *buf; int stream_fd, ret; @@ -233,16 +241,16 @@ int lttng_abi_open_stream(struct file *channel_filp) ret = stream_fd; goto fd_error; } - stream_filp = anon_inode_getfile("[lttng_stream]", + stream_file = anon_inode_getfile("[lttng_stream]", &lib_ring_buffer_file_operations, buf, O_RDWR); - if (IS_ERR(stream_filp)) { - ret = PTR_ERR(stream_filp); + if (IS_ERR(stream_file)) { + ret = PTR_ERR(stream_file); goto file_error; } - fd_install(stream_fd, stream_filp); + fd_install(stream_fd, stream_file); /* The stream holds a reference on the channel */ - atomic_inc(&channel_filp->f_count); + atomic_inc(&channel_file->f_count); return stream_fd; file_error: @@ -253,10 +261,10 @@ fd_error: } static -int lttng_abi_create_event(struct file *channel_filp, +int lttng_abi_create_event(struct file *channel_file, struct lttng_event __user *uevent_param) { - struct ltt_channel *channel = channel_filp->private_data; + struct ltt_channel *channel = channel_file->private_data; struct ltt_event *event; char *event_name; struct lttng_event event_param; @@ -277,11 +285,11 @@ int lttng_abi_create_event(struct file *channel_filp, ret = event_fd; goto fd_error; } - event_filp = anon_inode_getfile("[lttng_event]", + event_file = anon_inode_getfile("[lttng_event]", <tng_event_fops, /* TODO: filter */ NULL, O_RDWR); - if (IS_ERR(event_filp)) { - ret = PTR_ERR(event_filp); + if (IS_ERR(event_file)) { + ret = PTR_ERR(event_file); goto file_error; } /* @@ -293,15 +301,15 @@ int lttng_abi_create_event(struct file *channel_filp, goto event_error; ret = -EEXIST; } - event_filp->private_data = event; - fd_install(event_fd, event_filp); + event_file->private_data = event; + fd_install(event_fd, event_file); /* The event holds a reference on the channel */ - atomic_inc(&channel_filp->f_count); + atomic_inc(&channel_file->f_count); kfree(event_name); return event_fd; event_error: - fput(event_filp); + fput(event_file); file_error: put_unused_fd(event_fd); fd_error: @@ -313,7 +321,7 @@ name_error: /** * lttng_channel_ioctl - lttng syscall through ioctl * - * @filp: the file + * @file: the file * @cmd: the command * @arg: command arg * @@ -328,13 +336,13 @@ name_error: * Channel and event file descriptors also hold a reference on the session. */ static -long lttng_channel_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +long lttng_channel_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { case LTTNG_STREAM: - return lttng_abi_open_stream(filp); + return lttng_abi_open_stream(file); case LTTNG_EVENT: - return lttng_abi_create_event(filp, (struct lttng_event __user *)arg); + return lttng_abi_create_event(file, (struct lttng_event __user *)arg); default: return -ENOIOCTLCMD; } @@ -343,17 +351,17 @@ long lttng_channel_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) /** * lttng_channel_poll - lttng stream addition/removal monitoring * - * @filp: the file + * @file: the file * @wait: poll table */ -unsigned int lttng_channel_poll(struct file *filp, poll_table *wait) +unsigned int lttng_channel_poll(struct file *file, poll_table *wait) { - struct ltt_channel *channel = filp->private_data; + struct ltt_channel *channel = file->private_data; unsigned int mask = 0; - if (filp->f_mode & FMODE_READ) { + if (file->f_mode & FMODE_READ) { poll_wait_set_exclusive(wait); - poll_wait(filp, &channel->notify_wait, wait); + poll_wait(file, &channel->notify_wait, wait); /* TODO: identify when the channel is being finalized. */ if (finalized) diff --git a/ltt-events.c b/ltt-events.c index 3060c07e..d6cb397e 100644 --- a/ltt-events.c +++ b/ltt-events.c @@ -7,9 +7,13 @@ */ #include +#include +#include +#include #include "ltt-events.h" static LIST_HEAD(sessions); +static LIST_HEAD(ltt_transport_list); static DEFINE_MUTEX(sessions_mutex); static struct kmem_cache *event_cache; @@ -56,6 +60,38 @@ int ltt_session_destroy(struct ltt_session *session) kfree(session); } +int ltt_session_start(struct ltt_session *session) +{ + int ret = 0; + + mutex_lock(&sessions_mutex); + if (session->active) { + ret = -EBUSY; + goto end; + } + session->active = 1; + synchronize_trace(); /* Wait for in-flight events to complete */ +end: + mutex_unlock(&sessions_mutex); + return ret +} + +int ltt_session_stop(struct ltt_session *session) +{ + int ret = 0; + + mutex_lock(&sessions_mutex); + if (!session->active) { + ret = -EBUSY; + goto end; + } + session->active = 0; + synchronize_trace(); /* Wait for in-flight events to complete */ +end: + mutex_unlock(&sessions_mutex); + return ret +} + struct ltt_channel *ltt_channel_create(struct ltt_session *session, int overwrite, void *buf_addr, size_t subbuf_size, size_t num_subbuf, @@ -63,22 +99,35 @@ struct ltt_channel *ltt_channel_create(struct ltt_session *session, unsigned int read_timer_interval) { struct ltt_channel *chan; + struct ltt_transport *transport = NULL, *tran_iter; + char *transport_name; mutex_lock(&sessions_mutex); if (session->active) goto active; /* Refuse to add channel to active session */ + transport_name = overwrite ? "relay-overwrite" : "relay-discard"; + list_for_each_entry(tran_iter, <t_transport_list, node) { + if (!strcmp(tran_iter->name, transport_name)) { + transport = tran_iter; + break; + } + } + if (!transport) + goto notransport; chan = kmalloc(sizeof(struct ltt_channel), GFP_KERNEL); if (!chan) - return NULL; + goto nomem; chan->session = session; init_waitqueue_head(&chan->notify_wait); - - /* TODO: create rb channel */ + transport->ops.channel_create(session, buf_addr, subbuf_size, + num_subbuf, switch_timer_interval, + read_timer_interval); list_add(&chan->list, &session->chan); mutex_unlock(&sessions_mutex); return chan; -exist: +nomem: +notransport: active: mutex_unlock(&sessions_mutex); return NULL; @@ -89,7 +138,7 @@ active: */ int _ltt_channel_destroy(struct ltt_channel *chan) { - /* TODO: destroy rb channel */ + transport->ops.channel_destroy(chan); list_del(&chan->list); kfree(chan); } @@ -163,6 +212,46 @@ int _ltt_event_destroy(struct ltt_event *event) kmem_cache_free(event); } +/** + * ltt_transport_register - LTT transport registration + * @transport: transport structure + * + * Registers a transport which can be used as output to extract the data out of + * LTTng. The module calling this registration function must ensure that no + * trap-inducing code will be executed by the transport functions. E.g. + * vmalloc_sync_all() must be called between a vmalloc and the moment the memory + * is made visible to the transport function. This registration acts as a + * vmalloc_sync_all. Therefore, only if the module allocates virtual memory + * after its registration must it synchronize the TLBs. + */ +void ltt_transport_register(struct ltt_transport *transport) +{ + /* + * Make sure no page fault can be triggered by the module about to be + * registered. We deal with this here so we don't have to call + * vmalloc_sync_all() in each module's init. + */ + vmalloc_sync_all(); + + mutex_lock(&sessions_mutex); + list_add_tail(&transport->node, <t_transport_list); + mutex_unlock(&sessions_mutex); +} +EXPORT_SYMBOL_GPL(ltt_transport_register); + +/** + * ltt_transport_unregister - LTT transport unregistration + * @transport: transport structure + */ +void ltt_transport_unregister(struct ltt_transport *transport) +{ + mutex_lock(&sessions_mutex); + list_del(&transport->node); + mutex_unlock(&sessions_mutex); +} +EXPORT_SYMBOL_GPL(ltt_transport_unregister); + + static int __init ltt_events_init(void) { int ret; @@ -170,9 +259,6 @@ static int __init ltt_events_init(void) events_cache = KMEM_CACHE(ltt_event, 0); if (!events_cache) return -ENOMEM; - - /* TODO: show ABI to userspace */ - return 0; } @@ -180,8 +266,6 @@ static void __exit ltt_events_exit(void) { struct ltt_session *session, *tmpsession; - /* TODO: hide ABI from userspace, wait for callers to release refs. */ - list_for_each_entry_safe(session, tmpsession, &sessions, list) ltt_session_destroy(session); kmem_cache_destroy(events_cache); diff --git a/ltt-events.h b/ltt-events.h index 3d509772..b8485c35 100644 --- a/ltt-events.h +++ b/ltt-events.h @@ -47,7 +47,28 @@ struct ltt_session { struct list_head list; /* Session list */ }; +struct ltt_trace_ops { + struct channel *(*channel_create)(const char *name, + struct ltt_trace *trace, + void *buf_addr, + size_t subbuf_size, size_t num_subbuf, + unsigned int switch_timer_interval, + unsigned int read_timer_interval); + void (*channel_destroy)(struct channel *chan); + struct lib_ring_buffer *(*buffer_read_open)(struct channel *chan); + struct lib_ring_buffer *(*buffer_read_close)(struct lib_ring_buffer *buf); +}; + +struct ltt_transport { + char *name; + struct module *owner; + struct list_head node; + struct ltt_trace_ops ops; +}; + struct ltt_session *ltt_session_create(void); +int ltt_session_start(struct ltt_session *session); +int ltt_session_stop(struct ltt_session *session); int ltt_session_destroy(struct ltt_session *session); struct ltt_channel *ltt_channel_create(struct ltt_session *session, @@ -62,3 +83,6 @@ struct ltt_event *ltt_event_create(struct ltt_channel *chan, char *name, void *filter); int _ltt_event_destroy(struct ltt_event *event); + +void ltt_transport_register(struct ltt_transport *transport); +void ltt_transport_unregister(struct ltt_transport *transport); diff --git a/ltt-ring-buffer-client.h b/ltt-ring-buffer-client.h index c70c62b3..93c793ff 100644 --- a/ltt-ring-buffer-client.h +++ b/ltt-ring-buffer-client.h @@ -9,17 +9,11 @@ */ #include +#include +#include +#include "ltt-events.h" #include "ltt-tracer.h" -struct ring_buffer_priv { - struct dentry *dentry; -} - -struct channel_priv { - struct ltt_trace *trace; - struct ring_buffer_priv *buf; -}; - static const struct lib_ring_buffer_config client_config; static u64 client_ring_buffer_clock_read(struct channel *chan) @@ -92,52 +86,10 @@ static void client_buffer_end(struct lib_ring_buffer *buf, u64 tsc, static int client_buffer_create(struct lib_ring_buffer *buf, void *priv, int cpu, const char *name) { - struct channel_priv *chan_priv = priv; - struct ring_buffer_priv *buf_priv; - struct dentry *trace_dentry; - char *tmpname; - int ret = 0; - - if (client_config.alloc == RING_BUFFER_ALLOC_PER_CPU) - buf_priv = per_cpu_ptr(chan_priv->buf, cpu); - else - buf_priv = chan_priv->buf; - - tmpname = kzalloc(NAME_MAX + 1, GFP_KERNEL); - if (!tmpname) { - ret = -ENOMEM; - goto end; - } - - snprintf(tmpname, NAME_MAX, "%s%s_%d", - (client_config.mode == RING_BUFFER_OVERWRITE) ? "flight-" : "", - name, cpu); - - trace_dentry = chan_priv->trace->dentry.trace_root; - buf_priv->dentry = debugfs_create_file(tmpname, S_IRUSR, trace_dentry, - buf, - &lib_ring_buffer_file_operations); - if (!buf_priv->dentry) { - ret = -ENOMEM; - goto free_name; - } -free_name: - kfree(tmpname); -end: - return ret; } static void client_buffer_finalize(struct lib_ring_buffer *buf, void *priv, int cpu) { - struct channel_priv *chan_priv = priv; - struct lib_ring_buffer_priv *buf_priv; - - if (client_config.alloc == RING_BUFFER_ALLOC_PER_CPU) - buf_priv = per_cpu_ptr(chan_priv->buf, cpu); - else - buf_priv = chan_priv->buf; - - debugfs_remove(buf_priv->dentry); } static const struct lib_ring_buffer_config client_config = { @@ -166,54 +118,20 @@ static const struct lib_ring_buffer_config client_config = { }; static -struct channel *ltt_channel_create(const char *name, struct ltt_trace *trace, - void *buf_addr, +struct channel *ltt_channel_create(struct ltt_session *session, void *buf_addr, size_t subbuf_size, size_t num_subbuf, unsigned int switch_timer_interval, unsigned int read_timer_interval) { - struct channel *chan; - struct chan_priv *chan_priv; - - chan_priv = kzalloc(sizeof(struct chan_priv), GFP_KERNEL); - if (!chan_priv) - return NULL; - if (client_config.alloc == RING_BUFFER_ALLOC_PER_CPU) { - chan_priv->buf = alloc_percpu(struct lib_ring_buffer_priv); - memset(chan_priv->buf, 0, sizeof(*chan_priv->buf)); - } else - chan_priv->buf = kzalloc(sizeof(*chan_priv->buf), GFP_KERNEL) - if (!channel_priv->buf) - goto free_chan_priv; - chan_priv->trace = trace; - chan = channel_create(&client_config, name, chan_priv, buf_addr, + return channel_create(&client_config, "[lttng]", session, buf_addr, subbuf_size, num_subbuf, switch_timer_interval, read_timer_interval); - if (!chan) - goto free_buf_priv; - return chan; - -free_buf_priv: - if (client_config.alloc == RING_BUFFER_ALLOC_PER_CPU) - free_percpu(chan_priv->buf); - else - kfree(chan_priv->buf); -free_chan_priv: - kfree(chan_priv); - return NULL; } static void ltt_channel_destroy(struct channel *chan) { - struct chan_priv *chan_priv = channel_get_private(chan); - channel_destroy(chan); - if (client_config.alloc == RING_BUFFER_ALLOC_PER_CPU) - free_percpu(chan_priv->buf); - else - kfree(chan_priv->buf); - kfree(chan_priv); } static @@ -236,37 +154,10 @@ struct lib_ring_buffer *ltt_buffer_read_close(struct lib_ring_buffer *buf) lib_ring_buffer_release_read(buf); } -static void ltt_relay_remove_dirs(struct ltt_trace *trace) -{ - debugfs_remove(trace->dentry.trace_root); -} - -static int ltt_relay_create_dirs(struct ltt_trace *new_trace) -{ - struct dentry *ltt_root_dentry; - int ret; - - ltt_root_dentry = get_ltt_root(); - if (!ltt_root_dentry) - return ENOENT; - - new_trace->dentry.trace_root = debugfs_create_dir(new_trace->trace_name, - ltt_root_dentry); - put_ltt_root(); - if (new_trace->dentry.trace_root == NULL) { - printk(KERN_ERR "LTT : Trace directory name %s already taken\n", - new_trace->trace_name); - return EEXIST; - } - return 0; -} - static struct ltt_transport ltt_relay_transport = { .name = "relay-" RING_BUFFER_MODE_TEMPLATE_STRING, .owner = THIS_MODULE, .ops = { - .create_dirs = ltt_relay_create_dirs, - .remove_dirs = ltt_relay_remove_dirs, .create_channel = ltt_channel_create, .destroy_channel = ltt_channel_destroy, .buffer_read_open = ltt_buffer_read_open, diff --git a/ltt-tracer.h b/ltt-tracer.h index f75a0704..ca6187bf 100644 --- a/ltt-tracer.h +++ b/ltt-tracer.h @@ -123,30 +123,12 @@ struct user_dbg_data { unsigned long read; }; -struct ltt_trace_ops { - int (*create_dirs) (struct ltt_trace *new_trace); - void (*remove_dirs) (struct ltt_trace *new_trace); - struct channel *ltt_channel_create(const char *name, - struct ltt_trace *trace, - void *buf_addr, - size_t subbuf_size, size_t num_subbuf, - unsigned int switch_timer_interval, - unsigned int read_timer_interval); - void ltt_channel_destroy(struct channel *chan); -}; - -struct ltt_transport { - char *name; - struct module *owner; - struct list_head node; - struct ltt_trace_ops ops; -}; - enum trace_mode { LTT_TRACE_NORMAL, LTT_TRACE_FLIGHT, LTT_TRACE_HYBRID }; #define CHANNEL_FLAG_ENABLE (1U<<0) #define CHANNEL_FLAG_OVERWRITE (1U<<1) +#if 0 /* Per-trace information - each trace/flight recorder represented by one */ struct ltt_trace { /* First 32 bytes cache-hot cacheline */ @@ -172,6 +154,7 @@ struct ltt_trace { wait_queue_head_t kref_wq; /* Place for ltt_trace_destroy to sleep */ char trace_name[NAME_MAX]; } ____cacheline_aligned; +#endif //0 /* * Hardcoded event headers @@ -513,9 +496,6 @@ extern int ltt_module_register(enum ltt_module_function name, void *function, struct module *owner); extern void ltt_module_unregister(enum ltt_module_function name); -void ltt_transport_register(struct ltt_transport *transport); -void ltt_transport_unregister(struct ltt_transport *transport); - /* Exported control function */ enum ltt_control_msg { @@ -537,47 +517,10 @@ union ltt_control_args { } new_trace; }; -int _ltt_trace_setup(const char *trace_name); -int ltt_trace_setup(const char *trace_name); -struct ltt_trace *_ltt_trace_find_setup(const char *trace_name); -int ltt_trace_set_type(const char *trace_name, const char *trace_type); -int ltt_trace_set_channel_subbufsize(const char *trace_name, - const char *channel_name, - unsigned int size); -int ltt_trace_set_channel_subbufcount(const char *trace_name, - const char *channel_name, - unsigned int cnt); -int ltt_trace_set_channel_switch_timer(const char *trace_name, - const char *channel_name, - unsigned long interval); -int ltt_trace_set_channel_overwrite(const char *trace_name, - const char *channel_name, - unsigned int overwrite); -int ltt_trace_alloc(const char *trace_name); -int ltt_trace_destroy(const char *trace_name); -int ltt_trace_start(const char *trace_name); -int ltt_trace_stop(const char *trace_name); - -extern int ltt_control(enum ltt_control_msg msg, const char *trace_name, - const char *trace_type, union ltt_control_args args); - -enum ltt_filter_control_msg { - LTT_FILTER_DEFAULT_ACCEPT, - LTT_FILTER_DEFAULT_REJECT -}; - -extern int ltt_filter_control(enum ltt_filter_control_msg msg, - const char *trace_name); - -extern struct dentry *get_filter_root(void); - void ltt_core_register(int (*function)(u8, void *)); void ltt_core_unregister(void); -void ltt_release_trace(struct kref *kref); -void ltt_release_transport(struct kref *kref); - extern int ltt_probe_register(struct ltt_available_probe *pdata); extern int ltt_probe_unregister(struct ltt_available_probe *pdata); extern int ltt_marker_connect(const char *channel, const char *mname, @@ -586,14 +529,6 @@ extern int ltt_marker_disconnect(const char *channel, const char *mname, const char *pname); extern void ltt_dump_marker_state(struct ltt_trace *trace); -void ltt_lock_traces(void); -void ltt_unlock_traces(void); - -extern int ltt_ascii_create_dir(struct ltt_trace *new_trace); -extern void ltt_ascii_remove_dir(struct ltt_trace *trace); -extern int ltt_ascii_create(struct ltt_chan *chan); -extern void ltt_ascii_remove(struct ltt_chan *chan); - extern void ltt_statedump_register_kprobes_dump(void (*callback)(void *call_data)); extern -- 2.34.1